Spaces:
Running
Running
File size: 4,621 Bytes
09801ca | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | import React, { useEffect, useRef } from 'react';
interface PlotlyChartProps {
data: any[];
layout: any;
config?: any;
}
/**
* PlotlyChart component - Renders interactive Plotly charts
* Uses dynamic import to avoid SSR issues
*/
const PlotlyChart: React.FC<PlotlyChartProps> = ({ data, layout, config }) => {
// Detect theme - app uses light-theme class for light mode, absence = dark mode
const [isDark, setIsDark] = React.useState(!document.documentElement.classList.contains('light-theme'));
const containerRef = useRef<HTMLDivElement>(null);
// Watch for theme changes on html element
useEffect(() => {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === 'class') {
setIsDark(!document.documentElement.classList.contains('light-theme'));
}
});
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class'],
});
return () => observer.disconnect();
}, []);
useEffect(() => {
const renderChart = async () => {
if (!containerRef.current) return;
try {
// Dynamic import of Plotly
const Plotly = await import('plotly.js-dist-min');
// Define theme colors based on current theme
const themeColors = isDark ? {
text: '#e5e7eb', // gray-200 - light text for dark mode
grid: 'rgba(255,255,255,0.1)',
bg: 'rgba(0,0,0,0)' // transparent
} : {
text: '#1f2937', // gray-800 - dark text for light mode
grid: 'rgba(0,0,0,0.1)',
bg: 'rgba(0,0,0,0)' // transparent
};
const defaultConfig = {
responsive: true,
displayModeBar: true,
modeBarButtonsToRemove: ['lasso2d', 'select2d'],
displaylogo: false,
...config
};
// Merge and override layout for theme compatibility
const enhancedLayout = {
...layout,
autosize: true,
paper_bgcolor: themeColors.bg,
plot_bgcolor: themeColors.bg,
font: {
...layout?.font,
color: themeColors.text
},
xaxis: {
...layout?.xaxis,
gridcolor: themeColors.grid,
color: themeColors.text,
tickfont: { color: themeColors.text }
},
yaxis: {
...layout?.yaxis,
gridcolor: themeColors.grid,
color: themeColors.text,
tickfont: { color: themeColors.text }
},
legend: {
...layout?.legend,
font: {
...layout?.legend?.font,
color: themeColors.text
}
},
title: layout?.title ? {
...layout.title,
font: {
...layout.title.font,
color: themeColors.text
}
} : undefined,
margin: { l: 50, r: 30, t: 40, b: 40, ...layout?.margin },
};
Plotly.default.newPlot(
containerRef.current,
data,
enhancedLayout,
defaultConfig
);
// Cleanup on unmount
return () => {
if (containerRef.current) {
Plotly.default.purge(containerRef.current);
}
};
} catch (error) {
console.error('Failed to render Plotly chart:', error);
}
};
renderChart();
}, [data, layout, config, isDark]);
return (
<div
ref={containerRef}
className="w-full min-h-[300px] rounded-lg overflow-hidden chart-fade-in"
style={{ background: 'transparent' }}
/>
);
};
export default PlotlyChart;
|