Skip to content

Commit 9ef185d

Browse files
committed
WiP distribution chart
1 parent 5d2c7c5 commit 9ef185d

File tree

3 files changed

+214
-8
lines changed

3 files changed

+214
-8
lines changed

src/assets/synthetic-data.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -618,16 +618,10 @@ def run():
618618
))
619619
setResult(json.dumps({'type': 'table', 'data': synthetic_data.head().to_json(orient="records")}))
620620
621-
np.random.seed(42)
622-
# heatmap = np.random.rand(100, 10)
621+
setResult(json.dumps({'type': 'heatmap', 'real': real_data.corr().to_json(orient="records"), 'synthetic': synthetic_data.corr().to_json(orient="records")}))
623622
624-
# Compute the (test) correlation matrix
625-
# correlation_matrix = np.corrcoef(heatmap, rowvar=False)
626-
# setResult(json.dumps({'type': 'heatmap', 'data': correlation_matrix.tolist()}))
623+
setResult(json.dumps({'type': 'distribution', 'real': real_data.to_json(orient="records"), 'synthetic': synthetic_data.to_json(orient="records")}))
627624
628-
setResult(json.dumps({'type': 'heatmap', 'real': real_data.corr().to_json(orient="records"), 'synthetic': synthetic_data.corr().to_json(orient="records")}))
629-
630-
# setResult(json.dumps({'type': 'heatmap', 'data': synthetic_data.corr().to_json(orient="records")}))
631625
return
632626
633627

src/components/componentMapper.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Fragment } from 'react/jsx-runtime';
99
import { Accordion } from './ui/accordion';
1010
import { useTranslation } from 'react-i18next';
1111
import HeatMapChart from './graphs/HeatMap';
12+
import DistributionBarChart from './graphs/DistributionBarChart';
1213

1314
const createArrayFromPythonDictionary = (dict: Record<string, number>) => {
1415
const resultArray = [];
@@ -172,6 +173,37 @@ export default function ComponentMapper({
172173
</ErrorBoundary>
173174
);
174175
}
176+
case 'distribution': {
177+
const realData = JSON.parse(resultItem.real);
178+
const syntheticData = JSON.parse(resultItem.synthetic);
179+
return realData.length === 0 ||
180+
syntheticData.length === 0
181+
? null
182+
: Object.keys(realData[0]).map(
183+
(columnName: string, index: number) => {
184+
const realDataColumn = realData.map(
185+
(row: Record<string, number>) =>
186+
row[columnName]
187+
);
188+
const syntheticDataColumn =
189+
syntheticData.map(
190+
(row: Record<string, number>) =>
191+
row[columnName]
192+
);
193+
return (
194+
<ErrorBoundary key={index}>
195+
<DistributionBarChart
196+
realData={realDataColumn}
197+
syntheticData={
198+
syntheticDataColumn
199+
}
200+
column={columnName}
201+
/>
202+
</ErrorBoundary>
203+
);
204+
}
205+
);
206+
}
175207
case 'heatmap': {
176208
/*
177209
resultItem.real
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import { useEffect, useRef, useState } from 'react';
2+
import * as d3 from 'd3';
3+
4+
interface DistributionBarChartProps {
5+
column: string;
6+
realData: number[];
7+
syntheticData: number[];
8+
}
9+
10+
// Define margins for the chart
11+
const margin = { top: 10, right: 50, bottom: 40, left: 50 };
12+
// Define height for the chart, adjusting for margins
13+
const height = 300 - margin.top - margin.bottom;
14+
15+
// Define width of bars and adjust for screenwidth
16+
// const barWidth = 0.05 * window.innerWidth < 40 ? 40 : 0.05 * window.innerWidth;
17+
// const barGap = 5;
18+
19+
const DistributionBarChart = ({
20+
column,
21+
realData,
22+
syntheticData,
23+
}: DistributionBarChartProps) => {
24+
const svgRef = useRef<SVGSVGElement>(null); // Reference to the SVG element
25+
const containerRef = useRef<HTMLDivElement>(null); // Reference to the container div
26+
const [containerWidth, setContainerWidth] = useState(800); // Default container width
27+
28+
// Create x-axis scale using d3.scaleBand, with padding for spacing between bars
29+
// const x0 = useMemo(
30+
// () =>
31+
// d3
32+
// .scaleBand()
33+
// .domain(data.map(d => d.name))
34+
// .range([
35+
// 0,
36+
// Math.max(
37+
// containerWidth - margin.right,
38+
// data.length * (barWidth + barGap)
39+
// ),
40+
// ])
41+
// .padding(0.2),
42+
// [data, containerWidth]
43+
// );
44+
45+
// // Create y-axis scale using d3.scaleLinear, with a range from the height to 0
46+
// const y = useMemo(
47+
// () =>
48+
// d3
49+
// .scaleLinear()
50+
// .domain([
51+
// d3.min(data, d => d.values) ?? 0, // Minimum value in the dataset (or 0 if undefined)
52+
// d3.max(data, d => d.values) ?? 0, // Maximum value in the dataset (or 0 if undefined)
53+
// ])
54+
// .nice() // Rounds the domain to nice round values
55+
// .range([height, 0]),
56+
// [data]
57+
// );
58+
59+
useEffect(() => {
60+
const plotWidth = containerWidth - margin.left - margin.right;
61+
const plotHeight = height - margin.top - margin.bottom;
62+
63+
const combinedData = [...realData, ...syntheticData];
64+
const xScale = d3
65+
.scaleLinear()
66+
.domain([d3.min(combinedData) || 0, d3.max(combinedData) || 1])
67+
.range([0, plotWidth]);
68+
69+
const binsReal = d3
70+
.bin()
71+
.domain(xScale.domain() as [number, number])
72+
.thresholds(30)(realData);
73+
74+
const binsSynthetic = d3
75+
.bin()
76+
.domain(xScale.domain() as [number, number])
77+
.thresholds(30)(syntheticData);
78+
79+
const yScale = d3
80+
.scaleLinear()
81+
.domain([
82+
0,
83+
d3.max([...binsReal, ...binsSynthetic], d => d.length) || 1,
84+
])
85+
.range([plotHeight, 0]);
86+
87+
// Clear any previous SVG content to avoid overlapping elements
88+
d3.select(svgRef.current).selectAll('*').remove();
89+
90+
// Create the SVG container and set its dimensions
91+
const svg = d3
92+
.select(svgRef.current)
93+
.attr('class', `min-h-[${height}px]`)
94+
.attr('width', containerWidth)
95+
.attr('height', height + margin.top + margin.bottom)
96+
.append('g')
97+
.attr('transform', `translate(${margin.left},${margin.top})`);
98+
// Add axes
99+
svg.append('g')
100+
.attr('transform', `translate(0, ${plotHeight})`)
101+
.call(d3.axisBottom(xScale));
102+
svg.append('g').call(d3.axisLeft(yScale));
103+
104+
svg.append('defs')
105+
.append('style')
106+
.attr('type', 'text/css')
107+
.text(
108+
"@import url('https://fonts.googleapis.com/css2?family=Avenir:wght@600');"
109+
);
110+
111+
// Draw real data histogram
112+
svg.selectAll('.bar-real')
113+
.data(binsReal)
114+
.enter()
115+
.append('rect')
116+
.attr('class', 'bar-real')
117+
.attr('x', d => xScale(d.x0 || 0))
118+
.attr('y', d => yScale(d.length))
119+
.attr('width', d => xScale(d.x1 || 0) - xScale(d.x0 || 0) - 1)
120+
.attr('height', d => plotHeight - yScale(d.length))
121+
.style('fill', 'steelblue')
122+
.style('opacity', 0.5);
123+
124+
// Draw synthetic data histogram
125+
svg.selectAll('.bar-synthetic')
126+
.data(binsSynthetic)
127+
.enter()
128+
.append('rect')
129+
.attr('class', 'bar-synthetic')
130+
.attr('x', d => xScale(d.x0 || 0))
131+
.attr('y', d => yScale(d.length))
132+
.attr('width', d => xScale(d.x1 || 0) - xScale(d.x0 || 0) - 1)
133+
.attr('height', d => plotHeight - yScale(d.length))
134+
.style('fill', 'orange')
135+
.style('opacity', 0.5);
136+
137+
// Add title
138+
svg.append('text')
139+
.attr('x', plotWidth / 2)
140+
.attr('y', 10)
141+
.attr('text-anchor', 'middle')
142+
.style('font-size', '12px')
143+
.style('font-weight', 'bold')
144+
.text(`Distribution for ${column}`);
145+
146+
// Add a legend label for the mean line
147+
}, [containerWidth, column, realData, syntheticData]);
148+
149+
useEffect(() => {
150+
// Set up the ResizeObserver to track changes in the container's size
151+
const resizeObserver = new ResizeObserver(entries => {
152+
if (!entries || entries.length === 0) return;
153+
const { width } = entries[0].contentRect;
154+
setContainerWidth(width); // Update the state with the new container width
155+
});
156+
157+
if (containerRef.current) {
158+
resizeObserver.observe(containerRef.current); // Start observing the container
159+
}
160+
161+
return () => {
162+
if (containerRef.current) {
163+
resizeObserver.unobserve(containerRef.current); // Cleanup on component unmount
164+
}
165+
};
166+
}, []);
167+
168+
// Render the chart container and SVG element with horizontal scroll if needed
169+
return (
170+
<div
171+
ref={containerRef}
172+
style={{ width: '100%', display: 'flex', overflowX: 'auto' }}
173+
className={`min-h-[${height}px] flex-col`}
174+
>
175+
<svg ref={svgRef}></svg>
176+
</div>
177+
);
178+
};
179+
180+
export default DistributionBarChart;

0 commit comments

Comments
 (0)