Canvas animation react - reactjs

I want to create an animated canvas in ReactJs. I have a functional component. I created a class named circle with two methods: draw and update. When I instance circle, I want to call draw, but i have this error from the browser:
TypeError: cir.draw is not a function
I tried with const draw = function() {...} but is the same.
If you know a better way to crate an animated canvas I'm opened for suggestions. In Js it is easiest for me, but React has some difficulties for do that, I think.
This is my code.
import React, { useState, useEffect } from "react";
export default function Home() {
var canvas;
var c;
var radius = 40;
var circle = function (x, y, dx, dy) {
const draw = () => {
c.beginPath();
c.arc(x, y, radius, 0, Math.PI * 2, false);
c.strokeStyle = "#ccc";
c.stroke();
};
const update = () => {
if (x + radius > window.innerWidth || x - radius < 0) dx = -dx;
if (y + radius > window.innerHeight || y - radius < 0) dy = -dy;
x += dx;
y += dy;
};
};
var Animate = () => {
requestAnimationFrame(Animate);
var x = Math.random() * window.innerWidth;
var y = Math.random() * window.innerHeight;
var dx = (Math.random() - 0.5) * 15;
var dy = (Math.random() - 0.5) * 50;
let cir = new circle(x, y, dx, dy, c);
cir.draw();
console.log(cir);
};
useEffect(() => {
var canvas = document.querySelector("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var c = canvas.getContext("2d");
console.log("a");
Animate();
}, []);
return (
<div>
<canvas></canvas>
</div>
);
}

Related

How to fill a box with boxGeometry randomly and no overlap?

I am using THREE.js in react.
I want to fill 500 number of 1X1X1 box geometry in a 200x200x200 area randomly.
The following code shows how to get the position.xyz with the range from -100 to 100.
However, how to get the position that all 1X1X1 box geometry could not touch each other (no overlapping)?
I have no idea.
function Geometry() {
const geo_num = 500;
const box_length = 200;
const geo_position = useMemo(() => {
const array = []
for (let i = 0; i < geo_num; i++) {
const xdirection = Math.round(Math.random()) * 2 - 1;
const ydirection = Math.round(Math.random()) * 2 - 1;
const zdirection = Math.round(Math.random()) * 2 - 1;
const xpos = Math.random() * box_length * xdirection * 0.5;
const ypos = Math.random() * box_length * ydirection * 0.5;
const zpos = Math.random() * box_length * zdirection * 0.5;
array.push(new THREE.Vector3(xpos, ypos, zpos))
}
return array
})
return (
<>
{
geo_position.map((element, index) => (
<mesh position={element} key={index} >
<boxGeometry args={[1, 1, 1]} />
</mesh>
))
}
</>
)
}
A very rough concept of how you can do it, using an array and its .includes() method:
body{
overflow: hidden;
margin: 0;
}
<script type="module">
import * as THREE from "https://cdn.skypack.dev/three#0.136.0";
import {OrbitControls} from "https://cdn.skypack.dev/three#0.136.0/examples/jsm/controls/OrbitControls";
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(50, 50, 250);
let renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", event => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
let contols = new OrbitControls(camera, renderer.domElement);
let boxCount = 500;
let range = [-100, 100];
let v3Array = [];
let counter = 0;
let v3 = new THREE.Vector3();
while(counter < boxCount){
let v3 = [
THREE.MathUtils.randInt(range[0], range[1]).toFixed(0),
THREE.MathUtils.randInt(range[0], range[1]).toFixed(0),
THREE.MathUtils.randInt(range[0], range[1]).toFixed(0),
].join("|");
if (!v3Array.includes(v3)){
v3Array.push(v3);
counter++
}
}
v3Array.map( p => {
let o = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshBasicMaterial({color: Math.random() * 0xffffff, wireframe: true}));
let pos = p.split("|").map(c => {return parseInt(c)});
o.position.set(pos[0], pos[1], pos[2]);
scene.add(o);
});
let boxHelper = new THREE.Box3Helper(new THREE.Box3(new THREE.Vector3().setScalar(range[0] - 0.5), new THREE.Vector3().setScalar(range[1] + 0.5)), "yellow");
scene.add(boxHelper);
renderer.setAnimationLoop( () => {
renderer.render(scene, camera);
})
</script>

position undefined running Three.js component in React

I am running a three.js component inside a React (Typescript) app, everything was working fine rendering my 3D object, until I started to add some more functionality with vertex colors and now I am getting an error "Cannot read properties of undefined (reading 'position')" at line 27:
const count = geometry.attributes.position.count;
Is there an issue I am facing here with scope or its implementation as a component?
import React from "react";
import ReactDOM from "react-dom";
import * as THREE from "three";
class MyBox extends React.Component {
componentDidMount() {
let mouseX = 0;
let mouseY = 0;
let windowHalfX = window.innerWidth / 2;
let windowHalfY = window.innerHeight / 2;
const radius = 1;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth / 2, window.innerHeight / 2);
this.mount.appendChild(renderer.domElement);
var geometry = new THREE.IcosahedronGeometry(radius, 1);
const count = geometry.attributes.position.count;
geometry.setAttribute('color', new THREE.BufferAttribute(new Float32Array(count * 3), 3));
const color = new THREE.Color();
const positions = geometry.attributes.position;
const colors = geometry.attributes.color;
for (let i = 0; i < count; i++) {
color.setHSL((positions.getY(i) / radius + 1) / 2, 1.0, 0.5);
colors.setXYZ(i, color.r, color.g, color.b);
}
var material = new THREE.MeshPhongMaterial({ color: 0x7e31eb, vertexColours: true, shininess: 100 });
var cube = new THREE.Mesh(geometry, material);
scene.add(cube);
var wiregeometry = new THREE.IcosahedronGeometry(radius + 0.01, 1);
var wirematerial = new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true });
var wirecube = new THREE.Mesh(wiregeometry, wirematerial);
scene.add(wirecube);
const light = new THREE.HemisphereLight(0xffffbb, 0x080820, 1);
scene.add(light);
camera.position.z = 2;
document.addEventListener('mousemove', onDocumentMouseMove);
function onDocumentMouseMove(event) {
mouseX = (event.clientX - windowHalfX);
mouseY = (event.clientY - windowHalfY);
}
var animate = function() {
requestAnimationFrame(animate);
wirecube.rotation.x += mouseY / 20000;
wirecube.rotation.y += mouseX / 20000;
wirecube.rotation.z += 0.00;
cube.rotation.x += mouseY / 20000;
cube.rotation.y += mouseX / 20000;
cube.rotation.z += 0.00;
renderer.render(scene, camera);
};
animate();
renderer.render(scene, camera);
}
render() {
return ( <
div ref = { ref => (this.mount = ref) }
/>
)
}
}
const rootElement = document.getElementById("root")
ReactDOM.render( < MyBox / > , rootElement);
export default MyBox;
Similarly, if I write
var position = geometry.getAttribute('position');
I get a "geometry.getAttribute is not a function" error, which seems odd because that code would usually work for me.
Users encountered problems when mixing ThreeJS version with incompatible plugins. Try using the files for everything from the same release.
Example of a user using incompatible versions: https://discourse.threejs.org/t/buffergeometry-setattribute-is-not-a-function-error/22856

I have a relatively simple ReactJSX application and I'm not sure why I'm getting a missing ";" on both Player move(dx, dy) and draw(context) error

I have a relatively simple ReactJSX application and I'm not sure why I'm getting a missing ";" on move(dx, dy) and draw(context) error.
App.js
import React from 'react'
import ReactRogue from './ReactRougue'
const App = () => (
<div className="App">
<ReactRogue />
</div>
)
export default App
ReactRogue.js
import React, { useRef, useEffect, useState } from 'react'
import Player from './Player'
import InputManager from './InputManager'
const ReactRougue = ({ width, height, tileSize }) => {
const canvasRef = useRef()
const [player, setPlayer] = useState(new Player(1, 2, tileSize))
let InputManager = new InputManager()
const handleInput = (action, data)
let newPlayer = new Player()
object.asign(newPlayer)
newPlayer.move(data.x, data.y)
console.log('drawing to canvas....')
const context = canvasRef.current.getContext('2d')
context.clearRect(0, 0, width * tileSize, height * tileSize)
context.draw(context)
}
export default ReactRogue
Player.js
class Player {
constructor(x, y, tilesize) {
this.x = x;
this.y = y;
this.tilesize = tilesize;
move(dx, dy) {
this.x += dx;
this.dy += dy;
}
draw(context) {
context.fillStyle = "#f00";
context.textBaseline = "Hanging";
context.font = "16px Helevetica";
context.fillText('#', this.x * this.tilesize, this.y * this.tilesize);
}
}
}
export default Player
Any help would be be greatly appreciated.
Thanks
Dawson
why are you defining functions in the constructor? Put it outside the constructor, in the class, and use arrow functions, like so:
class Player {
// constructor
move = (dx, dy) => {
this.x += dx;
this.dy += dy;
}
draw = context => {
context.fillStyle = "#f00";
context.textBaseline = "Hanging";
context.font = "16px Helevetica";
context.fillText('#', this.x * this.tilesize, this.y * this.tilesize);
}
}
and in ReactRogue, each line does not have a semicolon:
// you put ReactRougue
const ReactRogue = ({ width, height, tileSize }) => {
// useRef what?
const canvasRef = useRef();
const [player, setPlayer] = useState(new Player(1, 2, tileSize));
let InputManager = new InputManager();
// is this a function? if so, then you have to put
// const handleInput = (action, data) => {function goes here}
const handleInput = (action, data);
let newPlayer = new Player();
// you put object.asign()
object.assign(newPlayer);
newPlayer.move(data.x, data.y);
console.log('drawing to canvas....');
const context = canvasRef.current.getContext('2d');
context.clearRect(0, 0, width * tileSize, height * tileSize);
context.draw(context);
}
As JS-Skipper suggested I refactored my code and move things out of constructors and everything starched working fine. Thanks for the help.
Dawson

drawing 4 circles in a canvas in reactjs

I am trying to draw multi color circles in a canvas image. When I draw one circle previous circle was deleted.So I make a function to replicate the previous circle but there I was unable to get the exact coordinate of the center of the circle and a straight line is created. I am sharing my code.
In the draw circle function I need the proper centerx and centery.
class Circle extends Component {
constructor(props) {
super(props);
//added state
this.state = {
isDown: false,
previousPointX: '',
previousPointY: '',
base_image: {},
circleConfig: {
maxCircle: 4,
color: ["red", "blue", "#ffa500", "green"]
},
circles: [],
canvasId: this.props.canvasid,
rotate:this.props.rotate
}
this.handleMouseDown = this.handleMouseDown.bind(this);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.handleMouseUp = this.handleMouseUp.bind(this);
}
render() {
return (
<div>
<canvas ref="canvas" className="CursorCanvas"
width={400}
height={390}
onMouseDown={
e => {
let nativeEvent = e.nativeEvent;
this.handleMouseDown(nativeEvent);
}}
onMouseMove={
e => {
let nativeEvent = e.nativeEvent;
this.handleMouseMove(nativeEvent);
}}
onMouseUp={
e => {
let nativeEvent = e.nativeEvent;
this.handleMouseUp(nativeEvent);
}}
/>
<pre hidden>{JSON.stringify(this.state.lines, null, 2)}</pre>
</div>
);
}
drawCircle(circles, ctx) {
console.log('draw circle',circles)
circles.forEach((item) => {
var r=(item.endx-item.startx)/2;
var centerx=(item.endx-item.startx)/2;
var centery=(item.endy-item.starty)/2;
ctx.arc(centerx, centery, r, 0, 2 * Math.PI);
ctx.strokeStyle = item.color ;
})
}
handleMouseDown(event) {
if (this.state.circles.length >= this.state.circleConfig.maxCircle) return;
this.setState({
isDown: true,
previousPointX: event.offsetX,
previousPointY: event.offsetY
},()=>{
console.log('mousedown',this.state)
})
}
handleMouseMove(event){
if (this.state.isDown) {
event.preventDefault();
event.stopPropagation();
const canvas = ReactDOM.findDOMNode(this.refs.canvas);
var x = event.offsetX;
var y = event.offsetY;
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(this.state.base_image, 0, 0);
//Save
ctx.save();
ctx.beginPath();
this.drawCircle(this.state.circles,ctx);
var circleLength = this.state.circles.length || 0;
//Dynamic scaling
var scalex = (x-this.state.previousPointX)/2;
var scaley = (y-this.state.previousPointY)/2;
ctx.scale(scalex,scaley);
//Create ellipse
var centerx = (this.state.previousPointX/scalex)+1;
var centery = (this.state.previousPointY/scaley)+1;
ctx.arc(centerx, centery, 1, 0, 2*Math.PI);
ctx.restore();
ctx.stroke();
ctx.strokeStyle = this.state.circleConfig.color[circleLength];;
}
}
handleMouseUp(event) {
if (this.state.circles.length >= this.state.circleConfig.maxCircle) return;
this.setState({
isDown: false
});
console.log('mouseup',this.state)
const canvas = ReactDOM.findDOMNode(this.refs.canvas);
var x = event.offsetX;
var y = event.offsetY;
var ctx = canvas.getContext("2d");
var circleLength = this.state.circles.length || 0;
if (this.state.previousPointX !== x && this.state.previousPointY !== y) {
this.setState({
circles: this.state.circles.concat({
startx: this.state.previousPointX,
starty: this.state.previousPointY,
endx: x,
endy: y,
color: this.state.circleConfig.color[circleLength]
})
},
() => {
ctx.stroke();
ctx.closePath();
this.props.drawCircleToStore(this.state.circles, this.state.canvasId, this.props.imgSrc,this.state.rotate)
}
);
}
}
componentDidMount(){
const canvas = ReactDOM.findDOMNode(this.refs.canvas);
const ctx = canvas.getContext("2d");
const base_image = new Image();
base_image.src = this.props.imgSrc
base_image.onload = function (){
ctx.drawImage(base_image, 0, 0);
}
this.setState({
base_image: base_image
});
}
}
In the drawCircle metho the center coordinate wil be
var r = (item.endx - item.startx) / 2;
centerx = (r + item.startx);
centery = (r + item.starty);
ctx.arc(centerx, centery, r, 0, 2 * Math.PI);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
class CircleMultiple extends Component {
constructor(props) {
super(props);
//added state
this.state = {
isDown: false,
previousPointX: '',
previousPointY: '',
base_image: {},
circleConfig: {
maxCircle: 4,
},
circles: [],
canvasId: this.props.canvasid,
rotate: this.props.rotate
}
this.handleMouseDown = this.handleMouseDown.bind(this);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.handleMouseUp = this.handleMouseUp.bind(this);
}
render() {
return (
<div>
<canvas ref="canvas" className="CursorCanvas" width="300" height="300"
onMouseDown={
e => {
let nativeEvent = e.nativeEvent;
this.handleMouseDown(nativeEvent);
}}
onMouseMove={
e => {
let nativeEvent = e.nativeEvent;
this.handleMouseMove(nativeEvent);
}}
onMouseUp={
e => {
let nativeEvent = e.nativeEvent;
this.handleMouseUp(nativeEvent);
}}
/>
<pre hidden>{JSON.stringify(this.state.circles, null, 2)}</pre>
</div>
);
}
drawCircle(circles, ctx) {
circles.forEach((item) => {
ctx.beginPath();
var r = (item.endx - item.startx) / 2;
var centerx = (r + item.startx);
var centery = (r + item.starty);
ctx.arc(centerx, centery, r, 0, 2 * Math.PI);
ctx.stroke();
ctx.closePath();
})
}
handleMouseDown(event) {
if (this.state.circles.length >= this.state.circleConfig.maxCircle) return;
this.setState({
isDown: true,
previousPointX: event.offsetX,
previousPointY: event.offsetY
}, () => {
// console.log('mousedown',this.state)
})
}
handleMouseMove(event) {
if (this.state.isDown) {
event.preventDefault();
event.stopPropagation();
const canvas = ReactDOM.findDOMNode(this.refs.canvas);
var x = event.offsetX;
var y = event.offsetY;
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
// ctx.drawImage(this.state.base_image, 0, 0);
ctx.drawImage(this.state.base_image,
canvas.width / 2 - this.state.base_image.width / 2,
canvas.height / 2 - this.state.base_image.height / 2
);
//Save
ctx.save();
ctx.beginPath();
this.drawCircle(this.state.circles, ctx);
// var circleLength = this.state.circles.length || 0;
//Dynamic scaling
var scalex = (x - this.state.previousPointX) / 2;
var scaley = (y - this.state.previousPointY) / 2;
ctx.scale(scalex, scaley);
//Create ellipse
var centerx = (this.state.previousPointX / scalex) + 1;
var centery = (this.state.previousPointY / scaley) + 1;
ctx.beginPath();
ctx.arc(centerx, centery, 1, 0, 2 * Math.PI);
ctx.restore();
ctx.stroke();
// ctx.strokeStyle = this.state.circleConfig.color[circleLength];
// console.log('centerx',centerx,'centery',centery)
}
}
handleMouseUp(event) {
if (this.state.circles.length >= this.state.circleConfig.maxCircle) return;
this.setState({
isDown: false
});
// console.log('mouseup',this.state)
var x = event.offsetX;
var y = event.offsetY;
if (this.state.previousPointX !== x && this.state.previousPointY !== y) {
this.setState({
circles: this.state.circles.concat({
startx: this.state.previousPointX,
starty: this.state.previousPointY,
endx: x,
endy: y,
r: (x - this.state.previousPointX) / 2,
centerx: (((x - this.state.previousPointX) / 2) + this.state.previousPointX),
centery: (((x - this.state.previousPointX) / 2) + this.state.previousPointY)
// color: this.state.circleConfig.color[circleLength]
})
},
() => {
//console.log('mouseup', this.state);
}
);
}
}
componentDidMount() {
const canvas = ReactDOM.findDOMNode(this.refs.canvas);
const ctx = canvas.getContext("2d");
const base_image = new Image();
base_image.src = this.props.imgSrc
base_image.onload = function () {
// ctx.drawImage(base_image, 0, 0);
ctx.drawImage(base_image,
canvas.width / 2 - base_image.width / 2,
canvas.height / 2 - base_image.height / 2
);
}
this.setState({
base_image: base_image
});
}
}
export default CircleMultiple;
To remove the connecting line between circles need to add
ctx.beginPath();
before calling drawCircle() method.

D3.js V4 zoom doesn't work in React + faux

I'm trying to zoom my timeline by x axis,
but it doesn't show any reaction on zooming, I've looked through many tutorials, tried separate zoom functions, eventually here what I've got (with this https://coderwall.com/p/psogia/simplest-way-to-add-zoom-pan-on-d3-js manual):
const { dataset } = this.props;
var minDt = 0;
var maxDt = 300;
var intervalMinWidth = 1;
var elementWidth = 1200; // TODO: make adoptable
var elementHeight = 200;
var width = 500;
var height = 600;
var groupHeight = 20;
var xTickSize = 5;
var x = d3.scaleLinear()
.domain([minDt, maxDt])
.range([0, width]);
var xAxis = d3.axisBottom(x)
.tickSize(xTickSize);
var svg = d3.select(svgRoot)
.append('svg')
.attr('id', 'chart')
.attr('class', 'timeline-chart')
.attr("width", "100%")
.attr("height", "100%")
.call(d3.zoom().on("zoom", function () {
svg.attr("transform", d3.event.transform)
}))
.append('g');
var gX = svg.append("g")
.attr("class", "axis axis--x")
.call(xAxis);
var groupExonItems = svg
.selectAll('.group-interval-item')
.data(dataset)
.enter()
.append('g')
.attr('clip-path', 'url(#chart-content)')
.attr('class', 'item')
.attr('transform', function (d, i) {
return 'translate(0, ' + groupHeight * i + ')';
})
.selectAll('.rect').data(function (d) {
return d.data.filter(function (_) {
return _.type === 'exon';
});
}).enter();
var exonBarHeight = 0.8 * groupHeight;
var exonBarMargin = 30;
var exons = groupExonItems
.append('rect')
.attr('class', 'interval')
.attr('width', function (d) {
return Math.max(intervalMinWidth, x(d.to) - x(d.from));
})
.attr('height', exonBarHeight)
.attr('y', exonBarMargin)
.attr('x', function (d) {
return x(d.from);
});
I have tried separate function, not any progress as well :
function zoomFunction(){
var new_xScale = d3.event.transform.rescaleX(x)
gX.call(xAxis.scale(new_xScale));
svg.selectAll('rect.interval').attr('x', function (d) {
return x(d.from);
}).attr('width', function (d) {
return Math.max(intervalMinWidth, x(d.to) - x(d.from));
});
};
If I log zoom function - I can see it's reachable, and scale changes, but not for my rectangles.
May be the reason is I'm using react + faux to draw svg, I have tried without faux - by putting D3 code into the componentDidMount function - still no any progress.
What am I missing? Appreciate any help.
Even react-faux-dom is the simplest way to render D3, it breaks dynamic changes of the object, such as zoom. And without faux here is a very simple example to implement zoom: https://swizec.com/blog/two-ways-build-zoomable-dataviz-component-d3-zoom-react/swizec/7753
As far as I need only x-axis zoom, here is solution:
import { Component} from 'react'
import { PropTypes} from 'prop-types'
import React from 'react'
import ReactDOM from 'react-dom';
import ReactFauxDOM from 'react-faux-dom'
import * as d3 from 'd3'
import styles from './chart.css';
const random = d3.randomNormal(2, 1);
class Scatterplot extends React.Component {
constructor(props) {
super(props);
this.updateD3(props);
}
componentWillUpdate(nextProps) {
this.updateD3(nextProps);
}
updateD3(props) {
const { data, width, height, zoomTransform } = props;
this.xScale = d3.scaleLinear()
.domain([0, d3.max(data, ([x, y]) => x)])
.range([0, width]);
}
get transform() {
const { x, y, zoomTransform } = this.props;
let transform = "";
if (zoomTransform) {
transform = `translate(${x + zoomTransform.x}, ${y}) scale(${zoomTransform.k} 1)`;
}
return transform;
}
render() {
const { data } = this.props;
return (
<g transform={this.transform} ref="scatterplot">
{data.map(([x, y]) => <rect x={this.xScale(x)} y={y} width={this.xScale(4)} height = {5} />)}
</g>
)
}
}
class Chart extends React.Component {
constructor(props) {
super(props);
this.state = {
data: d3.range(5).map(_ => [random(), random()]),
zoomTransform: null
}
this.zoom = d3.zoom()
.scaleExtent([-5, 5])
.translateExtent([[-100, -100], [props.width+100, props.height]])
.extent([[-100, -100], [props.width+100, props.height]])
.on("zoom", this.zoomed.bind(this))
}
componentDidMount() {
d3.select(this.refs.svg)
.call(this.zoom)
}
componentDidUpdate() {
d3.select(this.refs.svg)
.call(this.zoom)
}
zoomed() {
this.setState({
zoomTransform: d3.event.transform
});
}
render() {
const { zoomTransform } = this.state,
{ width, height } = this.props;
return (
<svg width={width} height={height} ref="svg">
<Scatterplot data={this.state.data}
x={0} y={0}
width={width/2}
height={height}
zoomTransform={zoomTransform}/>
</svg>
)
}
}
export default Chart

Resources