import React, { useRef, useEffect, useState } from 'react';
import * as d3 from 'd3';
import useWalks from '@component/stores/walks';
import { CHART_COLORS } from './colors';
/**
* Generates a line chart based on the provided data, attribute, and selected walks.
*
* @param {object} ref - The reference to the chart container.
* @param {Array} data - The data used to generate the chart.
* @param {string} attribute - Name of the changing attribute in the selected direction.
* @param {Array} selectedWalks - Walk selection for filtering (selected in umapScatterplot).
*/
const generateLineChart = (ref, data, attribute, selectedWalks) => {
const primary = CHART_COLORS.primary;
const secondary = CHART_COLORS.secondary;
const light_grey = CHART_COLORS.light_grey;
let slopesValues = [];
let walks = [];
data.forEach((obj) => {
let attrObj = obj.attributes.find(attr => attr.name === attribute);
slopesValues.push(attrObj.slope);
walks.push(attrObj.steps);
});
let selectedWalksData = walks;
if (selectedWalks.length > 0) {
selectedWalksData = selectedWalks.map((index) => walks[index]);
slopesValues = selectedWalks.map((index) => slopesValues[index]);
}
const container = d3.select(ref.current)
container.select('svg').remove();
const margin = { top: 40, right: 40, bottom: 0, left: 10 };
const width = container.node().clientWidth - margin.right;
const height = container.node().clientHeight;
// line chart settingsflattenWalks
const lineChartWidth = width * 0.7 - margin.left;
const innerWidth = lineChartWidth - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
// bar chart settings
const barChartWidth = width * 0.3 - margin.left;
const barChartHeight = height;
const svg = container
.append('svg')
.attr('width', width)
.attr('height', height);
// add border
svg
.append('rect')
.attr('width', width)
.attr('height', height)
.attr('fill', 'none')
.attr('stroke', 'grey')
.attr('stroke-width', 1);
// add title
svg
.append('text')
.attr('x', margin.left)
.attr('y', margin.top / 2)
.text(attribute.replaceAll('_', ' '))
.attr('font-size', '12px');
const xScale = d3
.scaleLinear()
.domain([0, 99])
.range([margin.left, lineChartWidth]);
// const yValues = flattenWalks.map((d) => d);
const yScale = d3
.scaleLinear()
.domain([-1, 1])
.range([180, margin.top]);
const line = d3
.line()
.x((d, i) => xScale(i))
.y((d, i) => yScale(d));
selectedWalksData.forEach((walk) => {
svg
.append('path')
.datum(walk)
.attr('fill', 'none')
.attr('stroke', 'grey')
.attr('stroke-width', 0.5)
.attr('d', line);
});
// generate Bar Chart
const extent = d3.extent(slopesValues);
const maxAbsVal = Math.max(Math.abs(extent[0]), Math.abs(extent[1]));
const thresholds = d3.range(-4, 5, 1).map(d => d * maxAbsVal / 4);
const bins = d3.bin()
.domain([-maxAbsVal, maxAbsVal])
.thresholds(thresholds)(slopesValues);
const numBins = bins.length;
const maxLength = d3.max(bins, d => d.length);
const padding = 0.2;
const xBarScale = d3
.scaleBand()
.domain(bins.map(d => d.x0)) // Use the bin start values as the domain
.range([0, barChartWidth])
.paddingInner(padding)
.paddingOuter(padding / 2);
const yBarScale = d3
.scaleLinear()
.domain([0, maxLength])
.range([barChartHeight, margin.top]);
const barChartContainer = svg
.append('g')
.attr('class', 'bar-chart-container')
.attr('transform', `translate(${lineChartWidth + margin.left}, 0)`);
barChartContainer
.selectAll('rect')
.data(bins)
.enter()
.append("rect")
.attr("x", d => xBarScale(+d.x0))
.attr("y", d => yBarScale(+d.length))
.attr("height", d => barChartHeight - yBarScale(+d.length))
.attr("width", xBarScale.bandwidth())
.attr('fill', d => (+d.x0 >= 0 ? secondary : primary));
svg.append("line")
.attr("x1", lineChartWidth) // x position of the first end of the line
.attr("y1", 0) // y position of the first end of the line
.attr("x2", lineChartWidth) // x position of the second end of the line
.attr("y2", height) // y position of the second end of the line
.attr("stroke-width", 1)
.attr("stroke", light_grey);
}
/**
* LineChart component displays a line chart based on the provided attribute.
*
* @param {object} props - The component props.
* @param {string} props.attribute - The changing attribute for the line chart.
* @returns {JSX.Element} - The LineChart component, displaying a linechart for a single attribute for a selection of walks.
*/
export function LineChart({ attribute }) {
const chartRef = useRef();
const walks = useWalks(state => state.walks);
const selectedWalks = useWalks(state => state.selectedWalks);
// generate line chart after data is loaded
useEffect(() => {
if (walks.length > 0) {
generateLineChart(chartRef, walks, attribute, selectedWalks);
}
}, [walks, attribute, selectedWalks]);
return (
<svg style={{ width: '300px', height: '110px' }} ref={chartRef} />
);
};