Skip to content

Commit

Permalink
updated charts
Browse files Browse the repository at this point in the history
  • Loading branch information
desafinadude committed Feb 28, 2025
1 parent 2a4d741 commit 22657d8
Show file tree
Hide file tree
Showing 10 changed files with 1,126 additions and 1,321 deletions.
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@
"react-bootstrap": "^2.10.2",
"react-custom-scrollbars": "^4.2.1",
"react-dom": "^18.2.0",
"react-use-measure": "^2.1.7",
"reaviz": "^16.0.2"
"react-use-measure": "^2.1.7"
},
"devDependencies": {
"@parcel/transformer-sass": "2.13.3",
Expand Down
200 changes: 92 additions & 108 deletions src/components/charts/BubbleChart.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { scaleLinear } from '@visx/scale';
import { Circle, Line } from '@visx/shape';
import { AxisBottom } from '@visx/axis';
import { useTooltip, Tooltip, defaultStyles } from '@visx/tooltip';
import { localPoint } from '@visx/event';
import { bisector } from 'd3-array';
import { useState, useEffect, useRef } from 'react';

export default function BubbleChart({ data, referenceLines }) {
export default function BubbleChart({ data, data2 = null, party = null, xType = "count" }) {
const containerRef = useRef(null);
const [chartWidth, setChartWidth] = useState(100);
const height = 150;
const padding = 20;
const padding = [20, 40, 5, 40];

// Handle resizing
useEffect(() => {
Expand All @@ -27,157 +26,142 @@ export default function BubbleChart({ data, referenceLines }) {
return () => resizeObserver.disconnect();
}, []);

// Define scales
// Define xScale based on xType (count, time, or late)
const xDomain = xType === "count"
? [1, Math.max(...(data?.map((d) => d.x) || [1]), ...(data2?.map((d) => d.x) || [1]))]
: [
Math.min(...(data?.map((d) => d.x) || [0]), ...(data2?.map((d) => d.x) || [0])),
Math.max(...(data?.map((d) => d.x) || [0]), ...(data2?.map((d) => d.x) || [0]))
];

const xScale = scaleLinear({
domain: [Math.min(...data.map((d) => d.x)), Math.max(...data.map((d) => d.x))],
range: [padding, chartWidth - padding],
domain: xDomain,
range: [padding[3], chartWidth - padding[1]],
});

const yScale = scaleLinear({
domain: [0, Math.max(...data.map((d) => d.y)) + 5],
range: [height, padding],
domain: [0, Math.max(...(data?.map((d) => d.y) || [0]), ...(data2?.map((d) => d.y) || [0])) + 5],
range: [height - padding[2], padding[0]],
});

const sizeScale = scaleLinear({
domain: [Math.min(...data.map((d) => d.size)), Math.max(...data.map((d) => d.size))],
range: [5, 20],
domain: [
1, // Ensure minimum size of 1
Math.max(...(data?.map((d) => d.size) || [1]), ...(data2?.map((d) => d.size) || [1]))
],
range: [5, 20], // Keeps small values small
});


const { showTooltip, hideTooltip, tooltipData, tooltipLeft, tooltipTop } = useTooltip();
const bisectX = bisector((d) => d.x).left;

// Generate 30-minute interval ticks
const xMin = Math.min(...data.map((d) => d.x));
const xMax = Math.max(...data.map((d) => d.x));
const bisectX = bisector((d) => d.x).left;

// Find the closest multiple of 30 to start from
const firstTick = Math.ceil(xMin / 60) * 60;
// Generate x-axis intervals
const xMin = xDomain[0];
const xMax = xDomain[1];
const tickValues = [];
for (let tick = firstTick; tick <= xMax; tick += 60) {
tickValues.push(tick);
}

const handleMouseMove = (event) => {
if (!containerRef.current) return;

const { x, y } = localPoint(containerRef.current, event) || { x: 0, y: 0 };

// Find the closest circle
let closestCircle = null;
let minDistance = Infinity;

data.forEach((d) => {
const cx = xScale(d.x);
const cy = yScale(d.y);
const r = sizeScale(d.size);
if (xType === "count") {
for (let tick = Math.ceil(xMin); tick <= xMax; tick++) {
tickValues.push(tick);
}
} else if (xType === "time" || xType === "late") {
for (let tick = Math.ceil(xMin / 120) * 120; tick <= xMax; tick += 120) {
tickValues.push(tick);
}
}

// Calculate Euclidean distance from mouse to circle center
const distance = Math.sqrt((x - cx) ** 2 + (y - cy) ** 2);


if (distance < r && distance < minDistance) {
closestCircle = d;
minDistance = distance;
}
});

if (closestCircle) {
showTooltip({
tooltipLeft: xScale(closestCircle.x),
tooltipTop: yScale(closestCircle.y),
tooltipData: closestCircle,
});
} else {
hideTooltip();
}
};

return (
<div ref={containerRef} style={{ width: '100%', position: 'relative' }}>
<svg width={chartWidth} height={height} onMouseMove={handleMouseMove} onMouseLeave={hideTooltip}>

{/* <AxisBottom scale={xScale} top={height - padding} /> */}

{/* Bubble Circles */}
{data.map((d, i) => (
<Circle
key={i}
cx={xScale(d.x)}
cy={yScale(d.y)}
r={sizeScale(d.size)}
stroke="black"
strokeWidth={1}
fill="#fff"
onMouseEnter={(event) => {
showTooltip({
tooltipLeft: xScale(d.x),
tooltipTop: yScale(d.y),
tooltipData: d,
});
}}
onMouseLeave={hideTooltip}
/>
))}

{/* Vertical Reference Lines */}
{referenceLines.map((xVal, i) => (
<Line
key={i}
from={{ x: xScale(xVal), y: 0 }}
to={{ x: xScale(xVal), y: height }}
stroke={i === 0 ? 'black' : 'orange'}
strokeWidth={1}
strokeDasharray="4,4"
/>
))}

{/* Highlight Circle on Hover */}
{tooltipData && (
<Circle cx={tooltipLeft} cy={tooltipTop} r={sizeScale(tooltipData.size)} fill="rgba(251, 153, 5, 0.5)" />
)}



{/* Vertical Grid Lines at 30-minute intervals */}
<svg width={chartWidth} height={height} onMouseLeave={hideTooltip}>
{/* Vertical Grid Lines & Hover Triggers */}
{tickValues.map((xVal, i) => {
const xPos = xScale(xVal);
return (
<g key={i}>
<g key={i} >
<Line
from={{ x: xPos, y: padding }}
to={{ x: xPos, y: height - padding }}
stroke="lightgray"
from={{ x: xPos, y: padding[0] }}
to={{ x: xPos, y: height - padding[2] }}
stroke="#dadada"
strokeWidth={1}
strokeDasharray="4,4"
/>
<text
x={xPos}
y={height - padding + 15}
y={padding[0] - 5}
fontSize={10}
textAnchor="middle"
fill="black"
fill="#868686"
>
{xVal}
{xType === "time" || xType === "late" ? `${xVal}min` : xVal}
</text>
</g>
);
})}

{data.map((d, i) => (
<Circle
key={`data1-${i}`}
cx={xScale(d.x)}
cy={yScale(d.y) - 20}
r={sizeScale(d.size)}
stroke="black"
strokeWidth={tooltipData?.primary?.x === d.x ? 2 : 1.5}
fill="#fff"
onMouseEnter={(event) => {
const { x, y } = localPoint(containerRef.current, event);
showTooltip({
tooltipLeft: x,
tooltipTop: y,
tooltipData: { primary: d }
});
}}
onMouseLeave={hideTooltip}
/>
))}


{data2 &&
data2.map((d, i) => (
<Circle
key={`data2-${i}`}
cx={xScale(d.x)}
cy={yScale(d.y) + 20}
r={sizeScale(d.size)}
stroke="black"
strokeWidth={tooltipData?.secondary?.x === d.x ? 2 : 1.5}
fill="#fb9905"
onMouseEnter={(event) => {
const { x, y } = localPoint(containerRef.current, event);
showTooltip({
tooltipLeft: x,
tooltipTop: y,
tooltipData: { secondary: d }
});
}}
onMouseLeave={hideTooltip}
/>
))}
</svg>

{/* Tooltip rendered outside the SVG */}
{/* Tooltip */}
{tooltipData && (
<Tooltip
left={tooltipLeft + 10}
top={tooltipTop - 30}
style={defaultStyles}
left={tooltipLeft > chartWidth - 120 ? tooltipLeft - 120 : tooltipLeft + 10}
top={tooltipTop + 15} // Always just below the pointer
className="chart-tooltip"
>
<div>
<strong>x:</strong> {tooltipData.x}, <strong>y:</strong> {tooltipData.y}, <strong>size:</strong> {tooltipData.size}
<strong>{xType === "late" ? "Minutes Late" : xType === "time" ? "Length" : "Meetings"}:</strong> {tooltipData.primary?.x || tooltipData.secondary?.x}
<br />
<strong>{xType === "late" ? "Meetings" : xType === "time" ? "Meetings" : "Members"}:</strong> {tooltipData.primary?.size || tooltipData.secondary?.size}
</div>
</Tooltip>
)}

</div>
);
}
Loading

0 comments on commit 22657d8

Please sign in to comment.