I'm both new to d3 and React, and I'm trying to draw a linechart with d3 with data I get from my backend through a async rest call. When I used static data from a .csv file, it worked. But when I try to use the backend data, neither the xScale is on the correct position nor the lines are shown.
As far I can see from my console outputs, the data is formatted and fetched correctly when I'm trying to draw the lines. Since neither the xScale or my lines are visible, my guess is that the zoom also doesn't work anymore.
Edit: I also noticed that the xAxis is displayed on top even when I use d3.axisBottom
Hope someone can help me.
type ChartDataType = {
timestamp: Date,
value: number
}
const LineChart = ({
width, height, top, right, bottom, left, fill, url, equipmentUUID
}: ZoomBasicLineChartProps) => {
const dispatch = useDispatch<AppDispatch>()
const [data, setData] = useState<ChartDataType[]>()
const title = useRef<string>('Default title, use label')
const containerRef = useRef<HTMLDivElement>()
let container: Selection<SVGGElement, unknown, HTMLElement, unknown>;
let zoom: d3.ZoomBehavior<HTMLDivElement, unknown>;
const chartWidth = width - left - right
const chartHeight = height - top - bottom
let xAxis: Selection<SVGGElement, unknown, HTMLElement, unknown>;
let yAxis: Selection<SVGGElement, unknown, HTMLElement, unknown>;
let path: any;
const diagramDTO = useSelector(LineChartDiagramSelectors.strippedLineChartDiagramData)
const loadStrippedLineChartDiagramData = useCallback(() => {
dispatch(LineChartDiagramDataThunks.getLineChartDiagramDataArray("services/performancemanagement/api/diagram/line-chart/7f5a2e69-0a51-4131-a77f-601ae9de24c6/0/SchlagstatistikGes_VR1?since=148"))
}, [dispatch])
const containerSelection = useMemo(() => (
d3.select<HTMLDivElement, unknown>('#ZoomLineChart')
), [containerRef.current])
const xScale = useMemo(() => {
const domain = data ? d3.extent(data, (d) => d.timestamp) : [new Date(), new Date()]
console.log("domain")
console.log(domain)
return d3
.scaleTime()
.domain([domain[0] ?? new Date(), domain[1] ?? new Date()])
.range([0, width])
}, [data])
const yScale = useMemo(() => {
const domain = data ? d3.extent(data, (d) => d.value) : [0, 0]
return d3
.scaleLinear()
.domain([domain[0] ?? 0, domain[1] ?? 0])
.range([height, 0])
}, [data])
const initSvg = (): SelectionType => (
containerSelection
.append('svg')
.attr('width', chartWidth left right)
.attr('height', chartHeight top bottom 75)
.append('g')
.attr('transform', `translate(${left},${top})`)
)
const drawAxes = (g: SelectionType): void => {
xAxis = g.append('g')
.call(d3.axisBottom(xScale))
yAxis = g.append('g')
.call(d3.axisLeft(yScale))
}
const drawLabel = (g: SelectionType): void => {
g.append('text')
.attr('text-anchor', 'start')
.attr('y', height 40)
.attr('x', 0)
.text(title.current)
}
const drawLines = (g: SelectionType): void => {
if (data) {
const lines = d3.line<ChartDataType>()
.x(d => {
// console.log(d.timestamp)
return xScale(d.timestamp)
})
.y(d => {
// console.log(d.value)
return yScale(d.value)
})
console.log("data")
console.log(data)
path = g
.append('g')
.attr('clip-path', "url(#clip)")
.append('path')
.datum(data)
.attr('class', 'line')
.attr('fill', 'none')
.attr('stroke', fill)
.attr('stroke-width', 1.5)
.attr('d', lines)
}
}
function updateChart(event: any) {
const {transform} = event;
const newX = transform.rescaleX(xScale);
xAxis.call(d3.axisBottom(newX));
path.attr("d", d3.line<ChartDataType>()
.x(d => newX(d.timestamp))
.y(d => yScale(d.value)));
}
const clip = (g: SelectionType): void => {
g.append("defs")
.append("SVG:clipPath")
.attr("id", "clip")
.append("SVG:rect")
.attr("width", width)
.attr("height", height)
.attr("x", 0)
.attr("y", 0);
}
const initZoom = (g: SelectionType): void => {
zoom = d3.zoom<HTMLDivElement, unknown>()
.scaleExtent([0.5, 5])
.on('zoom', updateChart)
containerSelection.call(zoom)
}
// Parse into data object
useEffect(() => {
if (diagramDTO) {
const updatedData = diagramDTO.payload?.map(dataPoint => {
const value = dataPoint.value
return {timestamp: dataPoint.timestamp, value}
})
setData(updatedData)
} else {
loadStrippedLineChartDiagramData()
}
}, [diagramDTO])
useEffect(() => {
container = initSvg();
initZoom(container)
clip(container)
drawAxes(container)
drawLines(container)
drawLabel(container)
}, [data])
return (
<>
<Box id='ZoomLineChart' ref={containerRef}/>
</>
)
}
Example data playload:
{
"unit" : "unit.schlagstatistikges_vr1",
"label" : "label.schlagstatistikges_vr1",
"payload" : [ {
"timestamp" : "2022-06-08T03:22:00Z",
"value" : "10676"
}, {
"timestamp" : "2022-06-08T03:23:00Z",
"value" : "10583"
}, {
"timestamp" : "2022-06-08T03:24:00Z",
"value" : "10647"
}, {
"timestamp" : "2022-06-08T03:25:00Z",
"value" : "10585"
}, {
"timestamp" : "2022-06-08T03:26:00Z",
"value" : "10644"
}, {
"timestamp" : "2022-06-08T03:27:00Z",
"value" : "10227"
}, {
"timestamp" : "2022-06-08T03:28:00Z",
"value" : "10620"
}, {
"timestamp" : "2022-06-08T03:29:00Z",
"value" : "10635"
}, {
"timestamp" : "2022-06-08T03:30:00Z",
"value" : "10432"
}, {
"timestamp" : "2022-06-08T03:31:00Z",
"value" : "10295"
}, {
"timestamp" : "2022-06-08T03:32:00Z",
"value" : "10674"
}, {
"timestamp" : "2022-06-08T03:33:00Z",
"value" : "10715"
}, {
"timestamp" : "2022-06-08T03:34:00Z",
"value" : "10068"
}, {
"timestamp" : "2022-06-08T03:35:00Z",
"value" : "10262"
}, {
"timestamp" : "2022-06-08T03:36:00Z",
"value" : "10926"
}, {
"timestamp" : "2022-06-08T03:37:00Z",
"value" : "10271"
}, {
"timestamp" : "2022-06-08T03:38:00Z",
"value" : "10870"
} ],
"color" : "#80BEBF"
}
CodePudding user response:
I found the problem: my IDE recognized the date object, but d3 required an explicit timeparse:
const updatedData = diagramDTO.payload?.map(dataPoint => {
const parser = d3.timeParse('%Y-%m-%dT%H:%M:%S.%LZ')
const timestamp = parser(String(dataPoint.timestamp))!
const value = dataPoint.value
return {timestamp, value}
})