From 26abbf6f54c4130fef1261dd74d722ec65c57c85 Mon Sep 17 00:00:00 2001 From: William Guss Date: Sun, 13 Oct 2024 06:13:15 -0700 Subject: [PATCH] added error bars --- ell-studio/package-lock.json | 10 ++ ell-studio/package.json | 1 + .../src/components/depgraph/graphUtils.js | 1 + .../evaluations/EvaluationOverview.js | 25 +++- .../evaluations/EvaluationRunsTable.js | 20 ++- .../components/evaluations/MetricDisplay.js | 8 +- .../components/evaluations/MetricGraphGrid.js | 112 ++++++++++------- .../src/components/evaluations/MetricTable.js | 2 +- .../src/components/graphing/ErrorBarPlugin.js | 83 +++++++++++++ .../src/components/graphing/GraphSystem.js | 117 ++++++++++++------ ...mTooltip.js => SharedVerticalIndicator.js} | 26 ++-- ell-studio/src/pages/Evaluation.js | 16 ++- 12 files changed, 313 insertions(+), 108 deletions(-) create mode 100644 ell-studio/src/components/graphing/ErrorBarPlugin.js rename ell-studio/src/components/graphing/{CustomTooltip.js => SharedVerticalIndicator.js} (62%) diff --git a/ell-studio/package-lock.json b/ell-studio/package-lock.json index c2e2c1b8..5f0d335f 100644 --- a/ell-studio/package-lock.json +++ b/ell-studio/package-lock.json @@ -22,6 +22,7 @@ "axios": "^1.6.0", "base64-js": "^1.5.1", "chart.js": "^4.4.4", + "chartjs-chart-error-bars": "^4.4.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "d3-force": "^3.0.0", @@ -7823,6 +7824,15 @@ "pnpm": ">=8" } }, + "node_modules/chartjs-chart-error-bars": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/chartjs-chart-error-bars/-/chartjs-chart-error-bars-4.4.2.tgz", + "integrity": "sha512-rRjfAKjwgoCc6Dt1WE8OWJlBYkNFk68xwVRGAK9J3pAjcUICOgN7E+VVqsCUXb6BBMd3SRFlwtr7u2M5L9AClQ==", + "license": "MIT", + "peerDependencies": { + "chart.js": "^4.1.0" + } + }, "node_modules/check-types": { "version": "11.2.3", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz", diff --git a/ell-studio/package.json b/ell-studio/package.json index da616b79..d7b22c09 100644 --- a/ell-studio/package.json +++ b/ell-studio/package.json @@ -17,6 +17,7 @@ "axios": "^1.6.0", "base64-js": "^1.5.1", "chart.js": "^4.4.4", + "chartjs-chart-error-bars": "^4.4.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "d3-force": "^3.0.0", diff --git a/ell-studio/src/components/depgraph/graphUtils.js b/ell-studio/src/components/depgraph/graphUtils.js index 3a3bc588..c329f3b0 100644 --- a/ell-studio/src/components/depgraph/graphUtils.js +++ b/ell-studio/src/components/depgraph/graphUtils.js @@ -38,6 +38,7 @@ const calculateNodeDimensions = (nodeType, data) => { * @returns {Object} - Contains initial nodes and edges. */ export const getInitialGraph = (lmps, traces, evals) => { + if(!lmps || !traces || !evals) return { initialNodes: [], initialEdges: [] }; const lmpIds = new Set(lmps.map(lmp => lmp.lmp_id)); const evalLmpIds = new Set(); const lmpToEvalMap = new Map(); diff --git a/ell-studio/src/components/evaluations/EvaluationOverview.js b/ell-studio/src/components/evaluations/EvaluationOverview.js index dec8eaf4..813f7318 100644 --- a/ell-studio/src/components/evaluations/EvaluationOverview.js +++ b/ell-studio/src/components/evaluations/EvaluationOverview.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { FiBarChart2, FiClock, FiDatabase, FiTag, FiZap } from 'react-icons/fi'; import { Card, CardContent } from '../common/Card'; import RunSummary from './RunSummary'; @@ -6,7 +6,14 @@ import VersionBadge from '../VersionBadge'; import { getTimeAgo } from '../../utils/lmpUtils'; import MetricGraphGrid from './MetricGraphGrid'; -function EvaluationOverview({ evaluation, groupedRuns }) { +function EvaluationOverview({ evaluation, groupedRuns, onActiveIndexChange }) { + const [activeIndex, setActiveIndex] = useState(null); + + const handleActiveIndexChange = (index) => { + setActiveIndex(index); + onActiveIndexChange(index); + }; + return ( <>
@@ -15,7 +22,19 @@ function EvaluationOverview({ evaluation, groupedRuns }) { - + {evaluation.labelers ? ( + + ) : ( +
+
+
+
+
+ )}
); diff --git a/ell-studio/src/components/evaluations/EvaluationRunsTable.js b/ell-studio/src/components/evaluations/EvaluationRunsTable.js index 7013871c..34266232 100644 --- a/ell-studio/src/components/evaluations/EvaluationRunsTable.js +++ b/ell-studio/src/components/evaluations/EvaluationRunsTable.js @@ -6,7 +6,7 @@ import { Card } from '../common/Card'; import { getTimeAgo } from '../../utils/lmpUtils'; import VersionBadge from '../VersionBadge'; -const EvaluationRunsTable = ({ runs, currentPage, setCurrentPage, pageSize, onSelectRun, currentlySelectedRun }) => { +const EvaluationRunsTable = ({ runs, currentPage, setCurrentPage, pageSize, onSelectRun, currentlySelectedRun, activeIndex }) => { const navigate = useNavigate(); const onClickLMP = (run) => { @@ -14,12 +14,13 @@ const EvaluationRunsTable = ({ runs, currentPage, setCurrentPage, pageSize, onSe }; const runsTableData = useMemo(() => { - return runs.map(run => ({ + return runs.map((run, index) => ({ ...run, id: run.id, name: run.evaluated_lmp.name, version: run.evaluated_lmp.version_number + 1, created_at: new Date(run.end_time), + runIndex: index, // Add this line to keep track of the run's index })); }, [runs]); @@ -99,9 +100,18 @@ const EvaluationRunsTable = ({ runs, currentPage, setCurrentPage, pageSize, onSe data={runsTableData} onRowClick={onSelectRun} initialSortConfig={initialSortConfig} - rowClassName={(item) => - item.id === currentlySelectedRun?.id ? 'bg-blue-600 bg-opacity-30' : '' - } + rowClassName={(item) => { + let className = ''; + if (item.runIndex === activeIndex) { + className += 'bg-blue-600 bg-opacity-30 '; + } else if (activeIndex !== null) { + className += 'opacity-50 '; + } + if (item.id === currentlySelectedRun?.id) { + className += 'border-2 border-blue-600 '; + } + return className.trim(); + }} currentPage={currentPage} onPageChange={setCurrentPage} pageSize={pageSize} diff --git a/ell-studio/src/components/evaluations/MetricDisplay.js b/ell-studio/src/components/evaluations/MetricDisplay.js index 72ba0cc9..931cb619 100644 --- a/ell-studio/src/components/evaluations/MetricDisplay.js +++ b/ell-studio/src/components/evaluations/MetricDisplay.js @@ -29,7 +29,7 @@ const MetricDisplay = ({ currentValue, previousValue, label, showTooltip = true }, [currentValue]); const content = ( -
+
{currentValue.toFixed(2)} @@ -50,7 +50,7 @@ const MetricDisplay = ({ currentValue, previousValue, label, showTooltip = true -
+
{content}
@@ -58,8 +58,8 @@ const MetricDisplay = ({ currentValue, previousValue, label, showTooltip = true

{label}

Current: {currentValue.toFixed(4)}

-

Previous: {previousValue !== undefined ? previousValue.toFixed(4) : 'N/A'}

-

Change: {previousValue !== undefined ? (currentValue - previousValue).toFixed(4) : 'N/A'}

+

Previous: {previousValue !== undefined && previousValue !== null ? previousValue.toFixed(4) : 'N/A'}

+

Change: {previousValue !== undefined && previousValue !== null ? (currentValue - previousValue).toFixed(4) : 'N/A'}

diff --git a/ell-studio/src/components/evaluations/MetricGraphGrid.js b/ell-studio/src/components/evaluations/MetricGraphGrid.js index 0c05612d..d2930aa7 100644 --- a/ell-studio/src/components/evaluations/MetricGraphGrid.js +++ b/ell-studio/src/components/evaluations/MetricGraphGrid.js @@ -5,27 +5,54 @@ import Graph from '../graphing/Graph'; import { GraphProvider } from '../graphing/GraphSystem'; import MetricDisplay from './MetricDisplay'; -const MetricGraphGrid = ({ evaluation, groupedRuns }) => { +const MetricGraphGrid = ({ evaluation, groupedRuns, onActiveIndexChange }) => { const [activeIndex, setActiveIndex] = useState(null); const getHistoricalData = (labeler) => { - return Object.values(groupedRuns).flatMap(runs => - runs.map(run => { + if (!labeler) return { means: [], stdDevs: [], errors: [], confidenceIntervals: [] }; + return Object.values(groupedRuns).reduce((acc, runs) => { + runs.forEach(run => { const summary = run.labeler_summaries.find(s => s.evaluation_labeler_id === labeler.id); - return summary ? summary.data.mean : null; - }).filter(Boolean) - ); + if (summary) { + const { mean, std, min, max } = summary.data; + const count = summary.count; + + // Calculate Standard Error of the Mean (SEM) + const sem = std / Math.sqrt(count); + + // Z-score for 95% confidence + const zScore = 1.96; + + // Margin of Error + let marginOfError = zScore * sem; + + // Bounded Confidence Interval + let lowerBound = Math.max(mean - marginOfError, min); + let upperBound = Math.min(mean + marginOfError, max); + + acc.means.push(mean); + acc.stdDevs.push(std); + acc.errors.push(marginOfError); + acc.confidenceIntervals.push({ low: lowerBound, high: upperBound }); + } + }); + return acc; + }, { means: [], stdDevs: [], errors: [], confidenceIntervals: [] }); }; - const xData = Array.from({ length: getHistoricalData(evaluation.labelers[0]).length }, (_, i) => `Run ${i + 1}`); + const xData = Array.from({ length: getHistoricalData(evaluation.labelers?.[0]).means.length}, (_, i) => `Run ${i + 1}`); const handleHover = useCallback((index) => { setActiveIndex(index); - }, []); + onActiveIndexChange(index); + }, [onActiveIndexChange]); const handleLeave = useCallback(() => { setActiveIndex(null); - }, []); + onActiveIndexChange(null); + }, [onActiveIndexChange]); + + const hasMultipleValues = getHistoricalData(evaluation.labelers[0]).means.length > 1; return ( { plugins: { title: { display: false }, legend: { display: false }, + tooltip: { + enabled: true, + intersect: false, + position: 'average', + // TODO: Make the label custom so when we click it takes us to that run id. + }, } } }} @@ -44,39 +77,15 @@ const MetricGraphGrid = ({ evaluation, groupedRuns }) => { >
{evaluation.labelers.map((labeler) => { - const historicalData = getHistoricalData(labeler); + const { means: historicalData, stdDevs, confidenceIntervals } = getHistoricalData(labeler); if (historicalData.length === 0) return null; - const graphId = `labeler-${labeler.id}`; - const trend = historicalData[historicalData.length - 1] - historicalData[0]; - const trendColor = trend > 0 ? 'rgba(52, 211, 153, 0.8)' : 'rgba(239, 68, 68, 0.8)'; - const fillColor = trend > 0 ? 'rgba(52, 211, 153, 0.2)' : 'rgba(239, 68, 68, 0.2)'; - - const metrics = [ - { - label: labeler.name, - yData: historicalData, - color: trendColor, - config: { - backgroundColor: fillColor, - borderColor: trendColor, - fill: true, - tension: 0.4, - borderWidth: 1, - pointRadius: 3, - } - } - ]; - const currentValue = activeIndex !== null && activeIndex < historicalData.length - ? historicalData[activeIndex] - : historicalData[historicalData.length - 1] || null; - const previousValue = activeIndex !== null && activeIndex > 0 - ? historicalData[activeIndex - 1] - : currentValue; + const currentValue = activeIndex !== null ? historicalData[activeIndex] : historicalData[historicalData.length - 1]; + const previousValue = activeIndex !== null && activeIndex > 0 ? historicalData[activeIndex - 1] : historicalData[historicalData.length - 2]; return ( -
+
{ label={labeler.name} />
-
- -
+ {hasMultipleValues && ( +
+ historicalData[0] ? 'rgba(52, 211, 153, 0.8)' : 'rgba(239, 68, 68, 0.8)', + config: { + backgroundColor: currentValue > historicalData[0] ? 'rgba(52, 211, 153, 0.2)' : 'rgba(239, 68, 68, 0.2)', + borderColor: currentValue > historicalData[0] ? 'rgba(52, 211, 153, 0.8)' : 'rgba(239, 68, 68, 0.8)', + fill: true, + tension: 0.4, + borderWidth: 1, + pointRadius: 3, + } + } + ]} + /> +
+ )} ); })} diff --git a/ell-studio/src/components/evaluations/MetricTable.js b/ell-studio/src/components/evaluations/MetricTable.js index e6505174..6032933f 100644 --- a/ell-studio/src/components/evaluations/MetricTable.js +++ b/ell-studio/src/components/evaluations/MetricTable.js @@ -14,7 +14,7 @@ const MetricTable = ({ summaries, historicalData, isVertical }) => { return ( -
+
diff --git a/ell-studio/src/components/graphing/ErrorBarPlugin.js b/ell-studio/src/components/graphing/ErrorBarPlugin.js new file mode 100644 index 00000000..21fcb838 --- /dev/null +++ b/ell-studio/src/components/graphing/ErrorBarPlugin.js @@ -0,0 +1,83 @@ +import { Chart as ChartJS } from 'chart.js'; + +// Add this new function to fade the color +const fadeColor = (color, opacity) => { + if (color.startsWith('#')) { + // Convert hex to RGB + const r = parseInt(color.slice(1, 3), 16); + const g = parseInt(color.slice(3, 5), 16); + const b = parseInt(color.slice(5, 7), 16); + return `rgba(${r}, ${g}, ${b}, ${opacity})`; + } else if (color.startsWith('rgb')) { + // If it's already RGB or RGBA, just change the opacity + const rgb = color.match(/\d+/g); + return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${opacity})`; + } + // If color format is not recognized, return the original color + return color; +}; + +const drawErrorBar = (ctx, x, y, errorLow, errorHigh, color, width) => { + ctx.save(); + ctx.strokeStyle = fadeColor(color, 0.3); + ctx.lineWidth = width; + + // Draw vertical line + ctx.beginPath(); + ctx.moveTo(x, y - errorHigh); + ctx.lineTo(x, y + errorLow); + ctx.stroke(); + + // Draw horizontal caps + const capLength = 5; + ctx.beginPath(); + ctx.moveTo(x - capLength, y - errorHigh); + ctx.lineTo(x + capLength, y - errorHigh); + ctx.moveTo(x - capLength, y + errorLow); + ctx.lineTo(x + capLength, y + errorLow); + ctx.stroke(); + + ctx.restore(); +}; + +const ErrorBarPlugin = { + id: 'errorBar', + afterDatasetsDraw(chart, args, options) { + const { ctx } = chart; + + if (!options.draw) { + return; + } + + chart.data.datasets.forEach((dataset, datasetIndex) => { + if (dataset.errorBars) { + const meta = chart.getDatasetMeta(datasetIndex); + + dataset.data.forEach((datapoint, index) => { + if (dataset.errorBars[index] !== undefined) { + const { x, y } = meta.data[index].getCenterPoint(); + + let errorLow, errorHigh; + if (typeof dataset.errorBars[index] === 'object') { + // Calculate error bar lengths in pixels + errorLow = Math.abs(chart.scales.y.getPixelForValue(datapoint) - + chart.scales.y.getPixelForValue(dataset.errorBars[index].low)); + errorHigh = Math.abs(chart.scales.y.getPixelForValue(datapoint) - + chart.scales.y.getPixelForValue(dataset.errorBars[index].high)); + } else { + errorLow = errorHigh = Math.abs(chart.scales.y.getPixelForValue(datapoint) - + chart.scales.y.getPixelForValue(datapoint + dataset.errorBars[index])); + } + + drawErrorBar(ctx, x, y, errorLow, errorHigh, dataset.borderColor, dataset.borderWidth || 1); + } + }); + } + }); + } +}; + +export default ErrorBarPlugin; + +// Register the plugin +ChartJS.register(ErrorBarPlugin); diff --git a/ell-studio/src/components/graphing/GraphSystem.js b/ell-studio/src/components/graphing/GraphSystem.js index 02e7ea8e..b8b5e730 100644 --- a/ell-studio/src/components/graphing/GraphSystem.js +++ b/ell-studio/src/components/graphing/GraphSystem.js @@ -1,9 +1,10 @@ import React, { createContext, useContext, useState, useCallback } from 'react'; import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend } from 'chart.js'; import { Line } from 'react-chartjs-2'; -import { CustomTooltip, useTooltip } from './CustomTooltip'; +import { SharedVerticalIndicator, useSharedVerticalIndicator } from './SharedVerticalIndicator'; +import ErrorBarPlugin from './ErrorBarPlugin'; -ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend); +ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend, ErrorBarPlugin); const GraphContext = createContext(); @@ -11,8 +12,8 @@ export const useGraphContext = () => useContext(GraphContext); export const GraphProvider = ({ children, xData, sharedConfig, onHover, onLeave }) => { const [graphs, setGraphs] = useState({}); - const [activeTooltipIndex, setActiveTooltipIndex] = useState(null); - const [sharedTooltipY, setSharedTooltipY] = useState(null); + const [activeIndicatorIndex, setActiveIndicatorIndex] = useState(null); + const [sharedIndicatorY, setSharedIndicatorY] = useState(null); const addGraph = useCallback((graphId) => { setGraphs(prevGraphs => { @@ -53,17 +54,17 @@ export const GraphProvider = ({ children, xData, sharedConfig, onHover, onLeave })); }, []); - const setActiveTooltip = useCallback((index, y) => { - setActiveTooltipIndex(index); - setSharedTooltipY(y); + const setActiveIndicator = useCallback((index, y) => { + setActiveIndicatorIndex(index); + setSharedIndicatorY(y); if (onHover) { onHover(index); } }, [onHover]); - const clearActiveTooltip = useCallback(() => { - setActiveTooltipIndex(null); - setSharedTooltipY(null); + const clearActiveIndicator = useCallback(() => { + setActiveIndicatorIndex(null); + setSharedIndicatorY(null); if (onLeave) { onLeave(); } @@ -78,10 +79,10 @@ export const GraphProvider = ({ children, xData, sharedConfig, onHover, onLeave addMetric, removeMetric, sharedConfig, - activeTooltipIndex, - sharedTooltipY, - setActiveTooltip, - clearActiveTooltip + activeIndicatorIndex, + sharedIndicatorY, + setActiveIndicator, + clearActiveIndicator }}> {children} @@ -93,14 +94,14 @@ export const GraphRenderer = ({ graphId }) => { xData, graphs, sharedConfig, - activeTooltipIndex, - sharedTooltipY, - setActiveTooltip, - clearActiveTooltip + activeIndicatorIndex, + sharedIndicatorY, + setActiveIndicator, + clearActiveIndicator } = useGraphContext(); const graph = graphs[graphId]; const chartRef = React.useRef(null); - const tooltipState = useTooltip(chartRef, activeTooltipIndex, sharedTooltipY, clearActiveTooltip); + const indicatorState = useSharedVerticalIndicator(chartRef, activeIndicatorIndex, sharedIndicatorY, clearActiveIndicator); if (!graph || !graph.metrics || graph.metrics.length === 0) { return
Loading graph...
; @@ -113,55 +114,98 @@ export const GraphRenderer = ({ graphId }) => { data: metric.yData, borderColor: metric.color, backgroundColor: metric.color, + errorBars: metric.errorBars, ...metric.config, })), }; + // Check if there are any non-zero error bars + const hasNonZeroErrorBars = data.datasets.some(dataset => + dataset.errorBars && dataset.errorBars.some(error => error > 0 || (error.low - error.high > 0)) + ); + + let yAxisScale = {}; + if (hasNonZeroErrorBars || true) { + // Calculate min and max values including error bars + const minMaxValues = data.datasets.reduce((acc, dataset) => { + dataset.data.forEach((value, index) => { + const errorBar = dataset.errorBars ? dataset.errorBars[index] : 0; + if (typeof errorBar === 'number') { + acc.min = Math.min(acc.min, value - errorBar); + acc.max = Math.max(acc.max, value + errorBar); + } else if (errorBar && typeof errorBar === 'object') { + console.log('errorBar', errorBar); + acc.min = Math.min(acc.min, errorBar.low); + acc.max = Math.max(acc.max, errorBar.high); + } else { + acc.min = Math.min(acc.min, value); + acc.max = Math.max(acc.max, value); + } + }); + return acc; + }, { min: Infinity, max: -Infinity }); + + // Add some padding to the min and max values + const yAxisPadding = (minMaxValues.max - minMaxValues.min) * 0.1; + + yAxisScale = { + y: { + beginAtZero: false, + min: Math.max(0, minMaxValues.min - yAxisPadding), + max: minMaxValues.max + yAxisPadding, + } + }; + } + const options = { responsive: true, maintainAspectRatio: false, - plugins: { - legend: { position: 'top' }, - title: { display: true, text: sharedConfig.title }, - tooltip: { enabled: false }, - }, hover: { mode: 'index', intersect: false }, + ...sharedConfig.options, onHover: (event, elements, chart) => { if (elements && elements.length > 0) { const rect = chart.canvas.getBoundingClientRect(); const y = rect.bottom - rect.top + 40; - setActiveTooltip(elements[0].index, y); + setActiveIndicator(elements[0].index, y); } else { - clearActiveTooltip(); + clearActiveIndicator(); } }, - ...sharedConfig.options, + scales: { + ...sharedConfig.options.scales, + ...yAxisScale, + }, + plugins: { + errorBar: { + draw: true, + }, + ...sharedConfig.options.plugins, + }, }; return (
-
); }; -export const MetricAdder = ({ graphId, label, yData, color, config }) => { +export const MetricAdder = ({ graphId, label, yData, color, config, errorBars }) => { const { addMetric, removeMetric } = useGraphContext(); React.useEffect(() => { const metricId = Date.now(); - console.log(`Adding metric to graph ${graphId}:`, { id: metricId, label, yData, color, config }); - addMetric(graphId, { id: metricId, label, yData, color, config }); + addMetric(graphId, { id: metricId, label, yData, color, config, errorBars }); return () => removeMetric(graphId, metricId); - }, [graphId, label, yData, color, config, addMetric, removeMetric]); + }, [graphId, label, yData, color, config, errorBars, addMetric, removeMetric]); return null; }; @@ -170,7 +214,6 @@ export const useGraph = (graphId) => { const { addGraph, removeGraph } = useGraphContext(); React.useEffect(() => { - console.log(`Initializing graph ${graphId}`); addGraph(graphId); return () => removeGraph(graphId); }, [graphId, addGraph, removeGraph]); diff --git a/ell-studio/src/components/graphing/CustomTooltip.js b/ell-studio/src/components/graphing/SharedVerticalIndicator.js similarity index 62% rename from ell-studio/src/components/graphing/CustomTooltip.js rename to ell-studio/src/components/graphing/SharedVerticalIndicator.js index 5c180f17..38cafd37 100644 --- a/ell-studio/src/components/graphing/CustomTooltip.js +++ b/ell-studio/src/components/graphing/SharedVerticalIndicator.js @@ -13,7 +13,7 @@ const formatNumber = (value) => { return value; }; -export const CustomTooltip = ({ visible, position, labels, datasets, activeIndex, chartHeight }) => { +export const SharedVerticalIndicator = ({ visible, position, labels, datasets, activeIndex, chartHeight }) => { const [animatedPosition, setAnimatedPosition] = useState(position); useEffect(() => { @@ -46,34 +46,34 @@ export const CustomTooltip = ({ visible, position, labels, datasets, activeIndex ); }; -export const useTooltip = (chartRef, activeTooltipIndex, sharedTooltipY, clearActiveTooltip) => { - const [tooltipState, setTooltipState] = useState({ visible: false, position: { x: 0, y: 0 } }); +export const useSharedVerticalIndicator = (chartRef, activeIndicatorIndex, sharedIndicatorY, clearActiveIndicator) => { + const [indicatorLocation, setIndicatorLocation] = useState({ visible: false, position: { x: 0, y: 0 } }); const [chartHeight, setChartHeight] = useState(0); useEffect(() => { const chart = chartRef.current; if (!chart) return; - const updateTooltip = () => { + const updateSharedVerticalIndicator = () => { if (!chart || !chart.canvas) return; - if (activeTooltipIndex !== null && activeTooltipIndex >= 0 && activeTooltipIndex < chart.data.labels.length) { + if (activeIndicatorIndex !== null && activeIndicatorIndex >= 0 && activeIndicatorIndex < chart.data.labels.length) { const meta = chart.getDatasetMeta(0); - if (!meta || !meta.data || activeTooltipIndex >= meta.data.length) return; + if (!meta || !meta.data || activeIndicatorIndex >= meta.data.length) return; - const activeElement = meta.data[activeTooltipIndex]; + const activeElement = meta.data[activeIndicatorIndex]; - setTooltipState({ visible: true, position: { x: activeElement.x, y: sharedTooltipY } }); + setIndicatorLocation({ visible: true, position: { x: activeElement.x, y: sharedIndicatorY } }); setChartHeight(chart.height); } else { - setTooltipState(prev => ({ ...prev, visible: false })); + setIndicatorLocation(prev => ({ ...prev, visible: false })); } }; - updateTooltip(); + updateSharedVerticalIndicator(); const handleMouseLeave = () => { - clearActiveTooltip(); + clearActiveIndicator(); }; chart.canvas.addEventListener('mouseleave', handleMouseLeave); @@ -83,7 +83,7 @@ export const useTooltip = (chartRef, activeTooltipIndex, sharedTooltipY, clearAc chart.canvas.removeEventListener('mouseleave', handleMouseLeave); } }; - }, [activeTooltipIndex, sharedTooltipY, clearActiveTooltip]); + }, [activeIndicatorIndex, sharedIndicatorY, clearActiveIndicator, chartRef]); - return { ...tooltipState, chartHeight }; + return { ...indicatorLocation, chartHeight }; }; diff --git a/ell-studio/src/pages/Evaluation.js b/ell-studio/src/pages/Evaluation.js index a5a91a4c..da55448a 100644 --- a/ell-studio/src/pages/Evaluation.js +++ b/ell-studio/src/pages/Evaluation.js @@ -25,6 +25,7 @@ function Evaluation() { const [selectedRun, setSelectedRun] = useState(null); const [currentPage, setCurrentPage] = useState(0); const pageSize = 10; + const [activeIndex, setActiveIndex] = useState(null); const { data: evaluation, isLoading: isLoadingEvaluation } = useEvaluation(id); @@ -57,7 +58,13 @@ function Evaluation() { }); }; - if (isLoadingEvaluation) { + // TODO: Move hte graph state all the way out so we don't do callbacks and get do bidirectional state propagation + const handleActiveIndexChange = (index) => { + setActiveIndex(index); + console.log('Active index in Evaluation:', index); + }; + + if (isLoadingEvaluation || !evaluation?.labelers?.length) { return
Loading evaluation...
; } @@ -88,7 +95,11 @@ function Evaluation() {
- +
@@ -116,6 +127,7 @@ function Evaluation() { pageSize={pageSize} onSelectRun={setSelectedRun} currentlySelectedRun={selectedRun} + activeIndex={activeIndex} /> )} {activeTab === 'metrics' && (