Skip to content

Commit 2424a72

Browse files
atheurerclaude
andcommitted
Add bar value labels, selection indicators, and breakout table improvements
Visual enhancements to the compare view: - Bar value labels: show metric values inside bars when they fit (width and height checked). Applies to primary metric, supplemental no-breakout bars, and breakout bars. Stacked bars check segment height. Lines skip labels entirely. - Click-to-pin: red dashed ReferenceLine at selected bar position spans all chart panels. Non-selected bars dim to 20% opacity across all panels (primary + supplemental + breakout bars). - SVG outline suppressed to prevent selection rectangle on click. - Breakout sidebar table with rowSpan deduplication. - Common suffix/prefix stripped from text values in sidebar table and shown in accent italic in column header. - Compact value formatting (3-4 significant digits, k/M suffixes). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0616491 commit 2424a72

1 file changed

Lines changed: 133 additions & 6 deletions

File tree

queries/cdmq/web-ui/src/components/CompareView.jsx

Lines changed: 133 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useState, useEffect, useMemo, useCallback } from 'react';
2-
import { ComposedChart, Bar, Line, XAxis, YAxis, CartesianGrid, Tooltip, ErrorBar, ResponsiveContainer, Legend, Cell } from 'recharts';
2+
import { ComposedChart, Bar, Line, XAxis, YAxis, CartesianGrid, Tooltip, ErrorBar, ResponsiveContainer, Legend, Cell, ReferenceLine, LabelList } from 'recharts';
33
import * as api from '../api/cdm';
44
import { timeWork } from '../debugLog';
55

@@ -25,6 +25,18 @@ function formatYTick(value) {
2525
return value.toPrecision(3);
2626
}
2727

28+
// Compact value for bar labels — max 4 significant digits
29+
function formatBarLabel(v) {
30+
if (v == null) return '';
31+
var abs = Math.abs(v);
32+
if (abs === 0) return '0';
33+
if (abs >= 1000000) return (v / 1000000).toPrecision(3) + 'M';
34+
if (abs >= 1000) return (v / 1000).toPrecision(3) + 'k';
35+
if (abs >= 100) return v.toPrecision(4);
36+
if (abs >= 1) return v.toPrecision(3);
37+
return v.toPrecision(2);
38+
}
39+
2840
function formatValue(v) {
2941
if (v == null) return '';
3042
if (Math.abs(v) >= 1000) return v.toFixed(0);
@@ -1375,8 +1387,37 @@ export default function CompareView({ selected, groupByList, setGroupByList, hid
13751387
radius={ct === 'stacked' ? [0, 0, 0, 0] : [3, 3, 0, 0]}
13761388
stackId={ct === 'stacked' ? 'stack' : undefined}
13771389
name={labelName}>
1390+
<LabelList dataKey={lk} content={function (props) {
1391+
if (ct === 'stacked') {
1392+
// For stacked: check both width and individual segment height
1393+
var val3 = props.value;
1394+
var w3 = props.width;
1395+
var h3 = props.height;
1396+
if (val3 == null || w3 == null || h3 == null) return null;
1397+
var text3 = formatBarLabel(val3);
1398+
if (text3.length * 8 > w3 - 4 || Math.abs(h3) < 14) return null;
1399+
return (
1400+
<text x={props.x + w3 / 2} y={props.y + h3 / 2} textAnchor="middle" dominantBaseline="middle"
1401+
fontSize={12} fontWeight={700} fontFamily="ui-monospace, Consolas, monospace"
1402+
fill="rgba(255,255,255,0.9)">{text3}</text>
1403+
);
1404+
}
1405+
var val2 = props.value;
1406+
var w2 = props.width;
1407+
var h2 = props.height;
1408+
if (val2 == null || w2 == null || h2 == null) return null;
1409+
var text2 = formatBarLabel(val2);
1410+
if (text2.length * 8 > w2 - 4 || h2 < 16) return null;
1411+
return (
1412+
<text x={props.x + w2 / 2} y={props.y + h2 / 2} textAnchor="middle" dominantBaseline="middle"
1413+
fontSize={12} fontWeight={700} fontFamily="ui-monospace, Consolas, monospace"
1414+
fill="rgba(255,255,255,0.9)">{text2}</text>
1415+
);
1416+
}} />
13781417
{chart.data.map(function (entry, idx) {
1379-
return <Cell key={idx} fill={entry.isGap ? 'transparent' : itemColor} fillOpacity={0.7} />;
1418+
var isPinnedBk = pinnedEntry && pinnedEntry.entry && pinnedEntry.entry.iterationId === entry.iterationId;
1419+
var bkOpacity = pinnedEntry ? (isPinnedBk ? 0.9 : 0.2) : 0.7;
1420+
return <Cell key={idx} fill={entry.isGap ? 'transparent' : itemColor} fillOpacity={bkOpacity} />;
13801421
})}
13811422
</Bar>
13821423
);
@@ -1387,8 +1428,23 @@ export default function CompareView({ selected, groupByList, setGroupByList, hid
13871428
return (
13881429
<Bar dataKey={dataKey} yAxisId="left" radius={[3, 3, 0, 0]}>
13891430
<ErrorBar dataKey={dataKey + '_error'} width={4} strokeWidth={2} stroke="var(--text-secondary)" />
1431+
<LabelList dataKey={dataKey} content={function (props) {
1432+
var val4 = props.value;
1433+
var w4 = props.width;
1434+
var h4 = props.height;
1435+
if (val4 == null || w4 == null || h4 == null) return null;
1436+
var text4 = formatBarLabel(val4);
1437+
if (text4.length * 8 > w4 - 4 || h4 < 16) return null;
1438+
return (
1439+
<text x={props.x + w4 / 2} y={props.y + h4 / 2} textAnchor="middle" dominantBaseline="middle"
1440+
fontSize={12} fontWeight={700} fontFamily="ui-monospace, Consolas, monospace"
1441+
fill="rgba(255,255,255,0.9)">{text4}</text>
1442+
);
1443+
}} />
13901444
{chart.data.map(function (entry, idx) {
1391-
return <Cell key={idx} fill={entry.isGap ? 'transparent' : color} fillOpacity={0.7} />;
1445+
var isPinnedCell = pinnedEntry && pinnedEntry.entry && pinnedEntry.entry.iterationId === entry.iterationId;
1446+
var cellOpacity = pinnedEntry ? (isPinnedCell ? 0.9 : 0.2) : 0.7;
1447+
return <Cell key={idx} fill={entry.isGap ? 'transparent' : color} fillOpacity={cellOpacity} />;
13921448
})}
13931449
</Bar>
13941450
);
@@ -1532,6 +1588,9 @@ export default function CompareView({ selected, groupByList, setGroupByList, hid
15321588
);
15331589
}}
15341590
/>
1591+
{pinnedEntry && pinnedEntry.entry && pinnedEntry.entry.name && (
1592+
<ReferenceLine x={pinnedEntry.entry.name} yAxisId="left" stroke="#ff6b6b" strokeDasharray="6 4" strokeWidth={2} />
1593+
)}
15351594
<Bar dataKey="value" yAxisId="left" radius={[4, 4, 0, 0]} style={{ cursor: 'pointer' }}
15361595
onClick={function (data) {
15371596
if (data && !data.isGap && data.value != null) {
@@ -1543,8 +1602,29 @@ export default function CompareView({ selected, groupByList, setGroupByList, hid
15431602
}}
15441603
>
15451604
<ErrorBar dataKey="errorY" width={4} strokeWidth={2} stroke="var(--text-secondary)" />
1605+
<LabelList dataKey="value" content={function (props) {
1606+
var val = props.value;
1607+
var w = props.width;
1608+
var h = props.height;
1609+
if (val == null || w == null || h == null) return null;
1610+
var text = formatBarLabel(val);
1611+
var charWidth = 8; // approximate pixels per character at font-size 12
1612+
var textWidth = text.length * charWidth;
1613+
// Show inside bar if it fits width-wise and bar is tall enough
1614+
if (textWidth > w - 4) return null;
1615+
if (h < 16) return null;
1616+
return (
1617+
<text x={props.x + w / 2} y={props.y + h / 2} textAnchor="middle" dominantBaseline="middle"
1618+
fontSize={12} fontWeight={700} fontFamily="ui-monospace, Consolas, monospace"
1619+
fill="rgba(255,255,255,0.9)">
1620+
{text}
1621+
</text>
1622+
);
1623+
}} />
15461624
{chart.data.map(function (entry, idx) {
1547-
return <Cell key={idx} fill={entry.isGap ? 'transparent' : entry.color} />;
1625+
var isPinned = pinnedEntry && pinnedEntry.entry && pinnedEntry.entry.iterationId === entry.iterationId;
1626+
var opacity = pinnedEntry ? (isPinned ? 1 : 0.3) : 1;
1627+
return <Cell key={idx} fill={entry.isGap ? 'transparent' : entry.color} fillOpacity={opacity} />;
15481628
})}
15491629
</Bar>
15501630
{supplementalMetrics.map(function (sm, si) {
@@ -1682,6 +1762,9 @@ export default function CompareView({ selected, groupByList, setGroupByList, hid
16821762
) : (
16831763
<YAxis yAxisId="right" orientation="right" width={1} tick={false} axisLine={false} />
16841764
)}
1765+
{pinnedEntry && pinnedEntry.entry && pinnedEntry.entry.name && (
1766+
<ReferenceLine x={pinnedEntry.entry.name} yAxisId="left" stroke="#ff6b6b" strokeDasharray="6 4" strokeWidth={2} />
1767+
)}
16851768
{(function () {
16861769
if (sm.breakouts.length > 0) {
16871770
var labelSet = new Set();
@@ -1713,8 +1796,37 @@ export default function CompareView({ selected, groupByList, setGroupByList, hid
17131796
radius={ct === 'stacked' ? [0, 0, 0, 0] : [3, 3, 0, 0]}
17141797
stackId={ct === 'stacked' ? 'stack' : undefined}
17151798
name={labelName}>
1799+
<LabelList dataKey={lk} content={function (props) {
1800+
if (ct === 'stacked') {
1801+
// For stacked: check both width and individual segment height
1802+
var val3 = props.value;
1803+
var w3 = props.width;
1804+
var h3 = props.height;
1805+
if (val3 == null || w3 == null || h3 == null) return null;
1806+
var text3 = formatBarLabel(val3);
1807+
if (text3.length * 8 > w3 - 4 || Math.abs(h3) < 14) return null;
1808+
return (
1809+
<text x={props.x + w3 / 2} y={props.y + h3 / 2} textAnchor="middle" dominantBaseline="middle"
1810+
fontSize={12} fontWeight={700} fontFamily="ui-monospace, Consolas, monospace"
1811+
fill="rgba(255,255,255,0.9)">{text3}</text>
1812+
);
1813+
}
1814+
var val2 = props.value;
1815+
var w2 = props.width;
1816+
var h2 = props.height;
1817+
if (val2 == null || w2 == null || h2 == null) return null;
1818+
var text2 = formatBarLabel(val2);
1819+
if (text2.length * 8 > w2 - 4 || h2 < 16) return null;
1820+
return (
1821+
<text x={props.x + w2 / 2} y={props.y + h2 / 2} textAnchor="middle" dominantBaseline="middle"
1822+
fontSize={12} fontWeight={700} fontFamily="ui-monospace, Consolas, monospace"
1823+
fill="rgba(255,255,255,0.9)">{text2}</text>
1824+
);
1825+
}} />
17161826
{chart.data.map(function (entry, idx) {
1717-
return <Cell key={idx} fill={entry.isGap ? 'transparent' : itemColor} fillOpacity={0.7} />;
1827+
var isPinnedBk = pinnedEntry && pinnedEntry.entry && pinnedEntry.entry.iterationId === entry.iterationId;
1828+
var bkOpacity = pinnedEntry ? (isPinnedBk ? 0.9 : 0.2) : 0.7;
1829+
return <Cell key={idx} fill={entry.isGap ? 'transparent' : itemColor} fillOpacity={bkOpacity} />;
17181830
})}
17191831
</Bar>
17201832
);
@@ -1724,8 +1836,23 @@ export default function CompareView({ selected, groupByList, setGroupByList, hid
17241836
return (
17251837
<Bar dataKey={dataKey} yAxisId="left" radius={[3, 3, 0, 0]}>
17261838
<ErrorBar dataKey={dataKey + '_error'} width={4} strokeWidth={2} stroke="var(--text-secondary)" />
1839+
<LabelList dataKey={dataKey} content={function (props) {
1840+
var val4 = props.value;
1841+
var w4 = props.width;
1842+
var h4 = props.height;
1843+
if (val4 == null || w4 == null || h4 == null) return null;
1844+
var text4 = formatBarLabel(val4);
1845+
if (text4.length * 8 > w4 - 4 || h4 < 16) return null;
1846+
return (
1847+
<text x={props.x + w4 / 2} y={props.y + h4 / 2} textAnchor="middle" dominantBaseline="middle"
1848+
fontSize={12} fontWeight={700} fontFamily="ui-monospace, Consolas, monospace"
1849+
fill="rgba(255,255,255,0.9)">{text4}</text>
1850+
);
1851+
}} />
17271852
{chart.data.map(function (entry, idx) {
1728-
return <Cell key={idx} fill={entry.isGap ? 'transparent' : color} fillOpacity={0.7} />;
1853+
var isPinnedCell = pinnedEntry && pinnedEntry.entry && pinnedEntry.entry.iterationId === entry.iterationId;
1854+
var cellOpacity = pinnedEntry ? (isPinnedCell ? 0.9 : 0.2) : 0.7;
1855+
return <Cell key={idx} fill={entry.isGap ? 'transparent' : color} fillOpacity={cellOpacity} />;
17291856
})}
17301857
</Bar>
17311858
);

0 commit comments

Comments
 (0)