refactored DreamCharts
to consolidate chart rendering logic into reusable renderChart
function; improved chart resize handling for better responsiveness and maintainability
Signed-off-by: Matthias Puchstein <matthias@puchstein.bayern>
This commit is contained in:
@@ -16,96 +16,101 @@ export const EEGChart: React.FC<{ chipInput: ChipInput }> = ({chipInput}) => {
|
|||||||
{name: 'Delta', values: chipInput.eeg.delta}
|
{name: 'Delta', values: chipInput.eeg.delta}
|
||||||
];
|
];
|
||||||
|
|
||||||
// Clear previous chart
|
// Function to render the chart
|
||||||
d3.select(chartRef.current).selectAll('*').remove();
|
const renderChart = () => {
|
||||||
|
if (!chartRef.current) return;
|
||||||
|
|
||||||
|
// Clear previous chart
|
||||||
|
d3.select(chartRef.current).selectAll('*').remove();
|
||||||
|
|
||||||
const margin = {top: 20, right: 20, bottom: 30, left: 50};
|
const margin = {top: 20, right: 20, bottom: 30, left: 50};
|
||||||
const width = chartRef.current.clientWidth - margin.left - margin.right;
|
const width = chartRef.current.clientWidth - margin.left - margin.right;
|
||||||
const height = chartRef.current.clientHeight - margin.top - margin.bottom;
|
const height = chartRef.current.clientHeight - margin.top - margin.bottom;
|
||||||
|
|
||||||
const svg = d3.select(chartRef.current)
|
const svg = d3.select(chartRef.current)
|
||||||
.append('svg')
|
.append('svg')
|
||||||
.attr('width', width + margin.left + margin.right)
|
.attr('width', width + margin.left + margin.right)
|
||||||
.attr('height', height + margin.top + margin.bottom)
|
.attr('height', height + margin.top + margin.bottom)
|
||||||
.append('g')
|
.append('g')
|
||||||
.attr('transform', `translate(${margin.left},${margin.top})`);
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
||||||
|
|
||||||
// X scale
|
// X scale
|
||||||
const x = d3.scaleLinear()
|
const x = d3.scaleLinear()
|
||||||
.domain([0, chipInput.eeg.alpha.length - 1])
|
.domain([0, chipInput.eeg.alpha.length - 1])
|
||||||
.range([0, width]);
|
.range([0, width]);
|
||||||
|
|
||||||
// Y scale
|
// Y scale
|
||||||
const y = d3.scaleLinear()
|
const y = d3.scaleLinear()
|
||||||
.domain([0, d3.max(eegData.flatMap(d => d.values)) || 50])
|
.domain([0, d3.max(eegData.flatMap(d => d.values)) || 50])
|
||||||
.range([height, 0]);
|
.range([height, 0]);
|
||||||
|
|
||||||
// Line generator
|
// Line generator
|
||||||
const line = d3.line<number>()
|
const line = d3.line<number>()
|
||||||
.x((_d, i) => x(i))
|
.x((_d, i) => x(i))
|
||||||
.y(d => y(d))
|
.y(d => y(d))
|
||||||
.curve(d3.curveMonotoneX);
|
.curve(d3.curveMonotoneX);
|
||||||
|
|
||||||
// Color scale
|
// Color scale
|
||||||
const color = d3.scaleOrdinal<string>()
|
const color = d3.scaleOrdinal<string>()
|
||||||
.domain(eegData.map(d => d.name))
|
.domain(eegData.map(d => d.name))
|
||||||
.range(['#8884d8', '#82ca9d', '#ffc658', '#ff8042']);
|
.range(['#8884d8', '#82ca9d', '#ffc658', '#ff8042']);
|
||||||
|
|
||||||
// Add X axis
|
// Add X axis
|
||||||
svg.append('g')
|
svg.append('g')
|
||||||
.attr('transform', `translate(0,${height})`)
|
.attr('transform', `translate(0,${height})`)
|
||||||
.call(d3.axisBottom(x))
|
.call(d3.axisBottom(x))
|
||||||
.selectAll('text')
|
.selectAll('text')
|
||||||
.style('fill', 'var(--text)');
|
.style('fill', 'var(--text)');
|
||||||
|
|
||||||
// Add Y axis
|
// Add Y axis
|
||||||
svg.append('g')
|
svg.append('g')
|
||||||
.call(d3.axisLeft(y))
|
.call(d3.axisLeft(y))
|
||||||
.selectAll('text')
|
.selectAll('text')
|
||||||
.style('fill', 'var(--text)');
|
.style('fill', 'var(--text)');
|
||||||
|
|
||||||
// Add lines
|
// Add lines
|
||||||
eegData.forEach(d => {
|
eegData.forEach(d => {
|
||||||
svg.append('path')
|
svg.append('path')
|
||||||
.datum(d.values)
|
.datum(d.values)
|
||||||
.attr('fill', 'none')
|
.attr('fill', 'none')
|
||||||
.attr('stroke', color(d.name))
|
.attr('stroke', color(d.name))
|
||||||
.attr('stroke-width', 1.5)
|
.attr('stroke-width', 1.5)
|
||||||
.attr('d', line);
|
.attr('d', line);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add legend at the top right
|
// Add legend at the top right
|
||||||
const legendItemHeight = 20; // Height allocated for each legend item
|
const legendItemHeight = 20; // Height allocated for each legend item
|
||||||
|
|
||||||
const legend = svg.append('g')
|
const legend = svg.append('g')
|
||||||
.attr('font-family', 'sans-serif')
|
.attr('font-family', 'sans-serif')
|
||||||
.attr('font-size', 10)
|
.attr('font-size', 10)
|
||||||
.attr('text-anchor', 'end')
|
.attr('text-anchor', 'end')
|
||||||
.selectAll('g')
|
.selectAll('g')
|
||||||
.data(eegData)
|
.data(eegData)
|
||||||
.enter().append('g')
|
.enter().append('g')
|
||||||
.attr('transform', (_d, i) => `translate(${width},${i * legendItemHeight + 10})`);
|
.attr('transform', (_d, i) => `translate(${width},${i * legendItemHeight + 10})`);
|
||||||
|
|
||||||
legend.append('rect')
|
legend.append('rect')
|
||||||
.attr('x', -15)
|
.attr('x', -15)
|
||||||
.attr('width', 15)
|
.attr('width', 15)
|
||||||
.attr('height', 15)
|
.attr('height', 15)
|
||||||
.attr('fill', d => color(d.name));
|
.attr('fill', d => color(d.name));
|
||||||
|
|
||||||
legend.append('text')
|
legend.append('text')
|
||||||
.attr('x', -20)
|
.attr('x', -20)
|
||||||
.attr('y', 7.5)
|
.attr('y', 7.5)
|
||||||
.attr('dy', '0.32em')
|
.attr('dy', '0.32em')
|
||||||
.style('fill', 'var(--text)')
|
.style('fill', 'var(--text)')
|
||||||
.text(d => d.name);
|
.text(d => d.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial render
|
||||||
|
renderChart();
|
||||||
|
|
||||||
// Handle resize
|
// Handle resize
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
if (!chartRef.current) return;
|
if (!chartRef.current) return;
|
||||||
|
renderChart();
|
||||||
// Re-render chart on resize
|
|
||||||
d3.select(chartRef.current).selectAll('*').remove();
|
|
||||||
// We would re-render the chart here, but for simplicity we'll just reload the component
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
@@ -127,98 +132,103 @@ export const VitalsChart: React.FC<{ chipInput: ChipInput }> = ({chipInput}) =>
|
|||||||
{name: 'HRV', values: chipInput.hrv}
|
{name: 'HRV', values: chipInput.hrv}
|
||||||
];
|
];
|
||||||
|
|
||||||
// Clear previous chart
|
// Function to render the chart
|
||||||
d3.select(chartRef.current).selectAll('*').remove();
|
const renderChart = () => {
|
||||||
|
if (!chartRef.current) return;
|
||||||
|
|
||||||
|
// Clear previous chart
|
||||||
|
d3.select(chartRef.current).selectAll('*').remove();
|
||||||
|
|
||||||
const margin = {top: 20, right: 20, bottom: 30, left: 50};
|
const margin = {top: 20, right: 20, bottom: 30, left: 50};
|
||||||
const width = chartRef.current.clientWidth - margin.left - margin.right;
|
const width = chartRef.current.clientWidth - margin.left - margin.right;
|
||||||
const height = chartRef.current.clientHeight - margin.top - margin.bottom;
|
const height = chartRef.current.clientHeight - margin.top - margin.bottom;
|
||||||
|
|
||||||
const svg = d3.select(chartRef.current)
|
const svg = d3.select(chartRef.current)
|
||||||
.append('svg')
|
.append('svg')
|
||||||
.attr('width', width + margin.left + margin.right)
|
.attr('width', width + margin.left + margin.right)
|
||||||
.attr('height', height + margin.top + margin.bottom)
|
.attr('height', height + margin.top + margin.bottom)
|
||||||
.append('g')
|
.append('g')
|
||||||
.attr('transform', `translate(${margin.left},${margin.top})`);
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
||||||
|
|
||||||
// X scale
|
// X scale
|
||||||
const x = d3.scaleLinear()
|
const x = d3.scaleLinear()
|
||||||
.domain([0, chipInput.puls.length - 1])
|
.domain([0, chipInput.puls.length - 1])
|
||||||
.range([0, width]);
|
.range([0, width]);
|
||||||
|
|
||||||
// Y scale
|
// Y scale
|
||||||
const minValue = d3.min(vitalsData.flatMap(d => d.values)) || 0;
|
const minValue = d3.min(vitalsData.flatMap(d => d.values)) || 0;
|
||||||
const maxValue = d3.max(vitalsData.flatMap(d => d.values)) || 100;
|
const maxValue = d3.max(vitalsData.flatMap(d => d.values)) || 100;
|
||||||
const y = d3.scaleLinear()
|
const y = d3.scaleLinear()
|
||||||
.domain([Math.max(0, minValue - 10), maxValue + 10])
|
.domain([Math.max(0, minValue - 10), maxValue + 10])
|
||||||
.range([height, 0]);
|
.range([height, 0]);
|
||||||
|
|
||||||
// Line generator
|
// Line generator
|
||||||
const line = d3.line<number>()
|
const line = d3.line<number>()
|
||||||
.x((_d, i) => x(i))
|
.x((_d, i) => x(i))
|
||||||
.y(d => y(d))
|
.y(d => y(d))
|
||||||
.curve(d3.curveMonotoneX);
|
.curve(d3.curveMonotoneX);
|
||||||
|
|
||||||
// Color scale
|
// Color scale
|
||||||
const color = d3.scaleOrdinal<string>()
|
const color = d3.scaleOrdinal<string>()
|
||||||
.domain(vitalsData.map(d => d.name))
|
.domain(vitalsData.map(d => d.name))
|
||||||
.range(['#ff6b6b', '#48dbfb']);
|
.range(['#ff6b6b', '#48dbfb']);
|
||||||
|
|
||||||
// Add X axis
|
// Add X axis
|
||||||
svg.append('g')
|
svg.append('g')
|
||||||
.attr('transform', `translate(0,${height})`)
|
.attr('transform', `translate(0,${height})`)
|
||||||
.call(d3.axisBottom(x))
|
.call(d3.axisBottom(x))
|
||||||
.selectAll('text')
|
.selectAll('text')
|
||||||
.style('fill', 'var(--text)');
|
.style('fill', 'var(--text)');
|
||||||
|
|
||||||
// Add Y axis
|
// Add Y axis
|
||||||
svg.append('g')
|
svg.append('g')
|
||||||
.call(d3.axisLeft(y))
|
.call(d3.axisLeft(y))
|
||||||
.selectAll('text')
|
.selectAll('text')
|
||||||
.style('fill', 'var(--text)');
|
.style('fill', 'var(--text)');
|
||||||
|
|
||||||
// Add lines
|
// Add lines
|
||||||
vitalsData.forEach(d => {
|
vitalsData.forEach(d => {
|
||||||
svg.append('path')
|
svg.append('path')
|
||||||
.datum(d.values)
|
.datum(d.values)
|
||||||
.attr('fill', 'none')
|
.attr('fill', 'none')
|
||||||
.attr('stroke', color(d.name))
|
.attr('stroke', color(d.name))
|
||||||
.attr('stroke-width', 1.5)
|
.attr('stroke-width', 1.5)
|
||||||
.attr('d', line);
|
.attr('d', line);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add legend at the top right
|
// Add legend at the top right
|
||||||
const legendItemHeight = 20; // Height allocated for each legend item
|
const legendItemHeight = 20; // Height allocated for each legend item
|
||||||
|
|
||||||
const legend = svg.append('g')
|
const legend = svg.append('g')
|
||||||
.attr('font-family', 'sans-serif')
|
.attr('font-family', 'sans-serif')
|
||||||
.attr('font-size', 10)
|
.attr('font-size', 10)
|
||||||
.attr('text-anchor', 'end')
|
.attr('text-anchor', 'end')
|
||||||
.selectAll('g')
|
.selectAll('g')
|
||||||
.data(vitalsData)
|
.data(vitalsData)
|
||||||
.enter().append('g')
|
.enter().append('g')
|
||||||
.attr('transform', (_d, i) => `translate(${width},${i * legendItemHeight + 10})`);
|
.attr('transform', (_d, i) => `translate(${width},${i * legendItemHeight + 10})`);
|
||||||
|
|
||||||
legend.append('rect')
|
legend.append('rect')
|
||||||
.attr('x', -15)
|
.attr('x', -15)
|
||||||
.attr('width', 15)
|
.attr('width', 15)
|
||||||
.attr('height', 15)
|
.attr('height', 15)
|
||||||
.attr('fill', d => color(d.name));
|
.attr('fill', d => color(d.name));
|
||||||
|
|
||||||
legend.append('text')
|
legend.append('text')
|
||||||
.attr('x', -20)
|
.attr('x', -20)
|
||||||
.attr('y', 7.5)
|
.attr('y', 7.5)
|
||||||
.attr('dy', '0.32em')
|
.attr('dy', '0.32em')
|
||||||
.style('fill', 'var(--text)')
|
.style('fill', 'var(--text)')
|
||||||
.text(d => d.name);
|
.text(d => d.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial render
|
||||||
|
renderChart();
|
||||||
|
|
||||||
// Handle resize
|
// Handle resize
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
if (!chartRef.current) return;
|
if (!chartRef.current) return;
|
||||||
|
renderChart();
|
||||||
// Re-render chart on resize
|
|
||||||
d3.select(chartRef.current).selectAll('*').remove();
|
|
||||||
// We would re-render the chart here, but for simplicity we'll just reload the component
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
@@ -239,92 +249,97 @@ export const MovementChart: React.FC<{ chipInput: ChipInput }> = ({chipInput}) =
|
|||||||
{name: 'Bewegung', values: chipInput.bewegung.map(v => v * 100)} // Scale movement for better visibility
|
{name: 'Bewegung', values: chipInput.bewegung.map(v => v * 100)} // Scale movement for better visibility
|
||||||
];
|
];
|
||||||
|
|
||||||
// Clear previous chart
|
// Function to render the chart
|
||||||
d3.select(chartRef.current).selectAll('*').remove();
|
const renderChart = () => {
|
||||||
|
if (!chartRef.current) return;
|
||||||
|
|
||||||
|
// Clear previous chart
|
||||||
|
d3.select(chartRef.current).selectAll('*').remove();
|
||||||
|
|
||||||
const margin = {top: 20, right: 20, bottom: 30, left: 50};
|
const margin = {top: 20, right: 20, bottom: 30, left: 50};
|
||||||
const width = chartRef.current.clientWidth - margin.left - margin.right;
|
const width = chartRef.current.clientWidth - margin.left - margin.right;
|
||||||
const height = chartRef.current.clientHeight - margin.top - margin.bottom;
|
const height = chartRef.current.clientHeight - margin.top - margin.bottom;
|
||||||
|
|
||||||
const svg = d3.select(chartRef.current)
|
const svg = d3.select(chartRef.current)
|
||||||
.append('svg')
|
.append('svg')
|
||||||
.attr('width', width + margin.left + margin.right)
|
.attr('width', width + margin.left + margin.right)
|
||||||
.attr('height', height + margin.top + margin.bottom)
|
.attr('height', height + margin.top + margin.bottom)
|
||||||
.append('g')
|
.append('g')
|
||||||
.attr('transform', `translate(${margin.left},${margin.top})`);
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
||||||
|
|
||||||
// X scale
|
// X scale
|
||||||
const x = d3.scaleLinear()
|
const x = d3.scaleLinear()
|
||||||
.domain([0, chipInput.bewegung.length - 1])
|
.domain([0, chipInput.bewegung.length - 1])
|
||||||
.range([0, width]);
|
.range([0, width]);
|
||||||
|
|
||||||
// Y scale
|
// Y scale
|
||||||
const y = d3.scaleLinear()
|
const y = d3.scaleLinear()
|
||||||
.domain([0, d3.max(bewegungData[0].values) || 100])
|
.domain([0, d3.max(bewegungData[0].values) || 100])
|
||||||
.range([height, 0]);
|
.range([height, 0]);
|
||||||
|
|
||||||
// Line generator
|
// Line generator
|
||||||
const line = d3.line<number>()
|
const line = d3.line<number>()
|
||||||
.x((_d, i) => x(i))
|
.x((_d, i) => x(i))
|
||||||
.y(d => y(d))
|
.y(d => y(d))
|
||||||
.curve(d3.curveMonotoneX);
|
.curve(d3.curveMonotoneX);
|
||||||
|
|
||||||
// Color scale
|
// Color scale
|
||||||
const color = '#1dd1a1'; // Use the same color as before for consistency
|
const color = '#1dd1a1'; // Use the same color as before for consistency
|
||||||
|
|
||||||
// Add X axis
|
// Add X axis
|
||||||
svg.append('g')
|
svg.append('g')
|
||||||
.attr('transform', `translate(0,${height})`)
|
.attr('transform', `translate(0,${height})`)
|
||||||
.call(d3.axisBottom(x))
|
.call(d3.axisBottom(x))
|
||||||
.selectAll('text')
|
.selectAll('text')
|
||||||
.style('fill', 'var(--text)');
|
.style('fill', 'var(--text)');
|
||||||
|
|
||||||
// Add Y axis
|
// Add Y axis
|
||||||
svg.append('g')
|
svg.append('g')
|
||||||
.call(d3.axisLeft(y))
|
.call(d3.axisLeft(y))
|
||||||
.selectAll('text')
|
.selectAll('text')
|
||||||
.style('fill', 'var(--text)');
|
.style('fill', 'var(--text)');
|
||||||
|
|
||||||
// Add line
|
// Add line
|
||||||
svg.append('path')
|
svg.append('path')
|
||||||
.datum(bewegungData[0].values)
|
.datum(bewegungData[0].values)
|
||||||
.attr('fill', 'none')
|
.attr('fill', 'none')
|
||||||
.attr('stroke', color)
|
.attr('stroke', color)
|
||||||
.attr('stroke-width', 1.5)
|
.attr('stroke-width', 1.5)
|
||||||
.attr('d', line);
|
.attr('d', line);
|
||||||
|
|
||||||
// Add legend at the top right
|
// Add legend at the top right
|
||||||
const legendItemHeight = 20; // Height allocated for each legend item
|
const legendItemHeight = 20; // Height allocated for each legend item
|
||||||
|
|
||||||
const legend = svg.append('g')
|
const legend = svg.append('g')
|
||||||
.attr('font-family', 'sans-serif')
|
.attr('font-family', 'sans-serif')
|
||||||
.attr('font-size', 10)
|
.attr('font-size', 10)
|
||||||
.attr('text-anchor', 'end')
|
.attr('text-anchor', 'end')
|
||||||
.selectAll('g')
|
.selectAll('g')
|
||||||
.data(bewegungData)
|
.data(bewegungData)
|
||||||
.enter().append('g')
|
.enter().append('g')
|
||||||
.attr('transform', (_d, i) => `translate(${width},${i * legendItemHeight + 10})`);
|
.attr('transform', (_d, i) => `translate(${width},${i * legendItemHeight + 10})`);
|
||||||
|
|
||||||
legend.append('rect')
|
legend.append('rect')
|
||||||
.attr('x', -15)
|
.attr('x', -15)
|
||||||
.attr('width', 15)
|
.attr('width', 15)
|
||||||
.attr('height', 15)
|
.attr('height', 15)
|
||||||
.attr('fill', () => color);
|
.attr('fill', () => color);
|
||||||
|
|
||||||
legend.append('text')
|
legend.append('text')
|
||||||
.attr('x', -20)
|
.attr('x', -20)
|
||||||
.attr('y', 7.5)
|
.attr('y', 7.5)
|
||||||
.attr('dy', '0.32em')
|
.attr('dy', '0.32em')
|
||||||
.style('fill', 'var(--text)')
|
.style('fill', 'var(--text)')
|
||||||
.text(d => d.name);
|
.text(d => d.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial render
|
||||||
|
renderChart();
|
||||||
|
|
||||||
// Handle resize
|
// Handle resize
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
if (!chartRef.current) return;
|
if (!chartRef.current) return;
|
||||||
|
renderChart();
|
||||||
// Re-render chart on resize
|
|
||||||
d3.select(chartRef.current).selectAll('*').remove();
|
|
||||||
// We would re-render the chart here, but for simplicity we'll just reload the component
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
|
Reference in New Issue
Block a user