background image is not displaying in react-chartjs-2 - reactjs

I want use image background in react-chartjs-2 but its not working.
I tried in plane chartjs page and its working without any problem.
const image = new Image();
image.src = 'https://www.chartjs.org/img/chartjs-logo.svg';
const plugin = {
id: 'custom_canvas_background_image',
beforeDraw: (chart) => {
if (image.complete) {
const ctx = chart.ctx;
const {top, left, width, height} = chart.chartArea;
const x = left + width / 2 - image.width / 2;
const y = top + height / 2 - image.height / 2;
ctx.drawImage(image, x, y);
} else {
image.onload = () => chart.draw();
}
}
};
export default function Chart() {
return <Line options = {
options
}
data = {
data
}
plugins = {
plugin
}
/>;
}

plugins must be defined as an array as follows:
plugins = {[
plugin
]}
I couldn't find this information on the react-chartjs-2 page directly but taking a look at types.ts on GitHub, I found this...
/**
* The plugins array that is passed into the Chart.js chart
* #see https://www.chartjs.org/docs/latest/developers/plugins.html
* #default []
*/
plugins?: Plugin<TType>[];

Related

How to create a screenshot that will include three.js canvases and html with CSS

I have a React app that displays a toolbar and 2 canvases built-in Three.js. I would like to take a screenshot of the entire app.
I tried already niklasvh/html2canvas
const element = document.getElementsByClassName("contentContainer-5")[0] as HTMLElement;
const url = html2canvas(element)
.then((canvas)=> {
return canvas.toDataURL();
})
}
const screenshotObject = {
url: url,
width: 128,
height: 64,
}
return screenshotObject
}
and BLOB html5
takeScreenshot() {
const screenshot = document.documentElement
.cloneNode(true) as Element;
const blob = new Blob([screenshot.outerHTML], {
type: 'text/html'
});
return blob;
}
generate() {
window.URL = window.URL || window.webkitURL;
window.open(window.URL
.createObjectURL(this.takeScreenshot()));
}
In the first case, the screenshot's URL is very long but the image is empty.
In the second case, HTML and CSS is perfectly snapshotted but canvases are empty.
.cloneNode() only copies the DOM and canvasses are not a part of DOM. A workround could be done with the cloneNode function (as done here)
const takeScreenshot = () => {
const _cloneNodeFn = HTMLCanvasElement.prototype.cloneNode;
HTMLCanvasElement.prototype.cloneNode = function () {
const customCloneNodeFn = _cloneNodeFn.apply(this, arguments);
if (this.getContext("2d")) {
customCloneNodeFn.getContext("2d").drawImage(this, 0, 0);
}
return customCloneNodeFn;
};
};
const _canvas = document.querySelector("canvas");
_canvas.getContext("2d").fillRect(20, 20, 20, 20);
for (let i = 0; i < 20; i++) {
document.body.appendChild(_canvas.cloneNode());
}
Note: cloneNode has to be called on the canvas directly, and not the parent node.
In order to take a screenshot of a Three.js scene using Javascript, you need to create a new canvas context with the original canvas' properties, draw the image onto the context, convert it to a blob, then make it available on an invisible a tag which is automatically click and downloads a screenshot of your Three.js scene.
I created the following barebones code that takes screenshots of scenes:
export const screenshot = async (idx) => {
const element = document.getElementById('existing-canvas-id');
const canvas = document.createElement('new-canvas-id');
canvas.width = element.width;
canvas.height = element.height;
const context = canvas.getContext('2d');
let url = element.toDataURL();
let img = new Image();
await new Promise(r => img.onload=r, img.src=url);
context.drawImage(img, 0, 0);
const blob = canvas.toBlob();
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `${index}.png`;
link.dispatchEvent(new MouseEvent('click'));
}
};

Only title is not updated in react-chartjs-2 doughnut chart

I am using react-chartjs-2 to implement a doughnut chart in my website.
Below is the code I am using to render the chart.
const Doughnut = (props) => {
const { title, labels, colors, projects, percentages, width, height } = props;
console.log(`title:${title}`); //this logs proper title
const plugins = [
{
beforeDraw: function (chart) {
console.log(`title:${title}`); //this logs previous title and thus renders previous title
var width = chart.width,
height = chart.height,
ctx = chart.ctx;
ctx.restore();
var fontSize = (height / 240).toFixed(2);
ctx.font = fontSize + "em sans-serif";
ctx.textBaseline = "top";
var text = `${title}`,
textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = height / 2;
ctx.fillText(title, textX, textY);
ctx.save();
},
},
];
const data = {
datasets: [
{
data: percentages,
backgroundColor: colors,
},
],
};
return (
<DoughnutChart
data={data}
plugins={plugins}
width={width}
height={height}
/>
);
};
I am passing doughnut chart details from my parent component to child component and all props are passed correctly. When I log props.title outside beforeDraw function then it logs the proper value but when I log props.title inside beforeDraw function then it logs the previous value of title and thus it renders the previous value of title.
what am I doing wrong here?

What's the best way to get image color analysis data in React?

I want to get pixel-by-pixel color data for an image so I can adapt it into a "8-bit" sort of look using a grid of colored squares.
From the research I've done so far, it looks like the standard way to get that kind of data is using an HTML5 canvas and context.getImageData (example). So far though I've had no luck getting this to work in a React app.
The closest I've gotten is this. I'm sure there are a million things wrong with it, probably to do with how I'm interacting with the DOM, but it at least returns an imageData object. The problem is that the color value for every pixel is 0.
Updated to use ref instead of getElementById
function App() {
const imgRef = useRef(null);
const img = <img ref={imgRef} src={headshot} />;
// presumably we want to wait until after render?
useEffect(() => {
if (imgRef === null) {
console.log("image ref missing");
return;
}
if (imgRef.current === null) {
console.log("image ref is null");
return;
}
// couldn't use imgRef.current.offsetHeight/Width for these because typscript
// thinks imgRef.current is `never`?
const height = 514;
const width = 514;
const canvas = document.createElement('canvas');
canvas.height = height; canvas.width = width;
const context = canvas.getContext && canvas.getContext('2d');
if (context === null) {
console.log(`context or image missing`);
return
}
context.drawImage(imgRef.current, 0, 0);
const imageData = context.getImageData(0, 0, width, height);
console.log(`Image Data`, imageData);
}, []);
return img;
}
Related: ultimately I want to get this data without actually displaying the image, so any tips on that would also be appreciated.
Thanks!
You're not waiting for the image to load. (As with regular HTML, that happens asynchronously.)
So, instead of using an effect that's run as soon as the component has mounted, hook it up to your image's onLoad. Aside from that:
You don't need to check whether imgRef is null; the ref box itself never is.
When using TypeScript and DOM refs, use useRef<HTML...Element>(null); to have ref.current have the correct type.
There is no need to make imgRef.current a dependency for the handler, since refs changing do not cause components to update.
export default function App() {
const imgRef = React.useRef<HTMLImageElement>(null);
const readImageData = React.useCallback(() => {
const img = imgRef.current;
if (!img?.width) {
return;
}
const { width, height } = img;
const canvas = document.createElement("canvas");
canvas.height = height;
canvas.width = width;
const context = canvas.getContext?.("2d");
if (context === null) {
return;
}
context.drawImage(img, 0, 0);
const imageData = context.getImageData(0, 0, width, height);
console.log(`Image Data`, imageData);
}, []);
return <img ref={imgRef} src={kitten} onLoad={readImageData} />;
}
EDIT
Do you know how I can do this now without actually displaying the image?
To read an image without actually showing it in the DOM, you'll need new Image - and then you can use an effect.
/** Promisified image loading. */
function loadImage(src: string): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
const img = new Image();
img.addEventListener("load", () => {
resolve(img);
});
img.addEventListener("error", reject);
img.src = src;
});
}
function analyzeImage(img: HTMLImageElement) {
const { width, height } = img;
const canvas = document.createElement("canvas");
canvas.height = height;
canvas.width = width;
const context = canvas.getContext?.("2d");
if (context === null) {
return;
}
context.drawImage(img, 0, 0);
const imageData = context.getImageData(0, 0, width, height);
console.log(`Image Data`, imageData);
}
export default function App() {
React.useEffect(() => {
loadImage(kitten).then(analyzeImage);
}, []);
return <>hi</>;
}

How to properly load images to Canvas in React

I have an array of image urls. I want to display one image at a time on canvas. I've setup useEffect to be triggered when index of current image is changed.
I've researched online and found out that images load async and i must use onload callback to draw new image on canvas (Why won't my image show on canvas?).
I've setup effect and callback like this:
const [image, setImage] = useState<HTMLImageElement>()
useEffect(() => {
const image = new Image()
image.onload = () => update
image.src = images[imageState.imageIndex]?.url
setImage(image)
}, [imageState.imageIndex, images, update])
const update = useCallback(() => {
if (!canvasRef.current) return
const ctx = canvasRef.current.getContext('2d')
if (!ctx || !image) return
ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height)
canvasRef.current.width = image.width
canvasRef.current.height = image.height
ctx.drawImage(image, 0, 0)
}, [image])
canvas is setup like this:
export const ImageGallery = (props: ImageGalleryProps) => {
const classes = useImageGalleryStyles()
return (
<div className={classes.outerContainer}>
<canvas
style={{
left: `${imageState.position.x}px`,
top: `${imageState.position.y}px`,
transform: `rotate(${imageState.rotate}deg) scale(${imageState.scale})`,
}}
ref={canvas.ref}
className={classes.image}
/>
</div>
)
}
Images are changed like this:
Callback never called and canvas remains blank. What is proper way to update canvas?
Ended up with this:
useEffect(() => {
const image = new Image()
image.onload = function() {
if (!canvasRef.current) return
const ctx = canvasRef.current.getContext('2d')
if (!ctx || !image) return
ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height)
canvasRef.current.width = image.width
canvasRef.current.height = image.height
ctx.drawImage(image, 0, 0)
coordinates.map(c => {
switch (c.coordinates.$type) {
case DefectCoordinatesType.WideSide:
c.coordinates.WideSides.map(ws => {
if (image) {
ctx.beginPath()
ctx.imageSmoothingEnabled = false
ctx.moveTo(ws[0].x * image.width, ws[0].y * image.height)
ctx.lineTo(ws[1].x * image.width, ws[1].y * image.height)
ctx.closePath()
ctx.strokeStyle = 'red'
ctx.lineWidth = 2
ctx.stroke()
}
})
}
})
}
image.src = images[imageState.imageIndex]?.url
}, [checkBoundaries, coordinates, imageState.imageIndex, images, prevIndex, zoomOut])

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

Resources