diff --git a/frontend/src/metabase/static-viz/components/Legend/types.ts b/frontend/src/metabase/static-viz/components/Legend/types.ts index 2e8344955da2cef8e3c25669656ab1f58d1ce8a2..225c48b134768f9163fbb44acb9e3efdb0c4b063 100644 --- a/frontend/src/metabase/static-viz/components/Legend/types.ts +++ b/frontend/src/metabase/static-viz/components/Legend/types.ts @@ -1,7 +1,4 @@ -export type LegendItem = { - name: string; - color: string; -}; +import type { LegendItem } from "metabase/visualizations/echarts/cartesian/model/types"; export type PositionedLegendItem = LegendItem & { left: number; diff --git a/frontend/src/metabase/static-viz/components/Legend/utils.ts b/frontend/src/metabase/static-viz/components/Legend/utils.ts index 9aa4dbc6b9d9f6b306e20a22910b192b2c0ed941..be59194e31c45c2ea0cfb2bd2e9606c9ae262bf1 100644 --- a/frontend/src/metabase/static-viz/components/Legend/utils.ts +++ b/frontend/src/metabase/static-viz/components/Legend/utils.ts @@ -1,4 +1,5 @@ import { measureTextWidth, truncateText } from "metabase/static-viz/lib/text"; +import type { LegendItem } from "metabase/visualizations/echarts/cartesian/model/types"; import { DEFAULT_LEGEND_FONT_SIZE, @@ -8,7 +9,7 @@ import { LEGEND_ITEM_MARGIN_RIGHT, DEFAULT_LEGEND_LINE_HEIGHT, } from "./constants"; -import type { LegendItem, PositionedLegendItem } from "./types"; +import type { PositionedLegendItem } from "./types"; const calculateItemWidth = ( item: LegendItem, @@ -89,6 +90,7 @@ export const calculateLegendRows = ({ currentRowX = horizontalPadding + itemWidth + LEGEND_ITEM_MARGIN_RIGHT; } else { currentRow.push({ + key: item.key, color: item.color, name: truncateText( item.name, diff --git a/frontend/src/metabase/static-viz/components/RowChart/RowChart.tsx b/frontend/src/metabase/static-viz/components/RowChart/RowChart.tsx index 5e455b0c5323dd752025adfdb9c7d4ab4f5786ad..d8a70c5df1da4460267532445be114772c6b1193 100644 --- a/frontend/src/metabase/static-viz/components/RowChart/RowChart.tsx +++ b/frontend/src/metabase/static-viz/components/RowChart/RowChart.tsx @@ -92,6 +92,7 @@ const StaticRowChart = ({ data, settings, getColor }: StaticRowChartProps) => { const legend = calculateLegendRows({ items: series.map(series => ({ + key: series.seriesKey, name: series.seriesName, color: seriesColors[series.seriesKey], })), diff --git a/frontend/src/metabase/visualizations/components/legend/Legend.jsx b/frontend/src/metabase/visualizations/components/legend/Legend.jsx index c780fddf9725725db9449988ce363790ca558181..f9e21efdf92211604b7340a84a1b0410b9511b25 100644 --- a/frontend/src/metabase/visualizations/components/legend/Legend.jsx +++ b/frontend/src/metabase/visualizations/components/legend/Legend.jsx @@ -19,8 +19,7 @@ const POPOVER_OFFSET = POPOVER_BORDER + POPOVER_PADDING; const propTypes = { className: PropTypes.string, - labels: PropTypes.array.isRequired, - colors: PropTypes.array.isRequired, + items: PropTypes.array.isRequired, hovered: PropTypes.object, visibleIndex: PropTypes.number, visibleLength: PropTypes.number, @@ -36,11 +35,10 @@ const alwaysTrue = () => true; const Legend = ({ className, - labels: originalLabels, - colors: originalColors, + items: originalItems, hovered, visibleIndex = 0, - visibleLength = originalLabels.length, + visibleLength = originalItems.length, isVertical, onHoverChange, onSelectSeries, @@ -62,16 +60,11 @@ const Legend = ({ setMaxWidth(0); }, []); - const labels = isReversed - ? _.clone(originalLabels).reverse() - : originalLabels; - const colors = isReversed - ? _.clone(originalColors).reverse() - : originalColors; + const items = isReversed ? _.clone(originalItems).reverse() : originalItems; const overflowIndex = visibleIndex + visibleLength; - const visibleLabels = labels.slice(visibleIndex, overflowIndex); - const overflowLength = labels.length - overflowIndex; + const visibleItems = items.slice(visibleIndex, overflowIndex); + const overflowLength = items.length - overflowIndex; return ( <LegendRoot @@ -79,18 +72,17 @@ const Legend = ({ aria-label={t`Legend`} isVertical={isVertical} > - {visibleLabels.map((label, index) => { + {visibleItems.map((item, index) => { const localIndex = index + visibleIndex; const itemIndex = isReversed - ? labels.length - 1 - localIndex + ? items.length - 1 - localIndex : localIndex; return ( <LegendItem - key={itemIndex} - label={label} + key={item.key} + item={item} index={itemIndex} - color={colors[localIndex % colors.length]} isMuted={hovered && itemIndex !== hovered.index} isVertical={isVertical} isReversed={isReversed} @@ -120,8 +112,7 @@ const Legend = ({ > <LegendPopoverContainer style={{ maxWidth }}> <Legend - labels={originalLabels} - colors={originalColors} + items={originalItems} hovered={hovered} visibleIndex={overflowIndex} visibleLength={overflowLength} diff --git a/frontend/src/metabase/visualizations/components/legend/LegendItem.jsx b/frontend/src/metabase/visualizations/components/legend/LegendItem.jsx index 8680f0f9f3ccaa57507d61c77b2a69d3958596f3..4b87bc6bd189940ff05fc003b8e0e969b600b063 100644 --- a/frontend/src/metabase/visualizations/components/legend/LegendItem.jsx +++ b/frontend/src/metabase/visualizations/components/legend/LegendItem.jsx @@ -15,9 +15,8 @@ import { } from "./LegendItem.styled"; const propTypes = { - label: PropTypes.string, + item: PropTypes.object, index: PropTypes.number, - color: PropTypes.string, isMuted: PropTypes.bool, isVertical: PropTypes.bool, isReversed: PropTypes.bool, @@ -27,9 +26,8 @@ const propTypes = { }; const LegendItem = ({ - label, + item, index, - color, isMuted, isVertical, isReversed, @@ -61,7 +59,7 @@ const LegendItem = ({ onMouseEnter={onHoverChange && handleItemMouseEnter} onMouseLeave={onHoverChange && handleItemMouseLeave} > - <LegendItemDot color={color} /> + <LegendItemDot color={item.color} /> <LegendItemTitle className={cx( DashboardS.fullscreenNormalText, @@ -69,7 +67,7 @@ const LegendItem = ({ EmbedFrameS.fullscreenNightText, )} > - <Ellipsified>{label}</Ellipsified> + <Ellipsified>{item.name}</Ellipsified> </LegendItemTitle> </LegendItemLabel> {onRemoveSeries && <LegendItemRemoveIcon onClick={handleRemoveClick} />} diff --git a/frontend/src/metabase/visualizations/components/legend/LegendLayout.jsx b/frontend/src/metabase/visualizations/components/legend/LegendLayout.jsx index dcdc8227804f5370c1bf77f471fe4ff849edaa72..6a7056a026fb384c68312d06057937900a73d57e 100644 --- a/frontend/src/metabase/visualizations/components/legend/LegendLayout.jsx +++ b/frontend/src/metabase/visualizations/components/legend/LegendLayout.jsx @@ -19,8 +19,7 @@ const MIN_LEGEND_WIDTH = 400; const propTypes = { className: PropTypes.string, - labels: PropTypes.array.isRequired, - colors: PropTypes.array.isRequired, + items: PropTypes.array.isRequired, hovered: PropTypes.object, width: PropTypes.number, height: PropTypes.number, @@ -38,8 +37,7 @@ const propTypes = { const LegendLayout = ({ className, - labels, - colors, + items, hovered, width = 0, height = 0, @@ -59,12 +57,12 @@ const LegendLayout = ({ const maxXItems = Math.floor(width / MIN_ITEM_WIDTH); const maxYItems = Math.floor(height / itemHeight); const maxYLabels = Math.max(maxYItems - 1, 0); - const minYLabels = labels.length > maxYItems ? maxYLabels : labels.length; + const minYLabels = items.length > maxYItems ? maxYLabels : items.length; const isNarrow = width < MIN_LEGEND_WIDTH; - const isVertical = maxXItems < labels.length; + const isVertical = maxXItems < items.length; const isVisible = hasLegend && !(isVertical && isNarrow); - const visibleLength = isVertical ? minYLabels : labels.length; + const visibleLength = isVertical ? minYLabels : items.length; return ( <LegendLayoutRoot className={className} isVertical={isVertical}> @@ -74,8 +72,7 @@ const LegendLayout = ({ isQueryBuilder={isQueryBuilder} > <Legend - labels={labels} - colors={colors} + items={items} hovered={hovered} visibleLength={visibleLength} isVertical={isVertical} diff --git a/frontend/src/metabase/visualizations/echarts/cartesian/model/legend.ts b/frontend/src/metabase/visualizations/echarts/cartesian/model/legend.ts index 4f2752e478ed5798fe5661ec0ced98666ccc43cd..452ac74c278a487a5923fb9c4ec49386b50ab6e2 100644 --- a/frontend/src/metabase/visualizations/echarts/cartesian/model/legend.ts +++ b/frontend/src/metabase/visualizations/echarts/cartesian/model/legend.ts @@ -1,10 +1,10 @@ import { isBreakoutSeries } from "./guards"; -import type { SeriesModel } from "./types"; +import type { LegendItem, SeriesModel } from "./types"; export const getLegendItems = ( seriesModels: SeriesModel[], showAllLegendItems: boolean = false, -) => { +): LegendItem[] => { if ( seriesModels.length === 1 && !isBreakoutSeries(seriesModels[0]) && @@ -14,6 +14,7 @@ export const getLegendItems = ( } return seriesModels.map(seriesModel => ({ + key: seriesModel.dataKey, name: seriesModel.name, color: seriesModel.color, })); diff --git a/frontend/src/metabase/visualizations/echarts/cartesian/model/legend.unit.spec.ts b/frontend/src/metabase/visualizations/echarts/cartesian/model/legend.unit.spec.ts index 03025a47463c335e47e786f9f7db9c3ad1a69e39..1fceea1e2de9d056752c933ee8c58d25a9f0309a 100644 --- a/frontend/src/metabase/visualizations/echarts/cartesian/model/legend.unit.spec.ts +++ b/frontend/src/metabase/visualizations/echarts/cartesian/model/legend.unit.spec.ts @@ -17,8 +17,8 @@ describe("getLegendItems", () => { createMockSeriesModel({ name: "Series 2", color: "blue" }), ]); expect(legendItems).toStrictEqual([ - { name: "Series 1", color: "red" }, - { name: "Series 2", color: "blue" }, + { name: "Series 1", color: "red", key: "dataKey" }, + { name: "Series 2", color: "blue", key: "dataKey" }, ]); }); @@ -26,6 +26,8 @@ describe("getLegendItems", () => { const legendItems = getLegendItems([ createMockBreakoutSeriesModel({ name: "breakout series" }), ]); - expect(legendItems).toEqual([{ name: "breakout series", color: "red" }]); + expect(legendItems).toEqual([ + { name: "breakout series", color: "red", key: "dataKey" }, + ]); }); }); diff --git a/frontend/src/metabase/visualizations/echarts/cartesian/model/types.ts b/frontend/src/metabase/visualizations/echarts/cartesian/model/types.ts index f46724eb485a184330cb609eecf8dc2c53af6325..46bd03f53b1c70fb0ea4aaa9fc79a583a9ac7c55 100644 --- a/frontend/src/metabase/visualizations/echarts/cartesian/model/types.ts +++ b/frontend/src/metabase/visualizations/echarts/cartesian/model/types.ts @@ -205,3 +205,9 @@ export type CartesianChartModel = BaseCartesianChartModel & { }; export type ShowWarning = (warning: string) => void; + +export type LegendItem = { + key: string; + name: string; + color: string; +}; diff --git a/frontend/src/metabase/visualizations/visualizations/CartesianChart/CartesianChart.tsx b/frontend/src/metabase/visualizations/visualizations/CartesianChart/CartesianChart.tsx index 9756feb1415199017f0da89ecb9ed8d684f1da4d..c5d1392d6e2da2f371697789ec698a68268e7197 100644 --- a/frontend/src/metabase/visualizations/visualizations/CartesianChart/CartesianChart.tsx +++ b/frontend/src/metabase/visualizations/visualizations/CartesianChart/CartesianChart.tsx @@ -106,8 +106,7 @@ function _CartesianChart(props: VisualizationProps) { <CartesianChartLegendLayout isReversed={settings["legend.is_reversed"]} hasLegend={hasLegend} - labels={legendItems.map(item => item.name)} - colors={legendItems.map(item => item.color)} + items={legendItems} actionButtons={!hasTitle ? actionButtons : undefined} hovered={hovered} isFullscreen={isFullscreen} diff --git a/frontend/src/metabase/visualizations/visualizations/RowChart/RowChart.tsx b/frontend/src/metabase/visualizations/visualizations/RowChart/RowChart.tsx index 5d96a9543d371518a59e996b8216d76a6a0d7997..3322728ff57e223df3527349b4410db6d9f3a63f 100644 --- a/frontend/src/metabase/visualizations/visualizations/RowChart/RowChart.tsx +++ b/frontend/src/metabase/visualizations/visualizations/RowChart/RowChart.tsx @@ -234,7 +234,7 @@ const RowChartVisualization = ({ const description = settings["card.description"]; const canSelectTitle = !!onChangeCardAndRun; - const { labels, colors } = useMemo( + const legendItems = useMemo( () => getLegendItems(series, seriesColors, settings), [series, seriesColors, settings], ); @@ -279,8 +279,7 @@ const RowChartVisualization = ({ )} <RowChartLegendLayout hasLegend={hasLegend} - labels={labels} - colors={colors} + items={legendItems} actionButtons={!hasTitle ? actionButtons : undefined} hovered={hovered} onHoverChange={onHoverChange} diff --git a/frontend/src/metabase/visualizations/visualizations/RowChart/utils/legend.ts b/frontend/src/metabase/visualizations/visualizations/RowChart/utils/legend.ts index e167e79b1ebd2d09d2b4f9a642cc9fddf24f5868..1ab4a54bb5307f06de65e73c464bc39e36b19ccb 100644 --- a/frontend/src/metabase/visualizations/visualizations/RowChart/utils/legend.ts +++ b/frontend/src/metabase/visualizations/visualizations/RowChart/utils/legend.ts @@ -1,3 +1,4 @@ +import type { LegendItem } from "metabase/visualizations/echarts/cartesian/model/types"; import type { Series } from "metabase/visualizations/shared/components/RowChart/types"; import type { VisualizationSettings } from "metabase-types/api"; @@ -5,14 +6,11 @@ export const getLegendItems = <TDatum>( multipleSeries: Series<TDatum, unknown>[], seriesColors: Record<string, string>, settings: VisualizationSettings, -) => { - const orderedTitles = multipleSeries.map( - series => +): LegendItem[] => { + return multipleSeries.map(series => ({ + key: series.seriesKey, + name: settings?.series_settings?.[series.seriesKey]?.title ?? series.seriesName, - ); - - return { - labels: orderedTitles, - colors: multipleSeries.map(single => seriesColors[single.seriesKey]), - }; + color: seriesColors[series.seriesKey], + })); };