When I click on the spin button, I want the value to be highlighted one by one in the canvas.
Here you can see the whole code: https://codesandbox.io/s/spinning-wheel-react-forked-s528m
renderWheel() {
// determine number/size of sectors that need to created
let numOptions = this.state.list.length;
let arcSize = (2 * Math.PI) / numOptions;
this.setState({
angle: arcSize
});
// dynamically generate sectors from state list
let angle = 0;
for (let i = 0; i < numOptions; i++) {
let text = this.state.list[i];
this.renderSector(i + 1, text, angle, arcSize);
angle += arcSize;
}
}
renderSector(index, text, start, arc) {
// create canvas arc for each list element
let canvas = document.getElementById("wheel");
let ctx = canvas.getContext("2d");
let x = canvas.width / 2;
let y = canvas.height / 2;
let radius = 200;
let startAngle = start;
let endAngle = start + arc - 0.02;
let angle = index * arc;
let baseSize = radius * 1.25;
let textRadius = baseSize - 48;
ctx.beginPath();
ctx.arc(x, y, radius, startAngle, endAngle, false);
ctx.lineWidth = radius * 0.25;
ctx.strokeStyle = "white";
ctx.font = "25px Arial";
ctx.fillStyle = "blue";
ctx.stroke();
ctx.save();
ctx.translate(
baseSize + Math.cos(angle - arc / 2) * textRadius,
baseSize + Math.sin(angle - arc / 2) * textRadius
);
ctx.rotate(0);
ctx.fillText(text, -ctx.measureText(text).width / 2, 7);
ctx.restore();
}
When I click on the spin button, this spin () method is executed.
The iteration ends between two and five seconds as written in the setTimeout method.
spin = () => {
var index = 0;
let timer = setInterval(() => {
index = (index) % this.state.list.length + 1;
console.log(index)
}, 150);
// calcalute result after wheel stops spinning
setTimeout(() => {
clearInterval(timer);
this.setState({
hasFinishedLoading: true,
display: this.state.list[index]
});
}, Math.floor(Math.random() * 5000 + 2000));
this.setState({
spinning: true
});
};
Remember: In canvas you cannot access a particular object, so the solution is always to erase and redraw. Don't worry about performance, this is pretty great. I leave you an idea with your code and at the end some suggestions. Try this code, the only thing I did is clean the canvas and redraw without the setTimeOut index
spin = () => {
let canvas = document.getElementById("wheel");
let ctx = canvas.getContext("2d");
var index = 0;
index = (index % this.state.list.length) + 1;
let angle = 0;
let numOptions = this.state.list.length;
let arcSize = (2 * Math.PI) / numOptions;
let timer = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
this.setState({
angle: arcSize
});
for (let i = 0; i < numOptions; i++) {
if (i !== index) {
let text = this.state.list[i];
this.renderSector(i + 1, text, angle, arcSize);
angle += arcSize;
} else {
}
}
}, 150);
// calcalute result after wheel stops spinning
setTimeout(() => {
clearInterval(timer);
this.setState({
hasFinishedLoading: true,
display: this.state.list[index]
});
}, Math.floor(Math.random() * 5000 + 2000));
this.setState({
spinning: true
});
};
Don't use "getElementById" in react, it's dangerous. Use a reference.
Related
here I have the pattern file hiro.patt & pattern-markerobj1.patt
let patternArray = ["hiro", "pattern-markerobj1"];
pattern hiro.patt represents mtl-obj Fish
pattern pattern-markerobj1.patt mtl-obj represents
let mtlobj = ['fish-2','test',];
file obj dan mtl
but when it is executed, all 3D models are only displayed on the 1st marker index or 2nd marker, namely pattern-markerobj1.
however when I use THREE.MeshBasicMaterial by using color in array
let colorArray = [0xff8800, 0xffff00, ]; they seemed no problem.
can be seen in the image below
Hiro without 3D Model without 3D Model :')
pattern-markerobj1 with 2 3D Models (stacked) pattern-markerobj1
then how to fix it?
hope
marker Hiro -> mtl obj fish
marker pattern-markerobj1 -> test mtl obj
let patternArray = ["hiro", "pattern-markerobj1"];
let colorArray = [0xff8800, 0xffff00, ];
let mtlobj = ['fish-2', 'percobaan', ];
let scale = [0.25, 0.0025, ];
for (let i = 0; i < 2; i++) {
markerRoot1 = new THREE.Group();
scene.add(markerRoot1);
let markerControls1 = new THREEx.ArMarkerControls(arToolkitContext, markerRoot1, {
type: 'pattern',
patternUrl: "data/" + patternArray[i] + ".patt",
})
let geometry1 = new THREE.PlaneBufferGeometry(1, 1, 4, 4);
// let texture = loader.load( 'images/earth.jpg', render );
let material1 = new THREE.MeshBasicMaterial({
color: colorArray[i],
opacity: 0.0005
});
mesh1 = new THREE.Mesh(geometry1, material1);
mesh1.rotation.x = -Math.PI / 2;
markerRoot1.add(mesh1);
function onProgress(xhr) {
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
}
function onError(xhr) {
console.log('An error happened');
}
new THREE.MTLLoader()
.setPath('models/')
.load(mtlobj[i] + '.mtl', function (materials) {
materials.preload();
new THREE.OBJLoader()
.setMaterials(materials)
.setPath('models/')
.load(mtlobj[i] + '.obj', function (group) {
mesh0 = group.children[0];
mesh0.material.side = THREE.DoubleSide;
mesh0.position.y = 0.25;
mesh0.scale.set(scale[i], scale[i], scale[i]);
markerRoot1.add(mesh0);
}, onProgress, onError);
});
}
I'm working with react-three-fiber and struggling with Line2 and some memory issues. I left my application running for a time and then when I came back my computer was frozen... After I recovered I realized that the the application was gradually consuming more and more memory and sometimes, but not always, showing this error: "webgl warning: drawelementsinstanced: instance fetch requires 30, but attribs only supply 20". I tried changing the getPoints divisions and the number of points in the line itself and I was able to make things a little bit better, but it still didn't solve the issue. The problem seems to be comming from the 'getPoints' method, but I'm not quite so sure. Can someone help me?
export function SingleTrace({
particleSystem,
physics,
steps,
detail,
width,
color,
}) {
const line = useRef();
let positions = useMemo(() => [], [physics]);
useFrame(() => {
let stepsSoFar = positions.length;
if (stepsSoFar === 0) {
let randomX = ((Math.random() - 1 / 2) * particleSystem.boundary.w +
particleSystem.boundary.x) as number;
let randomY = ((Math.random() - 1 / 2) * particleSystem.boundary.h +
particleSystem.boundary.y) as number;
let randomZ = ((Math.random() - 1 / 2) * particleSystem.boundary.d +
particleSystem.boundary.z) as number;
positions = [vec(randomX, randomY, randomZ)]; //otherwise just use a random position
} else if (stepsSoFar > steps) {
positions.shift();
}
let lastPosition = positions[positions.length - 1];
let totalField = vec();
particleSystem.particles.forEach((particle) => {
let field = particle["physics"][physics].field(
lastPosition
);
totalField.add(field);
});
let newPosition = vec()
.copy(lastPosition)
.add(vec().copy(totalField).setLength(detail));
if (particleSystem.boundary.contains(newPosition)) {
positions.push(newPosition);
}
let willRestart = Math.random() < 0.05 ? true : false;
if (willRestart) {
let randomX =
(Math.random() - 1 / 2) * particleSystem.boundary.w +
particleSystem.boundary.x;
let randomY =
(Math.random() - 1 / 2) * particleSystem.boundary.h +
particleSystem.boundary.y;
let randomZ =
(Math.random() - 1 / 2) * particleSystem.boundary.d +
particleSystem.boundary.z;
positions = [vec(randomX, randomY, randomZ)];
}
if (line.current && positions.length > 2) {
let curve = new THREE.CatmullRomCurve3(positions).getPoints(
positions.length
);
let convertedToArray = [];
curve.forEach((item) => convertedToArray.push(item.x, item.y, item.z));
line.current.setPositions(convertedToArray);
}
});
return (
<>
<line2>
<lineGeometry ref={line} />
<lineMaterial
color={color}
linewidth={width}
resolution={new THREE.Vector2(10, 10)}
/>
</line2>
</>
);
}
P.S. vec() is just new THREE.Vector3(0,0,0)
I am rendering Highcharts 3D scatter graph and enabled scrolling as follows
xAxis: {
categories: xlist,
min: 0,
max: 4,
scrollbar: {
enabled: true
},
}
Also having chart scrolling with below code
$(chart.container).on('mousedown.hc touchstart.hc', function (eStart) {
eStart = chart.pointer.normalize(eStart);
var posX = eStart.chartX,
posY = eStart.chartY,
alpha = chart.options.chart.options3d.alpha,
beta = chart.options.chart.options3d.beta,
newAlpha,
newBeta,
sensitivity = 5; // lower is more sensitive
$(document).on({
'mousemove.hc touchmove.hc': function (e) {
e = chart.pointer.normalize(e);
newBeta = beta + (posX - e.chartX) / sensitivity;
chart.options.chart.options3d.beta = newBeta;
newAlpha = alpha + (e.chartY - posY) / sensitivity;
chart.options.chart.options3d.alpha = newAlpha;
chart.redraw(false);
},
'mouseup touchend': function () {
$(document).off('.hc');
}
});
});
If I scroll through scrollbar then the chart also moves. Is there any way to avoid chart scrolling if I scroll the scrollbar and if I take mouse on chart then the chart should move.
I made a hack and updated the above function as below, where I have restricted the chart rotation with if(e.target.classList.length == 0) when we use scrollbar.
$(chart.container).on('mousedown.hc touchstart.hc', function (eStart) {
eStart = chart.pointer.normalize(eStart);
var posX = eStart.chartX,
posY = eStart.chartY,
alpha = chart.options.chart.options3d.alpha,
beta = chart.options.chart.options3d.beta,
newAlpha,
newBeta,
sensitivity = 5; // lower is more sensitive
$(document).on({
'mousemove.hc touchmove.hc': function (e) {
if (e.target.classList.length == 0) {
e = chart.pointer.normalize(e);
newBeta = beta + (posX - e.chartX) / sensitivity;
chart.options.chart.options3d.beta = newBeta;
newAlpha = alpha + (e.chartY - posY) / sensitivity;
chart.options.chart.options3d.alpha = newAlpha;
chart.redraw(false);
}
},
'mouseup touchend': function () {
$(document).off('.hc');
}
});
});
My exercise with Leaflet.js : resize the icon of a marker when zooming in or out by modifying the iconSize option (ie not by changing the icon source).
I tried this :
function resize(e) {
for (const marker of markers) {
const newY = marker.options.icon.options.iconSize.y * (mymap.getZoom() / parameterInitZoom);
const newX = marker.options.icon.options.iconSize.x * (mymap.getZoom() / parameterInitZoom);
marker.setIcon(marker.options.icon.options.iconsize = [newX, newY]);
}
}
mymap.on('zoomend', resize)
but I ended up with :
t.icon.createIcon is not a function
I saw also the method muliplyBy but couldn't find out the way to make it work.
How to do it ?
What I did finally, and it's working well :
let factor;
let markers = [];
//New class of icons
const MyIcon = L.Icon.extend({
options: {
iconSize: new L.Point(iconInitWidth, iconInitHeight) //Define your iconInitWidth and iconInitHeight before
},
});
/*------------ Functions - Callbacks ----------------*/
//Small function to keep the factor up to date with the current zoom
function updateFactor() {
let currentZoom = mymap.getZoom();
factor = Math.pow(currentZoom / mymap.options.zoom, 5);
};
updateFactor();
//Create a new marker
function makeMarker(e) {
const newX = Math.round(iconInitWidth * factor);
const newY = newX * iconInitHeight / iconInitWidth;
const newMarker = new L.Marker(new L.LatLng(e.latlng.lat, e.latlng.lng), {
icon: new MyIcon({ iconSize: new L.Point(newX, newY) })
}).addTo(mymap);
markers[markers.length] = newMarker;
}
//Update the marker
function resize(e) {
updateFactor();
for (const marker of markers) {
const newX = Math.round(iconInitWidth * factor);
const newY = newX * iconInitHeight / iconInitWidth;
marker.setIcon(new MyIcon({ iconSize: new L.Point(newX, newY) }));
}
}
/*------------ Event listeners ----------------*/
mymap.addEventListener('click', makeMarker);
mymap.on('zoom', resize);
I have multiple firebase items that I am looping through and they have name, categoryId, lat, lon.
I am trying to calculate a distance from 2 lat,lon (one is the one in firebase), the other is the user's location.
All of this is fine, and I can calculate it fine too. However, how do I inject/map the new variable into the firebase array that I am subscribed to?
this.categoryId = this.navParams.get('categoryId');
afoDatabase.list('/list', {query: {
orderByChild: "categoryId",
equalTo: parseInt(this.categoryId)
}}).subscribe(listItems => {
this.items = listItems; //need to add distance within array
loadingPopup.dismiss().catch(() => {});
});
For example, I have a variable called distance, that calculates everything and has the value. How do I add it in the this?items - so I can call it from the HTML side?
Thanks for the help :)
To inject, first create a function and create a variable (e.g. this.distance) and get the value of your function on this variable. After that use a for loop to increment, based on length - and use this.items[i]["distance"] to add to each one. For example:
this.categoryId = this.navParams.get('categoryId');
afoDatabase.list('/list', {query: {
orderByChild: "categoryId",
equalTo: parseInt(this.categoryId)
}}).subscribe(listItems => {
this.items = listItems;
this.geolocation.getCurrentPosition({timeout:15000}).then((resp) => {
this.myLat = resp.coords.latitude;
this.myLong = resp.coords.longitude;
}).catch((error) => {
console.log('Error getting location', error);
});
for (var i = 0, len = this.items.length; i < len; i++) {
this.distance = this.calculateDistance(this.myLat, this.myLong, this.items[i].lat, this.items[i].lng);
this.items[i]["distance"] = Math.round(this.distance);
console.log('testing myLat', this.myLat)
console.log('testing myLong', this.myLong)
}
console.log('testing inject', this.items)
loadingPopup.dismiss().catch(() => {});
});
Function
calculateDistance(lat1:number,lon1:number,lat2:number,lon2:number){
let R = 6371; // km
let dLat = (lat2 - lat1) * Math.PI / 180;
let dLon = (lon2 - lon1) * Math.PI / 180;
let a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
let c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
let d = R * c;
return d;
}