I am trying to follow the tutorial here but using react app. The chart is not showing up and I am not sure what I am doing wrong, please help. I am using d3 v7 and react 17. I get no errors but the buttons show up so the component is called correctly.
This is my component:
import * as d3 from 'd3'
import * as React from 'react'
import { useState, useEffect} from 'react'
const PieChart = (props) => {
const ref = React.createRef()
useEffect(() => {
draw()
});
// set the dimensions and margins of the graph
const width = 450, height = 450, margin = 40;
// The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
const radius = Math.min(width, height) / 2 - margin;
// set the color scale
const color = d3.scaleOrdinal()
.domain(["a", "b", "c", "d", "e", "f"])
.range(d3.schemeDark2);
// create 2 data_set
const data1 = {a: 9, b: 20, c:30, d:8, e:12}
const data2 = {a: 6, b: 16, c:20, d:14, e:19, f:12}
const svg = d3.select(".PieChart")
// A function that create / update the plot for a given variable:
const update = (data) => {
if (data == "data1"){
data = data1;
}else{
data = data2;
}
// Compute the position of each group on the pie:
const pie = d3.pie()
.value(function(d) {return d[1]; })
.sort(function(a, b) { return d3.ascending(a.key, b.key);} ) // This make sure that group order remains the same in the pie chart
const data_ready = pie(Object.entries(data))
// map to data
const u = svg.selectAll("path")
.data(data_ready)
// Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
u
.join('path')
.transition()
.duration(1000)
.attr('d', d3.arc()
.innerRadius(0)
.outerRadius(radius)
)
.attr('fill', function(d){ return(color(d.data[0])) })
.attr("stroke", "white")
.style("stroke-width", "2px")
.style("opacity", 1)
}
const draw = () => {
svg.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", `translate(${width/2}, ${height/2})`);
// Initialize the plot with the first dataset
update("data1")
};
return <div ref={ref} >
<button onClick={update("data1")}>Data 1</button>
<button onClick={update("data2")}>Data 2</button>
<div className="PieChart" />
</div>
}
export default PieChart
This is what I get, no graph
I think there are quite a few things that need to be fixed.
First & foremost is the usage of ref. You have to do const ref = React.useRef() i.e use useRef instead of createRef.
Also, the ref has to be set to the svg element and not the div.
The 'almost' working code is here. https://codesandbox.io/s/flamboyant-heisenberg-vppny?file=/src/pie.js
I say almost because the chart does not render on page load! I do not know why (I am not very familiar with react). To render the chart, change the value of width or height from 450 to 451. I do not know why this works but it does.
One the page is loaded with the chart, the buttons & the chart transition work fine.
If you figure how to render the chart on page load, please let me know.
Related
New to D3 and React.
I'm trying to show data on a grid (x/y axis) for each record against a background image (logo), showing a small images.
I've got the D3 graph working if I use static data (gcs in my example code below). But, if I try to filter/map the data from CohortContext, the elements are not being shown on the graph.
I think it's a timing issue that the D3 is rendering before the .map returns with the data, but I may be wrong. I tried wrapping the useEffect in an async, but I'm grasping at straws now and it didn't resolve the issue anyway, based on all the code being within the async.
The code:
import React, { useEffect, useContext } from "react"
import * as d3 from "d3"
import logo from '../../assets/images//charts/360Chart.svg'
import CohortContext from "../../utility/context/cohort/cohort-context"
import Saboteur from '../../assets/images/charts/chartBubbleKey.svg'
function ChartData({ goal }) {
const gcs = [
{
chartAsset: Saboteur,
xCoordinates: 200,
yCoordinates: 300
},
{
chartAsset: Saboteur,
xCoordinates: 150,
yCoordinates: 150
}
]
console.log(gcs)
const width = 800
const height = 800
const { cohorts } = useContext(CohortContext)
useEffect(() => {
//Retrieve the Goal data from the useContext
const fetchData = async () => {
const goalCohorts = cohorts
.filter(records => records.goalId === goal.goalId)
.map((records) => {
return {
chartAsset: Saboteur,
xCoordinates: records.xcoordinates,
yCoordinates: records.ycoordinates
}
})
console.log('goal cohorts for Chart :', goalCohorts)
const svg = d3
.select("#svgcontainer")
.append("svg")
.attr("width", width)
.attr("height", height)
//background Chart
svg
.append("svg:image")
.attr("xlink:href", () => logo)
.attr("width", 580)
.attr("height", 580)
.attr("x", 110)
.attr("y", 20)
// Add in the data for the chart data
const g = svg
.selectAll("g")
.append("g")
.data(goalCohorts) // works if I use .data(gcs)
.enter()
g.append("svg:image")
.attr("xlink:href", (data) => data.chartAsset)
.attr("width", 50)
.attr("height", 50)
.attr("x", (data) => data.xCoordinates)
.attr("y", (data) => data.yCoordinates)
}
fetchData()
}, [])
return (
< div className="App" >
<div id="svgcontainer"></div>
</div >
)
}
export default ChartData
If I look at the console.logs, the data is the same - so assuming a timing issue.
Can anyone see anything obvious on why data(goalCohorts) is not showing the data?
I'm using React v18.1, D3 v7.8
useEffect(() => {
//Retrieve the Goal data from the useContext
const fetchData = async () => {
const goalCohorts = cohorts
.....
, [cohorts]);
Please replace [cohorts] instead of [].
I've got a Uint8ClampedArray that comes from my backend representing the pixels array of an image and I'm trying to display it in React + Typescript by using a canvas element.
That's the code:
const imgData = new ImageData(
dataArray, //Uint8ClampedArray
width,
height
);
return <Canvas
imgData={imgData}
canvasHeight={ref.current.offsetHeight} //max height available in the screen
canvasWidth={ref.current.offsetHeight * ratio}
imgHeight={height}
imgWidth={width}
/>
---
export function Canvas(props: Props) {
const canvasRef = useRef(null);
useEffect(() => {
const canvasRefCurrent: HTMLCanvasElement = canvasRef.current!;
const ctx = canvasRefCurrent.getContext("2d", { alpha: false })!;
ctx.putImageData(props.data, 0, 0);
}, [props.data]);
return ( <canvas
ref={canvasRef}
width={props.imgWidth}
height={props.imgHeight}
/> );
However, now I'd like to apply zoom/pan to the canvas and to retrieve the coordinates of a clicked point in the image. Does anybody know about any library that can do these actions?
I'm new to both reactjs and d3js and was trying to create a pie chart using d3 in a reactjs website. I wanted to create a pie chart that shows the labels in the pie chart as tooltips and also an arrow pointing to the slices with their corresponding labels exactly like this example.
I have created a codesandbox to further demonstrate where I'm stuck right now. As you can see the values are being shown in the slices but I want the labels as well just like in the example above. I would be grateful if someone can please help me with this one.
This is the code that I have created so far.
import React, { useEffect, useRef } from "react";
import * as d3 from "d3";
const Pie = props => {
const ref = useRef(null);
const createPie = d3
.pie()
.value(d => d.value)
.sort(null);
const createArc = d3
.arc()
.innerRadius(props.innerRadius)
.outerRadius(props.outerRadius);
const colors = d3.scaleOrdinal(d3.schemeCategory10);
const format = d3.format(".2f");
useEffect(() => {
const data = createPie(props.data);
const group = d3.select(ref.current);
const groupWithData = group.selectAll("g.arc").data(data);
groupWithData.exit().remove();
const groupWithUpdate = groupWithData
.enter()
.append("g")
.attr("class", "arc");
const path = groupWithUpdate
.append("path")
.merge(groupWithData.select("path.arc"));
path
.attr("class", "arc")
.attr("d", createArc)
.attr("fill", (d, i) => colors(i));
const text = groupWithUpdate
.append("text")
.merge(groupWithData.select("text"));
text
.attr("text-anchor", "middle")
.attr("alignment-baseline", "middle")
.attr("transform", d => `translate(${createArc.centroid(d)})`)
.style("fill", "white")
.style("font-size", 10)
.text(d => format(d.value));
}, [props.data]);
return (
<svg width={props.width} height={props.height}>
<g
ref={ref}
transform={`translate(${props.outerRadius} ${props.outerRadius})`}
/>
</svg>
);
};
export default Pie;
Using react I draw a D3 scatter chart. When I try to add new lines on the chart through the svg i initialize at the start, it works i.e. when using the button rendered in the svg and the onlick function.
When I try to do this using the parent's props, the function to add the lines gets triggered but the lines are not added. I have read other posts about using enter() but this does not work. What I think is wrong is the way I setup the div structure to mount the original SVG, since I have a div with class chart-container and inside it a div with id chart, which I add the SVG inside. Does anyone thing the way I am selecting the svg when adding the lines is wrong? I do not understand why the lines are not rendered on the D3 chart.
import React, { useEffect, useLayoutEffect } from 'react';
import * as d3 from 'd3';
import './Chart.css';
const Chart = (props) => {
useEffect(() => {
drawChart(props.data)
}, [props.columnType]);
useLayoutEffect(() => {
if (showLines) {
addLines(props.data)
return
} else {
removeLines()
}
}, [props.showLines]);
const addLines = (data) => {
const x = d3.scaleLinear().range([0, width]);
const y = d3.scaleLinear().range([height, 0]);
const parsedData = parseData(data, columnType);
parsedData.forEach((dataSet, index) => {
const { points } = dataSet;
const line = d3.line()
.x((d) => x(d.x))
.y((d) => y(d.yhat));
// If I use this selection, I cannot add the lines
d3.select("svg").enter().append("path")
.datum(points)
.attr("class", `line`)
.attr("d", line);
});
}
const drawChart = (data) => {
..... // Other code not added (related to configuring the graph)
// If I use this SVG I can add the lines
const svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("id", "chart-svg")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
..... // other code not added (related to rendering the points using the data)
}
return (
<div className="chart-container">
<div id="chart"></div>
</div>
);
}
Figured it out. I need to reset the x and y ranges again, as it was rendering the lines off screen.
I found following code for zoom in a simple D3 solution which works well with Javascript:
var zoom = d3.behavior.zoom()
.on("zoom", function() {
projection.translate(d3.event.translate).scale(d3.event.scale);
feature.attr("d", path);
circle.attr("transform", ctr);
})
;
zoom.translate(projection.translate())
.scale(projection.scale())
.scaleExtent([h / 6, h])
;
When I try to convert this code in D3 + ReactJS, I get issues. There have been many since I've tried so many different solutions. The latest one zooms my world map once but it's all a mess.
This is my code:
import React, { Component } from 'react';
import 'd3';
import * as d3Z from 'd3-zoom'
import * as d3 from 'd3-selection';
//import d3Scale from 'd3-scale'
import { geoMercator, geoPath } from 'd3-geo';
import './WorldMap.css';
import countries from '../resources/world-countries.json';
//
export default class WorldMap extends Component {
componentDidUpdate() {
const width = 1300, //document.body.clientWidth,
height = 600;//document.body.clientHeight;
const circleRadius = 2;
const lstRadius = [7, circleRadius];
const lstColor = ["green", "white"];
const duration = 500;
const lstShots = [];
const projection = geoMercator();
const path = geoPath()
.projection(projection);
const d3Zoom = d3Z.zoom();
for (var i = 0; i < this.props.data.length; i++) {
lstShots.push(JSON.parse(JSON.stringify(this.props.data[i])));
}
const getCirclePos = (d) => {
return "translate(" + projection([d.long, d.lat]) + ")";
}
const svgObj = d3.select(this.refs.svgMap);
const feature = svgObj
.selectAll("path.feature")
.data(countries.features)
.enter().append("path")
.attr("class", "feature");
projection
.scale(width / 6.5)
.translate([width / 2, height / 1.6]);
const zoom = d3Zoom
.on("zoom", function() {
console.log(d3.event);
projection.translate([d3.event.transform.x, d3.event.transform.y]).scale(d3.event.scale);
feature.attr("d", path);
// circle.attr("transform", getCirclePos);
})
;
svgObj.call(zoom);
// console.log(zoom);
// zoom.translateTo(projection.translate())
// .scale(projection.scale())
// .scaleExtent([height / 6, height])
// ;
feature.attr("d", path);
// eslint-disable-next-line
const circle = svgObj.selectAll("circle")
.data(lstShots)
.enter()
.append("circle")
.attr("r", circleRadius)
.attr("fill", 'white')
.attr("transform", getCirclePos)
.attr("node", function(d) { return JSON.stringify(d); })
.style("cursor", "pointer");
}
render() {
return (
<svg ref="svgMap"></svg>
);
}
}
The problem is that d3.event does not contain fields of translate or scale which I have seen people use in other example of d3-zoom. While I have replaced translate with transform in zoom function, I don't know what to replace d3.event.scale with.
If you port code from d3v3 to d3v4 you need to do it all, d3.event.scale does not exist in d3v4
projection.translate([d3.event.transform.x, d3.event.transform.y]).scale(d3.event.transform.k);
You need to initialise the zoom too
svgObj.call(zoom);
svgObj.call(zoom.transform, d3.zoomIdentity.translate(projection.translate()[0], projection.translate()[1]).scale(projection.scale()));
Don't use ref strings but Callback Refs
I needed to use componentDidMount() to see anything.