d3 / React / Hooks - Updates do not clean up old rects and Groups - reactjs

I am working on a realtime updating bar chart implementation using d3 with React and Hooks.
Though the new 'g' groups and rects do get added to the svg, the old groups do not seem to be getting cleared up. So, the rects just get added on top of the old rects, as do the axis groups.
I am using the .join() API so I shouldn't need to do manually clean up with exit.remove() right? I am completely new to d3 so forgive the uncertainty.
app:
import React, { useRef, useEffect, useState } from 'react';
import * as firebase from 'firebase';
import Chart from './Chart';
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
function App() {
const [menu, setMenu] = useState([]);
const db = firebase.firestore();
useEffect(() => {
db.collection('dishes')
.get()
.then((res) => {
let data = [];
for (let doc of res.docs) {
data.push(doc.data());
}
setMenu(data);
});
}, []);
useInterval(() => {
let newMenu = [];
newMenu = [...menu];
if (newMenu[0] && newMenu[0].hasOwnProperty('orders')) {
newMenu[0].orders += 50;
setMenu(newMenu);
}
}, 3000);
return (
<div className="App">{menu.length > 0 ? <Chart data={menu} /> : null}</div>
);
}
export default App;
Chart component:
import React, { useRef, useEffect } from 'react';
import * as d3 from 'd3';
const Chart = ({ data }) => {
const height = 600;
// Generate a ref instance
const svgRef = useRef();
useEffect(() => {
const svg = d3.select(svgRef.current);
const margin = { top: 20, right: 20, bottom: 100, left: 100 };
const graphWidth = 600 - margin.left - margin.right;
const graphHeight = height - margin.top - margin.bottom;
// Find the maximum order value
const max = d3.max(data, (d) => d.orders);
// Establish the y scale
// i.e., map my max value to the pixel max value ratio
const y = d3
.scaleLinear()
.domain([0, max * 1.25])
.range([graphHeight, 0]);
// Calculates width and coordinates for each bar
// Can add padding here
const x = d3
.scaleBand()
.domain(data.map((item) => item.name))
.range([0, graphHeight])
.padding(0.25);
const graph = svg
.append('g')
.attr('width', graphWidth)
.attr('height', graphWidth)
.attr('transform', `translate(${margin.left}, ${margin.top})`);
// Creat axis groups for legends and labels
const xAxisGroup = graph
.append('g')
.attr('transform', `translate(0,${graphHeight})`);
const yAxisGroup = graph.append('g');
// Append the graph to the DOM
graph
.selectAll('rect')
.data(data, (entry, i) => entry)
.join(
(enter) => enter.append('rect'),
(update) => update.append('class', 'new'),
(exit) => exit.remove()
)
.transition()
.duration(300)
.attr('width', x.bandwidth)
.attr('height', (d) => graphHeight - y(d.orders))
.attr('fill', 'orange')
.attr('x', (d) => x(d.name))
.attr('y', (d) => y(d.orders));
// Create the axes
const xAxis = d3.axisBottom(x);
const yAxis = d3
.axisLeft(y)
// .ticks(3)
.tickFormat((d) => d + ' orders');
// Append the axes to the graph
xAxisGroup.call(xAxis);
yAxisGroup.call(yAxis);
xAxisGroup
.selectAll('text')
.attr('transform', 'rotate(-40)')
.attr('text-anchor', 'end');
}, [data]);
return (
<div>
<svg ref={svgRef} height={height} width="600" />
</div>
);
};
export default Chart;

Related

React/D3 - error when creating d3 linechart from local csv using useState - <path> attribute d: Expected number, "MNaN,205.40540540…"

I'm trying to create a linechart in React using D3.js . The linechart should have Y on the y-axis, which is a date column and on the x-axis X1. This is a copy of my data in the csv:
and also posted the sample on kaggle (not sure if this is accessible)
link to kaggle
api: kaggle datasets download -d squishy1/dataui
I got it working with a csv via a url , but now that I try to do with a local csv, I can't read in my data the right way. I'm new to Javascript and React, coming from a Python background, it is not really intuitive yet.
In my log I do see that my data gets loaded, but when the visualization want to use it I get the following error:
Error: <path> attribute d: Expected number, "MNaN,205.40540540…".
The code that I have so far is:
import * as d3 from 'd3';
import {useEffect, useState} from 'react';
import csvFile from '../data/Data_UI.csv';
const SimpleLinechart = (props) => {
const [data, setData] = useState([]);
const {width, height } = props;
useEffect(()=>{
if (data.length > 0) {
drawChart();
console.log(data)
} else {
getCSVData();
}
},[data]);
const getCSVData = async () => {
const tempData = [];
await d3.csv(
csvFile,
function (d) {
tempData.push({
date: d3.timeParse("%m/%d/%Y")(d.Y),
value: Number(d.X1),
});
}
);
setData(tempData);
console.log(data.date)
};
const drawChart = () => {
const test = data.map(function(d) { return d.value });
console.log(test)
// create the chart area
const svg = d3.select('.svg-canvas')
svg.selectAll("*").remove();
// Add X axis --> it is a date format
var x = d3.scaleTime()
.domain(d3.csv(data, function(d) { return d.date; }))
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return + d.value; })])
.range([ height, 0 ]);
svg.append("g")
.call(d3.axisLeft(y));
// set line coordinates
const line = d3.line()
.x(function(d) { return x(d.date) })
.y(function(d) { return y(d.value) })
// Add the line
svg.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", line)
}
return (
<div>
<svg className="svg-canvas" width="1000px" height="600px" />
</div>
)
}
export default SimpleLinechart;

Rendering Threejs in React dissapears my root element in HTML

I'm trying to use Three.js in Typescript react, I render Dodecahedron figure and random stars, I want to add some mark up to my three.js with React but when I render Three.js canvas into HTML it dissapears my root div, and I'm not able to add some other components
THREE.JS
import * as THREE from "three";
export function ThreeCanvas() {
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, window.innerHeight);
document.body.innerHTML = "";
document.body.appendChild(renderer.domElement);
const geometry = new THREE.DodecahedronBufferGeometry(1.7, 0);
const material = new THREE.MeshStandardMaterial({
color: "#00FF95",
});
const Stars = () => {
const starGeometry = new THREE.SphereGeometry(0.1, 24, 24)
const starMaterial = new THREE.MeshStandardMaterial({color: 0xffffff})
const star = new THREE.Mesh(starGeometry, starMaterial)
const [x, y, z] = Array(3).fill(1).map(() => THREE.MathUtils.randFloatSpread(70))
star.position.set(x, y, z)
scene.add(star)
}
Array(200).fill(100).forEach(Stars)
const light = new THREE.PointLight(0xffffff)
light.position.set(20, 20, 20)
scene.add(light)
camera.position.z = 5;
camera.position.x = 3.5
const Figure = new THREE.Mesh(geometry, material);
scene.add(Figure);
const animate = () => {
requestAnimationFrame(animate);
Figure.rotation.x += 0.01;
Figure.rotation.y += 0.01;
renderer.render(scene, camera);
};
animate();
window.addEventListener("resize", () => {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
});
return null
}
this is my three.js code what I do in react
React
import ReactDOM from "react-dom"
import { ThreeCanvas } from "./Components/Three";
import Landing from "./Components/Landing";
import "./Style/Style.css"
const FirstSection = () => {
return (
<div className="container">
<Landing />
<ThreeCanvas />;
</div>
);
}
ReactDOM.render(<FirstSection />, document.getElementById("root"));
Landing is my markup component when I open console I dont see anywhere my Landing element but in react tools I see, how to fix that issue I have no idea
You're removing document.body in Three.JS component which is why the body only contains the canvas. You might want to use a reference to the element instead of targeting document.body so that it's not disturbing the DOM structure which is why your markdown does not show. As a rule of thumb, you should never be interacting with the DOM via the document.
document.body.innerHTML = "";
I've quickly refactored the Three.JS component to use a React element reference so that you can add additional markup.
Refactored ThreeCanvas Component
import * as React from "react";
import * as THREE from "three";
export function ThreeCanvas() {
const ref = React.useRef();
const [loaded, setLoaded] = React.useState(false);
React.useEffect(() => {
if (!loaded && ref) {
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, window.innerHeight);
const geometry = new THREE.DodecahedronBufferGeometry(1.7, 0);
const material = new THREE.MeshStandardMaterial({
color: "#00FF95"
});
const Stars = () => {
const starGeometry = new THREE.SphereGeometry(0.1, 24, 24);
const starMaterial = new THREE.MeshStandardMaterial({
color: 0xffffff
});
const star = new THREE.Mesh(starGeometry, starMaterial);
const [x, y, z] = Array(3)
.fill(1)
.map(() => THREE.MathUtils.randFloatSpread(70));
star.position.set(x, y, z);
scene.add(star);
};
Array(200).fill(100).forEach(Stars);
const light = new THREE.PointLight(0xffffff);
light.position.set(20, 20, 20);
scene.add(light);
camera.position.z = 5;
camera.position.x = 3.5;
const Figure = new THREE.Mesh(geometry, material);
scene.add(Figure);
const animate = () => {
requestAnimationFrame(animate);
Figure.rotation.x += 0.01;
Figure.rotation.y += 0.01;
renderer.render(scene, camera);
};
const resize = () => {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
};
animate();
ref.current.appendChild(renderer.domElement);
window.addEventListener("resize", resize);
setLoaded(true);
return () => window.removeEventListener("resize", resize);
}
}, [ref, loaded]);
return <div ref={ref} />;
}
export default ThreeCanvas;
Typescript Version: ThreeCanvas.tsx
import * as React from "react";
import * as THREE from "three";
export function ThreeCanvas() {
const ref = React.useRef<HTMLDivElement>(null);
const [loaded, setLoaded] = React.useState(false);
React.useEffect(() => {
if (!loaded && ref.current) {
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, window.innerHeight);
const geometry = new THREE.DodecahedronBufferGeometry(1.7, 0);
const material = new THREE.MeshStandardMaterial({
color: "#00FF95"
});
const Stars = () => {
const starGeometry = new THREE.SphereGeometry(0.1, 24, 24);
const starMaterial = new THREE.MeshStandardMaterial({
color: 0xffffff
});
const star = new THREE.Mesh(starGeometry, starMaterial);
const [x, y, z] = Array(3)
.fill(1)
.map(() => THREE.MathUtils.randFloatSpread(70));
star.position.set(x, y, z);
scene.add(star);
};
Array(200).fill(100).forEach(Stars);
const light = new THREE.PointLight(0xffffff);
light.position.set(20, 20, 20);
scene.add(light);
camera.position.z = 5;
camera.position.x = 3.5;
const Figure = new THREE.Mesh(geometry, material);
scene.add(Figure);
const animate = () => {
requestAnimationFrame(animate);
Figure.rotation.x += 0.01;
Figure.rotation.y += 0.01;
renderer.render(scene, camera);
};
const resize = () => {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
};
animate();
ref.current.appendChild(renderer.domElement);
window.addEventListener("resize", resize);
setLoaded(true);
return () => window.removeEventListener("resize", resize);
}
}, [ref, loaded]);
return <div ref={ref} />;
}
export default ThreeCanvas;

Can i call a specific function inside `useEffect` in React?

I have a canvas component in react. I am using useEffect to get the canvas element. So i have defined all needed functions in useEffect, as you can see below
import { useEffect, useRef } from "react"
import * as blobs2Animate from "blobs/v2/animate"
export const CornerExpand = () => {
const canvasRef = useRef<HTMLCanvasElement>(null)
useEffect(() => {
const canvas = canvasRef.current!
const ctx = canvas.getContext("2d")
const animation = blobs2Animate.canvasPath()
const width = canvas.clientWidth * window.devicePixelRatio
const height = canvas.clientHeight * window.devicePixelRatio
canvas.width = width
canvas.height = height
const renderAnimation = () => {
if (!ctx) return
ctx.clearRect(0, 0, width, height)
ctx.fillStyle = "#E0F2FE"
ctx.fill(animation.renderFrame())
requestAnimationFrame(renderAnimation)
}
requestAnimationFrame(renderAnimation)
const size = Math.min(width, height) * 1
const defaultOptions = () => ({
blobOptions: {
seed: Math.random(),
extraPoints: 36,
randomness: 0.7,
size,
},
canvasOptions: {
offsetX: -size / 2.2,
offsetY: -size / 2.2,
},
})
const loopAnimation = () => {
animation.transition({
duration: 4000,
timingFunction: "ease",
callback: loopAnimation,
...defaultOptions(),
})
}
animation.transition({
duration: 0,
callback: loopAnimation,
...defaultOptions(),
})
const fullscreen = () => {
const options = defaultOptions()
options.blobOptions.size = Math.max(width, height) * 1.6
options.blobOptions.randomness = 1.4
options.canvasOptions.offsetX = -size / 2
options.canvasOptions.offsetY = -size / 2
animation.transition({
duration: 2000,
timingFunction: "elasticEnd0",
...options,
})
}
}, [canvasRef])
return (
<div>
<canvas className="absolute top-0 left-0 h-full w-full" ref={canvasRef} />
</div>
)
}
export default CornerExpand
Everything works as well, but now I have a problem. I want to execute the fullscreen() function when a button is clicked in the parent component. Since I have defined the function in useEffect, I can't call it directly, isn't it? What can I do to solve this?
You can do it like this.
export const CornerExpand = React.forwardRef((props, ref) => {
//....
{
//...
const fullscreen = () => {
const options = defaultOptions()
options.blobOptions.size = Math.max(width, height) * 1.6
options.blobOptions.randomness = 1.4
options.canvasOptions.offsetX = -size / 2
options.canvasOptions.offsetY = -size / 2
animation.transition({
duration: 2000,
timingFunction: "elasticEnd0",
...options,
})
}
ref.current = fullscreen;
}, [canvasRef]);
You can wrap this comp with React.forwardRef. And call it from parent.
<CornerExpand ref={fullscreenCallbackRef} />
And then call like this
fullscreenCallbackRef.current()

React + D3 + Force-Directed Tree + Adjustable Link Strength

I'm trying to make Force-Directed Tree with React and it works. But I cannot modify "link strength", if I pass it outside component through the props.
Honestly, I can change "strength", but I need to append d3 svg to my react ref div after that to see the changes. And whole graph will be redrawn.
I find example by Mike Bostock. He advice to modify the parameters of a force-directed graph with reheat the simulation using simulation.alpha and simulation.restart. But I cannot make it works with react. Nothing happens.
Here is my code:
export default function Hierarchy(props) {
const {
strength,
lineColor,
lineStroke,
width,
height,
nodeSize,
nodeColor,
} = props;
const root = d3.hierarchy(data);
const links = root.links();
const nodes = root.descendants();
const svg = d3.create("svg");
const link = svg
.append("g")
.selectAll("line")
.data(links)
.join("line");
const node = svg
.append("g")
.selectAll("circle")
.data(nodes)
.join("circle");
function applyStyle(selectionSVG) {
selectionSVG
.attr("width", width)
.attr("height", height)
.attr("viewBox", [-width / 2, -height / 2, width, height]);
selectionSVG
.selectAll("circle")
.attr("r", nodeSize)
.attr("fill", nodeColor)
selectionSVG
.selectAll("line")
.attr("stroke", lineColor)
.attr("stroke-width", lineStroke);
}
applyStyle(svg);
const divRef = React.useRef(null);
const linkForce = d3
.forceLink(links)
.id(d => d.id)
.distance(0)
.strength(strength);
const simulation = d3
.forceSimulation(nodes)
.force("link", linkForce)
.force("charge", d3.forceManyBody().strength(-500))
.force("x", d3.forceX())
.force("y", d3.forceY());
simulation.on("tick", () => {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
node.attr("cx", d => d.x).attr("cy", d => d.y);
});
//ComponentDidMount
useEffect(() => {
//Append d3 svg to ref div
var div = d3.select(divRef.current);
if (div.node().firstChild) {
div.node().removeChild(div.node().firstChild);
}
div.node().appendChild(svg.node());
}, []);
//ComponentDidUpdate
useEffect(() => {
simulation.force("link").strength(strength);
simulation.alpha(1).restart();
}, [strength]);
//ComponentDidUpdate
useEffect(() => {
var div = d3.select(divRef.current);
applyStyle(div.select("svg"));
});
//Render
return <div id="hierarchyTree" ref={divRef} />;
}
Here is Sandbox.
I find solution, if anybody interesting.
The fact is simulation was not saved when component was updated. So I create ref for it.
const simulationRef = React.useRef(simulation)
and replace it in useEffect section
//ComponentDidUpdate
useEffect(() => {
simulationRef.current.force("link").strength(strength)
simulationRef.current.alpha(1).restart()
console.log(simulationRef.current)
}, [strength])
After that everything works fine.

passing data from a react based component to vanilla js class

I am trying to pass data from react class base component to a vanillajs class so this class is able to render D3 bar chart ,
I've tried passing the data from the react component through the contractor of the vanilla class , i have the data available in the vanilla class when i try to consol log it , but when i want to call the data variable in the method call d3.data() it is empty , here is the code
React class
//imports..
const _data = []
const firebaseConfig = {
//configuration ..
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const db = firebase.firestore()
class TableOfD3 extends Component {
constructor(){
super()
this.svgId = `SVG_${uuid()}`
}
getData(){
db.collection('db').get().then( res=>{
res.docs.forEach(doc => {
_data.push(doc.data())
})
}
componentDidMount(){
this.start()
}
componentDidUpdate(){
this.start()
}
start(){
this._graph = new D3TableEngine('#' + this.svgId,_data)
this._graph.start()
}
render() {
return (
<div>
<svg id={this.svgId}></svg>
</div>
);
}
}
export default TableOfD3;
// vanillajs class
export default class D3TableEngine {
constructor(svgId, passedData) {
this._svg = d3.select(`${svgId}`);
this._svg.attr('width', _WIDTH)
this._svg.attr('height', _HEIGHT)
this._passedData = passedData
}
start() {
const self = this;
var _g = self._svg;
const graphWidth = _WIDTH - _MARGIN.left - _MARGIN.right
const graphHeight = _HEIGHT - _MARGIN.top - _MARGIN.bottom
const graph = _g.append('g')
.attr('width', graphWidth)
.attr('height', graphHeight)
.attr('transform', `translate(${_MARGIN.left + 20}, ${_MARGIN.top})`)
const xAxisGroup = graph.append('g')
.attr('transform', `translate(0,${graphHeight })`)
const yAxisGroup = graph.append('g')
const yScale = d3.scaleLinear()
.domain([0,d3.max(self._passedData, (d) => d.orders)])
.range([graphHeight,0])
const xScale = d3.scaleBand()
.domain(self._passedData.map((el) => el.name))
.range([0,500])
.paddingInner(0.2)
.paddingOuter(0.2)
const rects = graph.selectAll("rect").data(self._passedData);
rects
.attr("x", (d)=> xScale(d.name))
.attr("y", (d) => yScale( d.orders))
.attr("height", (d)=> graphHeight - yScale( d.orders))
.attr("width", xScale.bandwidth)
.attr('fill', 'blue')
rects
.enter()
.append("rect")
.attr("x", (d)=> xScale(d.name))
.attr("y", (d) => yScale( d.orders))
.attr("height", (d)=> graphHeight - yScale( d.orders ))
.attr("width", xScale.bandwidth)
.attr('fill', 'blue')
const xAxis = d3.axisBottom(xScale)
xAxisGroup.call(xAxis)
const yAxis = d3.axisLeft(yScale)
.ticks(5)
.tickFormat((d) => 'Orders ' +d )
yAxisGroup.call(yAxis)
xAxisGroup.selectAll('text')
.attr('transform', 'rotate(-40)' )
.attr('text-anchor', 'end')
} )
}
refresh() {}
}
I re-wrote your React class because you were doing many things that would be considered anti-pattern. In general, you want to shove as much as you can in this.state. Otherwise, you miss out on the main advantage of React - and that is optimally re-rendering the DOM when variables change. I think the main issue you're likely having is that you're updating the DOM from componentDidUpdate(), which will fire another update. It'll continue infinitely and crash. I would strongly recommend refactoring D3TableEngine into a React Component instead of a plain JS class. The challenge is that the way you have written the d3 component, it has to be destroyed and re-created for each render, which is a problem because React doesn't know what to do other than re-create it.
import React, { Component } from 'react';
class TableOfD3 extends Component {
constructor() {
super();
const firebaseConfig = {
//configuration ..
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
this.state = {
svgId: `SVG_${uuid()}`,
data: [],
db: db
};
}
componentDidMount() {
const response = await this.state.db.collection('db').get();
const data = response.docs.map(doc => doc.data());
this.setState({
data
});
}
componentDidUpdate() {
}
render() {
return (
<div>
<D3TableEngine
id={this.state.svgId}
data={this.state.data}
/>
</div>
);
}
}
UPDATE: I gave a shot at refactoring your d3 class into a React Component. The important pieces here are the ref, which let's you get a reference to the element so redraw can execute all the d3 code on the right svg element. Then, inside componentDidMount and componentDidUpdate, you must call redraw. However, I would refactor the redraw method to break out the parts that will change from the parts that will not change (eg: move the graph pieces into a different function and call that in componentDidUpdate). We do this so that React is performing as expected and only updating the elements in the DOM that have changed. If you need additional help, you may take a look at this jsfiddle example/medium article.
const MARGIN = 0;
const WIDTH = 0;
const HEIGHT = 0;
class D3TableEngine extends Component {
componentDidMount() {
redraw();
}
componentDidUpdate() {
redraw();
}
redraw = () => {
this.svg = d3.select(this.svg);
const graphWidth = WIDTH - MARGIN.left - MARGIN.right
const graphHeight = HEIGHT - MARGIN.top - MARGIN.bottom
const graph = this.svg.append('g')
.attr('width', graphWidth)
.attr('height', graphHeight)
.attr('transform', `translate(${_MARGIN.left + 20}, ${_MARGIN.top})`)
const xAxisGroup = graph.append('g')
.attr('transform', `translate(0,${graphHeight})`)
const yAxisGroup = graph.append('g')
const yScale = d3.scaleLinear()
.domain([0, d3.max(props.data, (d) => d.orders)])
.range([graphHeight, 0])
const xScale = d3.scaleBand()
.domain(props.data.map((el) => el.name))
.range([0, 500])
.paddingInner(0.2)
.paddingOuter(0.2)
const rects = graph.selectAll("rect").data(props.data);
rects
.attr("x", (d) => xScale(d.name))
.attr("y", (d) => yScale(d.orders))
.attr("height", (d) => graphHeight - yScale(d.orders))
.attr("width", xScale.bandwidth)
.attr('fill', 'blue')
rects
.enter()
.append("rect")
.attr("x", (d) => xScale(d.name))
.attr("y", (d) => yScale(d.orders))
.attr("height", (d) => graphHeight - yScale(d.orders))
.attr("width", xScale.bandwidth)
.attr('fill', 'blue')
const xAxis = d3.axisBottom(xScale)
xAxisGroup.call(xAxis)
const yAxis = d3.axisLeft(yScale)
.ticks(5)
.tickFormat((d) => 'Orders ' + d)
yAxisGroup.call(yAxis)
xAxisGroup.selectAll('text')
.attr('transform', 'rotate(-40)')
.attr('text-anchor', 'end')
}
render() {
return (
<svg
id={this.props.svgId}
width={WIDTH}
height={HEIGHT}
ref={el => (this.svg = d3.select(el))}
>
</svg>
);
}
}

Resources