How do I properly display a chart in html canvas with Chart.js and Angular Framework - html

I am following a tutorial to build a real time polling using angular. However, by the end of the tutorial, I am not able to display the chart like the tutorial does. What am I doing wrong?
I followed the tutorial, used angular, pusher, chart.js as instructed. It worked fine until I reached Data Visualization part, where I need to display the polling result on a 'Doughnut' chart. All I have is a white line which is used to divide different color of the dataset, but the rest of the chart is not displaying.
My app.component.ts
voteCount = {
salah: 0,
kane: 0,
eriksen: 0,
kevin: 0,
};
castVote(player) {
this.http
.post(`http://localhost:4000/vote`, { player })
.subscribe((res: any) => {
this.vote = res.player;
this.voted = true;
});
}
getVoteClasses(player) {
return {
elect: this.voted && this.vote === player,
lost: this.voted && this.vote !== player,
};
}
chartLabels: string[] = Object.keys(this.voteCount);
chartData: number[] = Object.values(this.voteCount);
chartType = 'doughnut';
ngOnInit() {
const channel = this.pusher.init();
channel.bind('vote', ({player}) => {
this.voteCount[player] += 1;
this.chartData = Object.values(this.voteCount);
});
}
My app.component.html
<div class="chart-box" *ngIf="voted">
<h2>How others voted</h2>
<canvas
baseChart
[data]="chartData"
[labels]="chartLabels"
[chartType]="chartType"
>
</canvas>
</div>
According to the tutorial, the data visualization should look like this:
However, mine looks like this:
I changed the background color of the div, so you can see there is a white line below Eriksen just like the tutorial, however, the rest of the data is not shown.
Edit: I hard-coded some data and assigned them to chartLabels, chartData and chartType, and now the canvas can visualize them:
chartLabels = ['salah', 'kane', 'eriksen', 'kevin'];
chartData = [120, 150, 180, 90];
chartType = 'doughnut';
So this means
chartLabels: string[] = Object.keys(this.voteCount);
chartData: number[] = Object.values(this.voteCount);
chartType = 'doughnut';
are not receiving any data. Why is that?
Edit 2:
After further experiment, I found that
ngOnInit() {
const channel = this.pusher.init();
channel.bind('vote', ({player}) => {
this.voteCount[player] += 1;
this.chartData = Object.values(this.voteCount);
});
}
Object.values(this.voteCount) is not increased so its value stays at 0, so that's why the chart is empty. How do I increase its value each time I click on the player's pic?

Related

Is it possible to create a variable in HTML dependent upon a local-storage variable?

I am using the below script to implement light and dark theme support on my website. For contrast reasons I would like to display a light version of the image (img1.png) when the dark theme is enabled and a dark version of the image (img2.png) when the light theme is enabled. I am using the img tag in the HTML file to insert the image, but I am usure how I could go about doing that. Given I must specify a path to one image image in the html file.
Is there any way I can create a variable and somehow make it dependent on the local storage variable I create? How should I go about it?
let darkTheme = localStorage.getItem('darkTheme');
const themeToggle = document.querySelector('#themeButton');
const bodyBackground = document.getElementById('#body');
const enableDark = () => {
document.body.classList.add('darktheme');
localStorage.setItem('darkTheme', 'enabled');
themeToggle.innerHTML = `<i id="themeButton__icon" icon-name="sun"></i>`;
lucide.createIcons();
};
const disableDark = () => {
document.body.classList.remove('darktheme');
localStorage.setItem('darkTheme', null);
themeToggle.innerHTML = `<i id="themeButton__icon" icon-name="moon"></i>`;
lucide.createIcons();
};
if (darkTheme === 'enabled') {
document.body.classList.add('notransition');
enableDark();
document.body.classList.remove('notransition');
} else {
disableDark();
}
themeToggle.addEventListener('click', () => {
darkTheme = localStorage.getItem('darkTheme');
if (darkTheme !== 'enabled') {
enableDark();
} else {
disableDark();
}
});

Implementing Three.js SSAOPass in AFrame

I was able to successfully integrate Threejs Effect composer in aframe as a component by exporting everything as THREE.Effectcomposer, THREE.SSAOPass etc. and adding the effect inside a aframe component and i tweaked the AFrame renderer to update the effects in the scene. OutlinePass from threejs worked fine in this code but SSAO is not working and i don't get any errors. Please someone help me figure out the problem. the code for SSAOPass looks like this
AFRAME.registerComponent('ssao', {
init: function () {
this.el.addEventListener('that', evt => this.onEnter());
this.el.addEventListener('mouseleave', evt => this.onLeave());
setTimeout(() => this.el.emit("that"), 2000);
},
onEnter: function () {
const scene = this.el.sceneEl.object3D;
const camera = this.el.sceneEl.camera;
const renderer = this.el.sceneEl.renderer;
const render = renderer.render;
const composer = new THREE.EffectComposer(renderer);
//let renderPass = new THREE.RenderPass(scene, camera);
//let outlinePass = new THREE.OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), scene, camera);
const ssaoPass = new THREE.SSAOPass( scene, camera, window.innerWidth, window.innerHeight );
//composer.addPass(renderPass);
//composer.addPass(outlinePass);
ssaoPass.kernelRadius = 16;
composer.addPass( ssaoPass );
// let objects = [];
// this.el.object3D.traverse(node => {
// if (!node.isMesh) return;
// objects.push(node);
// });
// outlinePass.selectedObjects = objects;
// outlinePass.renderToScreen = true;
// outlinePass.edgeStrength = this.data.strength;
// outlinePass.edgeGlow = this.data.glow;
// outlinePass.visibleEdgeColor.set(this.data.color);
// HACK the AFRAME render method (a bit ugly)
const clock = new THREE.Clock();
this.originalRenderMethod = render;
let calledByComposer = false;
renderer.render = function () {
if (calledByComposer) {
render.apply(renderer, arguments);
} else {
calledByComposer = true;
composer.render(clock.getDelta());
calledByComposer = false;
}
};
},
onLeave: function () {
this.el.sceneEl.renderer.render = this.originalRenderMethod;
},
remove: function () {
this.onLeave();
}
});
I have also created a glitch project which i am sharing here. Please feel free to join and collaborate in my project
Edit link: https://glitch.com/edit/#!/accessible-torpid-partridge
Site link:https://accessible-torpid-partridge.glitch.me
Thanks in advance
The code is correct, all you need is to tweak the exposed SSAOShader uniforms: SSAOPass.kernelRadius, SSAOPass.minDistance, SSAOPass.maxDistance - like in the Three.js example.
Keep in mind - the scale in the example is huge, so the values will need to be different in a default aframe scene.
It's a good idea to be able to dynamically update a component (via setAttribute() if you properly handle updates), so you can see what's going on in realtime. Something like I did here - SSAO in a-frame (also based on Don McCurdys gist.
I've used some basic HTML elements, most threejs examples use dat.GUI - it is made for demo / debug tweaks.

Save & load a texture with alpha component in three.js

The following code works perfectly for images that do not contain an alpha channel:
toJSON() {
let output = super.toJSON();
output["geometry"] = this.geometry;
output['imageURL'] = this.mesh.toJSON().images[0]["url"];
return output;
}
fromJSON(data) {
super.fromJSON(data);
this.geometry = data["geometry"];
this.image_path = data["imageURL"];
this.refreshImage();
}
refreshImage() {
const this_obj = this;
const image_texture = new THREE.TextureLoader().load(
//image to load
this.image_path,
//onLoad callback to create the material only once the texture is loaded and its dimensions are available,
//this will ensure aspect ratio is based on the actual texture loaded.
(texture) => {
this_obj.changeGeometry(texture.image.width / texture.image.height)
},
//not required
undefined,
//onError callback
(err) => {
alert("An error occurred while attempting to load image");
}
);
this.mesh.material.map.dispose();
this.mesh.material.dispose();
this.mesh.material = new THREE.MeshPhongMaterial({map: image_texture, side: THREE.DoubleSide,
transparent: true})
this.mesh.material.color.set(this.color);
this.mesh.material.needsUpdate = true;
}
Unfortunately, it does not work for images with alpha channel, because transparent areas are rendered with black opaque color.
Does anyone know why this happens and how best to achieve the desired result?
EDIT:
I got an answer to my question when I realized that the issue is coming from the Mesh.toJSON call. The method is a recursive one that is a real rabbit-hole. But at the bottom of the rabbit-hole you find that texture images are converted to base64 by drawing the image onto an temporary internal canvas. This happens in the ImageUtils.js module inside the getDataURL() function
The issue is that texture images larger than 2048 in width or height are converted into compressed "jpeg" format rather than "png" format that retains the alpha component.
This explains everything.
You can load any image, apply it to a material using TextureLoader, but as soon as you call toJSON to serialize your mesh, the alpha component is lost if the underlying image is larger than 2048 wide or long.
The solution in my case is to write my own function that draws to a canvas and converts the image to base64, but supports larger image sizes. Offcourse one would have to warn the user that it may take some time to perform the conversion.
Here is the texture to url converter that I came up with...stealing heavily from ImageUtils.js and removing error handling code.
function ImageURLfromTexture( image_texture, retain_alpha = true ) {
const image = image_texture.image;
if (image !== undefined) {
if (/^data:/i.test(image.src)) {
return image.src;
}
let _canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
_canvas.width = image.width;
_canvas.height = image.height;
const context = _canvas.getContext('2d');
if (image instanceof ImageData) {
context.putImageData(image, 0, 0);
} else {
context.drawImage(image, 0, 0, image.width, image.height);
}
if ((_canvas.width > 2048 || _canvas.height > 2048) && (!retain_alpha)) {
return _canvas.toDataURL('image/jpeg', 0.6);
} else {
return _canvas.toDataURL('image/png');
}
} else {
return null;
}
}

How to filter stacked bar chart?

I found following example for a stacked bar chart with dc.js:
https://dc-js.github.io/dc.js/examples/stacked-bar.html
If I click on some (or several) legend item, I would like the chart to
only show the corresponding items (e.g. red and blue) and
adapt the total numbers to only consider the selected items
I already managed to add some click event to the legend entries:
chart.on('pretransition.hideshow', ()=> {
chart.selectAll('.dc-legend-item')
.on('click', function (data, index, nodeList) {
const stackName = data.name;
if(data.hidden){
chart.showStack(stackName);
} else {
chart.hideStack(stackName);
}
dc.redrawAll();
});
});
This hides some stack but the sum is not shown as expected (multiplie, overlapping values are shown).
=>How can I filter the data correctly?
I also tried to use chart.filter() but that only seems to be able filter the x axis and not the stacks.
Currently, if I hover over a legend entry, the chart already adapts but does not show the wanted behavior.
Thanks to Gordon I found following solution:
Step 1: Create an extra dimension for the stack property:
const stackDimension = crossFilter.dimension(d => d.stackProperty);
Step 2: Create an event handler and filter on that dimension:
const selectedStackNames = [];
const legendItemClickHandler = (data, index, nodeList) => {
const stackName = data.name;
if(selectedStackNames.includes(stackName)){
const index = selectedStackNames.indexOf(stackName);
selectedStackNames.splice(index,1);
} else {
selectedStackNames.push(stackName);
}
if(selectedStackNames.length){
stackDimension.filter((name)=>{
return selectedStackNames.includes(name);
});
} else {
stackDimension.filter(null);
}
dc.redrawAll();
};
chart.on('pretransition.hideshow', ()=> {
chart.selectAll('.dc-legend-item')
.on('click', legendItemClickHandler);
});
Step 3: Highlight selected legend items
chart.on('pretransition.show', ()=> {
chart.selectAll('.dc-legend-item')
.on('click', legendItemClickHandler);
const selectedStackNames = new Set(
stackDimension.top(Infinity)
.map(d=>d.stackProperty)
);
chart.selectAll('.dc-legend-item')
.each((data, index, nodeList)=>{
const node = nodeList[index];
const colorRect = node.children[0];
if(selectedStackNames.has(data.name)){
colorRect.style.outline = "1px solid grey";
colorRect.opacity="";
data.hidden=false;
} else {
colorRect.style.outline = "";
data.hidden=true;
colorRect.opacity="0.3";
}
});
});

How to change the color of sphere objects dynamically (used SceneBuilder in Autodesk forge)

I am working on the example from Custom models in Forge Viewer blog by Petr Broz. I am facing issue in updating the color of sphere objects dynamically. I am getting the value of sphere's color from a json file like this "color": "#FF0000". I have created 3 spheres and I am getting the color of first sphere for the rest also. Why the color is not updating for the other spheres? If the problem is on using same material then I tried giving the sphereMaterial in array also as shown below. Is that wrong or how can i update the color?
var spherecolor='';
var sphereMaterial = [];
const button = document.getElementById('button-geometry');
button.addEventListener('click', async function () {
const sceneBuilder = await viewer.loadExtension('Autodesk.Viewing.SceneBuilder');
const modelBuilder = await sceneBuilder.addNewModel({ conserveMemory: true, modelNameOverride: 'My Custom Model' });
for (var i = 0; i < numOfSphere;i++) {
addGeometry(modelBuilder, jsonGeomConfig.geom[i].dbId, i);
}
});
function addGeometry(modelBuilder, dbId, i) {
const sphereGeometry = new THREE.BufferGeometry().fromGeometry(new THREE.SphereGeometry(0.05, 8, 10));
//Getting spherecolor from json file
spherecolor = jsonGeomConfig.geom[i].color;
sphereMaterial[i] = new THREE.MeshPhongMaterial({ color: spherecolor });
const sphereTransform = new THREE.Matrix4().compose(
new THREE.Vector3(jsonGeomConfig.geom[i].Position.posX, jsonGeomConfig.geom[i].Position.posY, jsonGeomConfig.geom[i].Position.posZ),
new THREE.Quaternion(0, 0, 0, 1),
new THREE.Vector3(2,2,2)
);
modelBuilder.addMaterial('MyCustomMaterial', sphereMaterial[i]);
const sphereGeomId = modelBuilder.addGeometry(sphereGeometry);
const sphereFragId = modelBuilder.addFragment(sphereGeomId, 'MyCustomMaterial', sphereTransform);
modelBuilder.changeFragmentsDbId(sphereFragId, dbId);
}
Be sure to give the materials with different colors different names ... otherwise it'd get overridden - see this live environment:
modelBuilder.addMaterial('MyCustomMaterial'+i, sphereMaterial[i]);
const sphereGeomId = modelBuilder.addGeometry(sphereGeometry);
const sphereFragId = modelBuilder.addFragment(sphereGeomId, 'MyCustomMaterial'+i, sphereTransform);