From ebdb000d4f0bc01612f52ed5fa61ca256826645c Mon Sep 17 00:00:00 2001 From: Emma Cunningham Date: Wed, 10 Apr 2019 11:19:03 -0700 Subject: [PATCH] feat(legend): display series value (dependent on hover) & sort in legend (#155) --- .../osd-charts/src/components/_legend.scss | 19 +++++- packages/osd-charts/src/components/legend.tsx | 16 +++-- .../src/components/legend_element.tsx | 38 +++++++++-- .../osd-charts/src/lib/series/legend.test.ts | 40 +++++++++--- packages/osd-charts/src/lib/series/legend.ts | 28 ++++++-- .../osd-charts/src/lib/series/series.test.ts | 60 +++++++++++++++++ packages/osd-charts/src/lib/series/series.ts | 30 +++++++++ packages/osd-charts/src/lib/series/specs.ts | 2 + packages/osd-charts/src/lib/series/tooltip.ts | 33 +++++++++- .../osd-charts/src/lib/utils/interactions.ts | 3 +- packages/osd-charts/src/specs/settings.tsx | 5 ++ .../osd-charts/src/state/chart_state.test.ts | 46 ++++++++++++- packages/osd-charts/src/state/chart_state.ts | 12 +++- packages/osd-charts/stories/legend.tsx | 64 ++++++++++++++++++- 14 files changed, 365 insertions(+), 31 deletions(-) diff --git a/packages/osd-charts/src/components/_legend.scss b/packages/osd-charts/src/components/_legend.scss index 755b60379acc..f6f4269c4bf7 100644 --- a/packages/osd-charts/src/components/_legend.scss +++ b/packages/osd-charts/src/components/_legend.scss @@ -81,11 +81,13 @@ $elasticChartsLegendMaxHeight: $euiSize * 4; overflow: hidden; flex-shrink: 1; flex-grow: 0; + max-width: 100%; } .elasticChartsLegendList { overflow-y: auto; overflow-x: hidden; height: 100%; + max-width: 100%; @include euiScrollBar; } @@ -108,9 +110,20 @@ $elasticChartsLegendMaxHeight: $euiSize * 4; max-width: $elasticChartsLegendMaxWidth - 4 * $euiSize; &.elasticChartsLegendListItem__title--selected { - .elasticChartsLegendListItem__title { - text-decoration: underline; - } + text-decoration: underline; + } + + &.elasticChartsLegendListItem__title--hasDisplayValue { + width: $elasticChartsLegendMaxWidth - 6 * $euiSize; + max-width: $elasticChartsLegendMaxWidth - 6 * $euiSize; + } +} + +.elasticChartsLegendListItem__displayValue { + text-align: right; + + &.elasticChartsLegendListItem__displayValue--hidden { + display: none; } } diff --git a/packages/osd-charts/src/components/legend.tsx b/packages/osd-charts/src/components/legend.tsx index 46ba3bd32091..e77815a80185 100644 --- a/packages/osd-charts/src/components/legend.tsx +++ b/packages/osd-charts/src/components/legend.tsx @@ -68,7 +68,7 @@ class LegendComponent extends React.Component { responsive={false} > {[...legendItems.values()].map((item) => { - const { color, label, isSeriesVisible, isLegendItemVisible } = item; + const { isLegendItemVisible } = item; const legendItemProps = { key: item.key, @@ -81,7 +81,7 @@ class LegendComponent extends React.Component { return ( - {this.renderLegendElement({ color, label, isSeriesVisible }, item.key)} + {this.renderLegendElement(item, item.key)} ); })} @@ -100,10 +100,18 @@ class LegendComponent extends React.Component { } private renderLegendElement = ( - { color, label, isSeriesVisible }: Partial, + { color, label, isSeriesVisible, displayValue }: LegendItem, legendItemKey: string, ) => { - const props = { color, label, isSeriesVisible, legendItemKey }; + const tooltipValues = this.props.chartStore!.legendItemTooltipValues.get(); + let tooltipValue; + + if (tooltipValues && tooltipValues.get(legendItemKey)) { + tooltipValue = tooltipValues.get(legendItemKey); + } + + const display = tooltipValue != null ? tooltipValue : displayValue.formatted; + const props = { color, label, isSeriesVisible, legendItemKey, displayValue: display }; return ; } diff --git a/packages/osd-charts/src/components/legend_element.tsx b/packages/osd-charts/src/components/legend_element.tsx index f36b7be950a2..dc6801befa9c 100644 --- a/packages/osd-charts/src/components/legend_element.tsx +++ b/packages/osd-charts/src/components/legend_element.tsx @@ -22,6 +22,7 @@ interface LegendElementProps { color: string | undefined; label: string | undefined; isSeriesVisible?: boolean; + displayValue: string; } interface LegendElementState { @@ -50,18 +51,37 @@ class LegendElementComponent extends React.Component + {displayValue} + + ); + } + render() { const { legendItemKey } = this.props; - const { color, label, isSeriesVisible } = this.props; + const { color, label, isSeriesVisible, displayValue } = this.props; const onTitleClick = this.onLegendTitleClick(legendItemKey); + const showLegendDisplayValue = this.props.chartStore!.showLegendDisplayValue.get(); const isSelected = legendItemKey === this.props.chartStore!.selectedLegendItemKey.get(); const titleClassNames = classNames( + 'eui-textTruncate', + 'elasticChartsLegendListItem__title', { ['elasticChartsLegendListItem__title--selected']: isSelected, + ['elasticChartsLegendListItem__title--hasDisplayValue']: this.props.chartStore!.showLegendDisplayValue.get(), }, - 'elasticChartsLegendListItem__title', ); const colorDotProps = { @@ -71,6 +91,13 @@ class LegendElementComponent extends React.Component; + const displayValueClassNames = classNames( + 'elasticChartsLegendListItem__displayValue', + { + ['elasticChartsLegendListItem__displayValue--hidden']: !isSeriesVisible, + }, + ); + return ( @@ -90,11 +117,11 @@ class LegendElementComponent extends React.Component {this.renderVisibilityButton(legendItemKey, isSeriesVisible)} - + + {label} } @@ -111,6 +138,9 @@ class LegendElementComponent extends React.Component + + {this.renderDisplayValue(displayValue, showLegendDisplayValue)} + ); } diff --git a/packages/osd-charts/src/lib/series/legend.test.ts b/packages/osd-charts/src/lib/series/legend.test.ts index c2558c919672..f4bfde607d8b 100644 --- a/packages/osd-charts/src/lib/series/legend.test.ts +++ b/packages/osd-charts/src/lib/series/legend.test.ts @@ -1,8 +1,8 @@ -import { getGroupId, getSpecId, SpecId } from '../utils/ids'; +import { AxisId, getAxisId, getGroupId, getSpecId, SpecId } from '../utils/ids'; import { ScaleType } from '../utils/scales/scales'; import { computeLegend, getSeriesColorLabel } from './legend'; import { DataSeriesColorsValues } from './series'; -import { BasicSeriesSpec } from './specs'; +import { AxisSpec, BasicSeriesSpec, Position } from './specs'; const colorValues1a = { specId: getSpecId('spec1'), @@ -46,6 +46,22 @@ const spec2: BasicSeriesSpec = { hideInLegend: false, }; +const axesSpecs = new Map(); +const axisSpec: AxisSpec = { + id: getAxisId('axis1'), + groupId: getGroupId('group1'), + hide: false, + showOverlappingTicks: false, + showOverlappingLabels: false, + position: Position.Left, + tickSize: 10, + tickPadding: 10, + tickFormat: (value: any) => { + return `${value}`; + }, +}; +axesSpecs.set(axisSpec.id, axisSpec); + describe('Legends', () => { const seriesColor = new Map(); const seriesColorMap = new Map(); @@ -61,7 +77,7 @@ describe('Legends', () => { }); it('compute legend for a single series', () => { seriesColor.set('colorSeries1a', colorValues1a); - const legend = computeLegend(seriesColor, seriesColorMap, specs, 'violet'); + const legend = computeLegend(seriesColor, seriesColorMap, specs, 'violet', axesSpecs); const expected = [ { color: 'red', @@ -70,6 +86,7 @@ describe('Legends', () => { isSeriesVisible: true, isLegendItemVisible: true, key: 'colorSeries1a', + displayValue: {}, }, ]; expect(Array.from(legend.values())).toEqual(expected); @@ -77,7 +94,7 @@ describe('Legends', () => { it('compute legend for a single spec but with multiple series', () => { seriesColor.set('colorSeries1a', colorValues1a); seriesColor.set('colorSeries1b', colorValues1b); - const legend = computeLegend(seriesColor, seriesColorMap, specs, 'violet'); + const legend = computeLegend(seriesColor, seriesColorMap, specs, 'violet', axesSpecs); const expected = [ { color: 'red', @@ -86,6 +103,7 @@ describe('Legends', () => { isSeriesVisible: true, isLegendItemVisible: true, key: 'colorSeries1a', + displayValue: {}, }, { color: 'blue', @@ -94,6 +112,7 @@ describe('Legends', () => { isSeriesVisible: true, isLegendItemVisible: true, key: 'colorSeries1b', + displayValue: {}, }, ]; expect(Array.from(legend.values())).toEqual(expected); @@ -101,7 +120,7 @@ describe('Legends', () => { it('compute legend for multiple specs', () => { seriesColor.set('colorSeries1a', colorValues1a); seriesColor.set('colorSeries2a', colorValues2a); - const legend = computeLegend(seriesColor, seriesColorMap, specs, 'violet'); + const legend = computeLegend(seriesColor, seriesColorMap, specs, 'violet', axesSpecs); const expected = [ { color: 'red', @@ -110,6 +129,7 @@ describe('Legends', () => { isSeriesVisible: true, isLegendItemVisible: true, key: 'colorSeries1a', + displayValue: {}, }, { color: 'green', @@ -118,19 +138,20 @@ describe('Legends', () => { isSeriesVisible: true, isLegendItemVisible: true, key: 'colorSeries2a', + displayValue: {}, }, ]; expect(Array.from(legend.values())).toEqual(expected); }); it('empty legend for missing spec', () => { seriesColor.set('colorSeries2b', colorValues2b); - const legend = computeLegend(seriesColor, seriesColorMap, specs, 'violet'); + const legend = computeLegend(seriesColor, seriesColorMap, specs, 'violet', axesSpecs); expect(legend.size).toEqual(0); }); it('compute legend with default color for missing series color', () => { seriesColor.set('colorSeries1a', colorValues1a); const emptyColorMap = new Map(); - const legend = computeLegend(seriesColor, emptyColorMap, specs, 'violet'); + const legend = computeLegend(seriesColor, emptyColorMap, specs, 'violet', axesSpecs); const expected = [ { color: 'violet', @@ -139,6 +160,7 @@ describe('Legends', () => { isSeriesVisible: true, isLegendItemVisible: true, key: 'colorSeries1a', + displayValue: {}, }, ]; expect(Array.from(legend.values())).toEqual(expected); @@ -152,7 +174,7 @@ describe('Legends', () => { const emptyColorMap = new Map(); const deselectedDataSeries = null; - const legend = computeLegend(seriesColor, emptyColorMap, specs, 'violet', deselectedDataSeries); + const legend = computeLegend(seriesColor, emptyColorMap, specs, 'violet', axesSpecs, deselectedDataSeries); const visibility = [...legend.values()].map((item) => item.isSeriesVisible); @@ -167,7 +189,7 @@ describe('Legends', () => { const emptyColorMap = new Map(); const deselectedDataSeries = [colorValues1a, colorValues1b]; - const legend = computeLegend(seriesColor, emptyColorMap, specs, 'violet', deselectedDataSeries); + const legend = computeLegend(seriesColor, emptyColorMap, specs, 'violet', axesSpecs, deselectedDataSeries); const visibility = [...legend.values()].map((item) => item.isSeriesVisible); expect(visibility).toEqual([false, false, true]); diff --git a/packages/osd-charts/src/lib/series/legend.ts b/packages/osd-charts/src/lib/series/legend.ts index b4983b30d8cd..055529e35e9c 100644 --- a/packages/osd-charts/src/lib/series/legend.ts +++ b/packages/osd-charts/src/lib/series/legend.ts @@ -1,7 +1,8 @@ -import { findDataSeriesByColorValues } from '../../state/utils'; -import { SpecId } from '../utils/ids'; -import { DataSeriesColorsValues } from './series'; -import { BasicSeriesSpec } from './specs'; +import { findDataSeriesByColorValues, getAxesSpecForSpecId } from '../../state/utils'; +import { identity } from '../utils/commons'; +import { AxisId, SpecId } from '../utils/ids'; +import { DataSeriesColorsValues, getSortedDataSeriesColorsValuesMap } from './series'; +import { AxisSpec, BasicSeriesSpec } from './specs'; export interface LegendItem { key: string; @@ -10,16 +11,25 @@ export interface LegendItem { value: DataSeriesColorsValues; isSeriesVisible?: boolean; isLegendItemVisible?: boolean; + displayValue: { + raw: any; + formatted: any; + }; } + export function computeLegend( seriesColor: Map, seriesColorMap: Map, specs: Map, defaultColor: string, + axesSpecs: Map, deselectedDataSeries?: DataSeriesColorsValues[] | null, ): Map { const legendItems: Map = new Map(); - seriesColor.forEach((series, key) => { + + const sortedSeriesColors = getSortedDataSeriesColorsValuesMap(seriesColor); + + sortedSeriesColors.forEach((series, key) => { const spec = specs.get(series.specId); const color = seriesColorMap.get(key) || defaultColor; @@ -33,6 +43,10 @@ export function computeLegend( return; } + // Use this to get axis spec w/ tick formatter + const { yAxis } = getAxesSpecForSpecId(axesSpecs, spec.groupId); + const formatter = yAxis ? yAxis.tickFormat : identity; + const { hideInLegend } = spec; legendItems.set(key, { @@ -42,6 +56,10 @@ export function computeLegend( value: series, isSeriesVisible, isLegendItemVisible: !hideInLegend, + displayValue: { + raw: series.lastValue, + formatted: formatter(series.lastValue), + }, }); }); return legendItems; diff --git a/packages/osd-charts/src/lib/series/series.test.ts b/packages/osd-charts/src/lib/series/series.test.ts index d79772eec510..2e666ea1c6d5 100644 --- a/packages/osd-charts/src/lib/series/series.test.ts +++ b/packages/osd-charts/src/lib/series/series.test.ts @@ -6,6 +6,7 @@ import { formatStackedDataSeriesValues, getFormattedDataseries, getSeriesColorMap, + getSortedDataSeriesColorsValuesMap, getSplittedSeries, RawDataSeries, splitSeries, @@ -316,4 +317,63 @@ describe('Series', () => { const subsetSplit = getSplittedSeries(seriesSpecs, deselectedDataSeries); expect(subsetSplit.splittedSeries.get(specId)!.length).toBe(1); }); + + test('should sort series color by series spec sort index', () => { + const spec1Id = getSpecId('spec1'); + const spec2Id = getSpecId('spec2'); + const spec3Id = getSpecId('spec3'); + + const colorValuesMap = new Map(); + const dataSeriesValues1: DataSeriesColorsValues = { + specId: spec1Id, + colorValues: [], + specSortIndex: 0, + }; + + const dataSeriesValues2: DataSeriesColorsValues = { + specId: spec2Id, + colorValues: [], + specSortIndex: 1, + }; + + const dataSeriesValues3: DataSeriesColorsValues = { + specId: spec3Id, + colorValues: [], + specSortIndex: 3, + }; + + colorValuesMap.set(spec3Id, dataSeriesValues3); + colorValuesMap.set(spec1Id, dataSeriesValues1); + colorValuesMap.set(spec2Id, dataSeriesValues2); + + const descSortedColorValues = new Map(); + descSortedColorValues.set(spec1Id, dataSeriesValues1); + descSortedColorValues.set(spec2Id, dataSeriesValues2); + descSortedColorValues.set(spec3Id, dataSeriesValues3); + + expect(getSortedDataSeriesColorsValuesMap(colorValuesMap)).toEqual(descSortedColorValues); + + const ascSortedColorValues = new Map(); + dataSeriesValues1.specSortIndex = 2; + dataSeriesValues2.specSortIndex = 1; + dataSeriesValues3.specSortIndex = 0; + + ascSortedColorValues.set(spec3Id, dataSeriesValues3); + ascSortedColorValues.set(spec2Id, dataSeriesValues2); + ascSortedColorValues.set(spec1Id, dataSeriesValues1); + + expect(getSortedDataSeriesColorsValuesMap(colorValuesMap)).toEqual(ascSortedColorValues); + + // Any series with undefined sort order should come last + const undefinedSortedColorValues = new Map(); + dataSeriesValues1.specSortIndex = 1; + dataSeriesValues2.specSortIndex = undefined; + dataSeriesValues3.specSortIndex = 0; + + undefinedSortedColorValues.set(spec3Id, dataSeriesValues3); + undefinedSortedColorValues.set(spec1Id, dataSeriesValues1); + undefinedSortedColorValues.set(spec2Id, dataSeriesValues2); + + expect(getSortedDataSeriesColorsValuesMap(colorValuesMap)).toEqual(undefinedSortedColorValues); + }); }); diff --git a/packages/osd-charts/src/lib/series/series.ts b/packages/osd-charts/src/lib/series/series.ts index eeff8ef3515a..366db0f4e3af 100644 --- a/packages/osd-charts/src/lib/series/series.ts +++ b/packages/osd-charts/src/lib/series/series.ts @@ -47,6 +47,8 @@ export interface DataSeriesCounts { export interface DataSeriesColorsValues { specId: SpecId; colorValues: any[]; + lastValue?: any; + specSortIndex?: number; } /** @@ -61,6 +63,7 @@ export function splitSeries( rawDataSeries: RawDataSeries[]; colorsValues: Map; xValues: Set; + splitSeriesLastValues: Map; } { const { xAccessor, yAccessors, splitSeriesAccessors = [] } = accessors; const colorAccessors = accessors.colorAccessors ? accessors.colorAccessors : splitSeriesAccessors; @@ -68,6 +71,8 @@ export function splitSeries( const series = new Map(); const colorsValues = new Map(); const xValues = new Set(); + const splitSeriesLastValues = new Map(); + data.forEach((datum) => { const seriesKey = getAccessorsValues(datum, splitSeriesAccessors); if (isMultipleY) { @@ -76,6 +81,7 @@ export function splitSeries( const colorValuesKey = getColorValuesAsString(colorValues, specId); colorsValues.set(colorValuesKey, colorValues); const cleanedDatum = cleanDatum(datum, xAccessor, accessor); + splitSeriesLastValues.set(colorValuesKey, cleanedDatum.y); xValues.add(cleanedDatum.x); updateSeriesMap(series, [...seriesKey, accessor], cleanedDatum, specId, colorValuesKey); }, {}); @@ -84,14 +90,17 @@ export function splitSeries( const colorValuesKey = getColorValuesAsString(colorValues, specId); colorsValues.set(colorValuesKey, colorValues); const cleanedDatum = cleanDatum(datum, xAccessor, yAccessors[0]); + splitSeriesLastValues.set(colorValuesKey, cleanedDatum.y); xValues.add(cleanedDatum.x); updateSeriesMap(series, [...seriesKey], cleanedDatum, specId, colorValuesKey); } }, {}); + return { rawDataSeries: [...series.values()], colorsValues, xValues, + splitSeriesLastValues, }; } @@ -370,9 +379,13 @@ export function getSplittedSeries( splittedSeries.set(specId, currentRawDataSeries); dataSeries.colorsValues.forEach((colorValues, key) => { + const lastValue = dataSeries.splitSeriesLastValues.get(key); + seriesColors.set(key, { specId, + specSortIndex: spec.sortIndex, colorValues, + lastValue, }); }); @@ -387,6 +400,23 @@ export function getSplittedSeries( }; } +export function getSortedDataSeriesColorsValuesMap( + colorValuesMap: Map, +): Map { + const seriesColorsArray = [...colorValuesMap]; + seriesColorsArray.sort((seriesA, seriesB) => { + const [, colorValuesA] = seriesA; + const [, colorValuesB] = seriesB; + + const specAIndex = colorValuesA.specSortIndex != null ? colorValuesA.specSortIndex : colorValuesMap.size; + const specBIndex = colorValuesB.specSortIndex != null ? colorValuesB.specSortIndex : colorValuesMap.size; + + return specAIndex - specBIndex; + }); + + return new Map([...seriesColorsArray]); +} + export function getSeriesColorMap( seriesColors: Map, chartColors: ColorConfig, diff --git a/packages/osd-charts/src/lib/series/specs.ts b/packages/osd-charts/src/lib/series/specs.ts index 2db8acae91b5..c344c0a82d7a 100644 --- a/packages/osd-charts/src/lib/series/specs.ts +++ b/packages/osd-charts/src/lib/series/specs.ts @@ -43,6 +43,8 @@ export interface SeriesSpec { * @default false */ hideInLegend?: boolean; + /** Index per series to sort by */ + sortIndex?: number; } export type CustomSeriesColorsMap = Map; diff --git a/packages/osd-charts/src/lib/series/tooltip.ts b/packages/osd-charts/src/lib/series/tooltip.ts index efbe3d52daad..bb79d3ccb47d 100644 --- a/packages/osd-charts/src/lib/series/tooltip.ts +++ b/packages/osd-charts/src/lib/series/tooltip.ts @@ -1,8 +1,29 @@ import { Accessor } from '../utils/accessor'; +import { SpecId } from '../utils/ids'; import { TooltipValue } from '../utils/interactions'; import { IndexedGeometry } from './rendering'; +import { getColorValuesAsString } from './series'; import { AxisSpec, BasicSeriesSpec, Datum, TickFormatter } from './specs'; +export function getSeriesTooltipValues( + tooltipValues: TooltipValue[], +): Map { + // map from seriesKey to tooltipValue + const seriesTooltipValues = new Map(); + + // First tooltipValue is the header + if (tooltipValues.length <= 1) { + return seriesTooltipValues; + } + + tooltipValues.slice(1).forEach((tooltipValue: TooltipValue) => { + const { seriesKey, value } = tooltipValue; + seriesTooltipValues.set(seriesKey, value); + }); + + return seriesTooltipValues; +} + export function formatTooltip( searchIndexValue: IndexedGeometry, spec: BasicSeriesSpec, @@ -24,6 +45,8 @@ export function formatTooltip( } // format y value return formatAccessor( + spec.id, + seriesKey, datum, yAccessors, color, @@ -53,6 +76,8 @@ export function formatXTooltipValue( name = spec.name || `${spec.id}`; } const xValues = formatAccessor( + spec.id, + searchIndexValue.seriesKey, searchIndexValue.datum, [spec.xAccessor], color, @@ -63,7 +88,10 @@ export function formatXTooltipValue( ); return xValues[0]; } -function formatAccessor( + +export function formatAccessor( + specId: SpecId, + seriesKeys: any[], datum: Datum, accessors: Accessor[] = [], color: string, @@ -72,9 +100,12 @@ function formatAccessor( isXValue: boolean, name?: string, ): TooltipValue[] { + const seriesKey = getColorValuesAsString(seriesKeys, specId); + return accessors.map( (accessor): TooltipValue => { return { + seriesKey, name: name || `${accessor}`, value: formatter(datum[accessor]), color, diff --git a/packages/osd-charts/src/lib/utils/interactions.ts b/packages/osd-charts/src/lib/utils/interactions.ts index b9ccc74ae172..196ab3466745 100644 --- a/packages/osd-charts/src/lib/utils/interactions.ts +++ b/packages/osd-charts/src/lib/utils/interactions.ts @@ -19,6 +19,7 @@ export interface TooltipValue { color: string; isHighlighted: boolean; isXValue: boolean; + seriesKey: string; } export interface HighlightedElement { position: { @@ -83,7 +84,7 @@ export function areIndexedGeometryArraysEquals(arr1: IndexedGeometry[], arr2: In if (arr1.length !== arr2.length) { return false; } - for (let i = arr1.length; i--; ) { + for (let i = arr1.length; i--;) { return areIndexedGeomsEquals(arr1[i], arr2[i]); } return true; diff --git a/packages/osd-charts/src/specs/settings.tsx b/packages/osd-charts/src/specs/settings.tsx index de415300898a..4ae7d06cc346 100644 --- a/packages/osd-charts/src/specs/settings.tsx +++ b/packages/osd-charts/src/specs/settings.tsx @@ -29,6 +29,8 @@ interface SettingSpecProps { tooltipSnap?: boolean; debug: boolean; legendPosition?: Position; + isLegendItemsSortDesc: boolean; + showLegendDisplayValue: boolean; onElementClick?: ElementClickListener; onElementOver?: ElementOverListener; onElementOut?: () => undefined; @@ -52,6 +54,7 @@ function updateChartStore(props: SettingSpecProps) { tooltipType, tooltipSnap, legendPosition, + showLegendDisplayValue, onElementClick, onElementOver, onElementOut, @@ -78,6 +81,7 @@ function updateChartStore(props: SettingSpecProps) { chartStore.setShowLegend(showLegend); chartStore.legendPosition = legendPosition; + chartStore.showLegendDisplayValue.set(showLegendDisplayValue); chartStore.xDomain = xDomain; if (onElementOver) { @@ -118,6 +122,7 @@ export class SettingsComponent extends PureComponent { debug: false, tooltipType: DEFAULT_TOOLTIP_TYPE, tooltipSnap: DEFAULT_TOOLTIP_SNAP, + showLegendDisplayValue: true, }; componentDidMount() { updateChartStore(this.props); diff --git a/packages/osd-charts/src/state/chart_state.test.ts b/packages/osd-charts/src/state/chart_state.test.ts index 0c0113506ac8..3cb34c308f31 100644 --- a/packages/osd-charts/src/state/chart_state.test.ts +++ b/packages/osd-charts/src/state/chart_state.test.ts @@ -1,3 +1,4 @@ +import { LegendItem } from '../lib/series/legend'; import { GeometryValue, IndexedGeometry } from '../lib/series/rendering'; import { DataSeriesColorsValues } from '../lib/series/series'; import { @@ -37,7 +38,7 @@ describe('Chart Store', () => { hideInLegend: false, }; - const firstLegendItem = { + const firstLegendItem: LegendItem = { key: 'color1', color: 'foo', label: 'bar', @@ -45,9 +46,13 @@ describe('Chart Store', () => { specId: SPEC_ID, colorValues: [], }, + displayValue: { + raw: 'last', + formatted: 'formatted-last', + }, }; - const secondLegendItem = { + const secondLegendItem: LegendItem = { key: 'color2', color: 'baz', label: 'qux', @@ -55,6 +60,10 @@ describe('Chart Store', () => { specId: SPEC_ID, colorValues: [], }, + displayValue: { + raw: 'last', + formatted: 'formatted-last', + }, }; beforeEach(() => { store = new ChartStore(); @@ -579,6 +588,7 @@ describe('Chart Store', () => { color: 'a', isHighlighted: false, isXValue: false, + seriesKey: 'a', }; store.cursorPosition.x = -1; store.cursorPosition.y = 1; @@ -632,6 +642,7 @@ describe('Chart Store', () => { color: 'a', isHighlighted: false, isXValue: false, + seriesKey: 'a', }; store.xScale = new ScaleContinuous([0, 100], [0, 100], ScaleType.Linear); store.cursorPosition.x = 1; @@ -718,4 +729,35 @@ describe('Chart Store', () => { store.cursorPosition.x = 0; expect(store.annotationTooltipState.get()).toBe(null); }); + test('can get tooltipValues by seriesKeys', () => { + store.tooltipData.clear(); + + expect(store.legendItemTooltipValues.get()).toEqual(new Map()); + + const headerValue: TooltipValue = { + name: 'header', + value: 'foo', + color: 'a', + isHighlighted: false, + isXValue: true, + seriesKey: 'headerSeries', + }; + + store.tooltipData.replace([headerValue]); + expect(store.legendItemTooltipValues.get()).toEqual(new Map()); + + const tooltipValue: TooltipValue = { + name: 'a', + value: 123, + color: 'a', + isHighlighted: false, + isXValue: false, + seriesKey: 'seriesKey', + }; + store.tooltipData.replace([headerValue, tooltipValue]); + + const expectedTooltipValues = new Map(); + expectedTooltipValues.set('seriesKey', 123); + expect(store.legendItemTooltipValues.get()).toEqual(expectedTooltipValues); + }); }); diff --git a/packages/osd-charts/src/state/chart_state.ts b/packages/osd-charts/src/state/chart_state.ts index d5ba2f871d35..8ef292c3c438 100644 --- a/packages/osd-charts/src/state/chart_state.ts +++ b/packages/osd-charts/src/state/chart_state.ts @@ -39,7 +39,7 @@ import { Rendering, Rotation, } from '../lib/series/specs'; -import { formatTooltip, formatXTooltipValue } from '../lib/series/tooltip'; +import { formatTooltip, formatXTooltipValue, getSeriesTooltipValues } from '../lib/series/tooltip'; import { LIGHT_THEME } from '../lib/themes/light_theme'; import { mergeWithDefaultAnnotationLine, Theme } from '../lib/themes/theme'; import { computeChartDimensions, Dimensions } from '../lib/utils/dimensions'; @@ -219,6 +219,8 @@ export class ChartStore { showLegend = observable.box(false); legendCollapsed = observable.box(false); legendPosition: Position | undefined; + showLegendDisplayValue = observable.box(true); + toggleLegendCollapsed = action(() => { this.legendCollapsed.set(!this.legendCollapsed.get()); this.computeChart(); @@ -401,6 +403,11 @@ export class ChartStore { } }); + legendItemTooltipValues = computed(() => { + // update legend items with value to display + return getSeriesTooltipValues(this.tooltipData); + }); + annotationTooltipState = computed(() => { // get positions relative to chart const xPos = this.rawCursorPosition.x - this.chartDimensions.left; @@ -455,6 +462,7 @@ export class ChartStore { // clear highlight geoms this.highlightedGeometries.clear(); this.tooltipData.clear(); + document.body.style.cursor = 'default'; }); @@ -758,8 +766,10 @@ export class ChartStore { this.seriesColorMap, this.seriesSpecs, this.chartTheme.colors.defaultVizColor, + this.axesSpecs, this.deselectedDataSeries, ); + // tslint:disable-next-line:no-console // console.log({ legendItems: this.legendItems }); diff --git a/packages/osd-charts/stories/legend.tsx b/packages/osd-charts/stories/legend.tsx index d0a357a0b476..a606b48de6d1 100644 --- a/packages/osd-charts/stories/legend.tsx +++ b/packages/osd-charts/stories/legend.tsx @@ -1,10 +1,12 @@ -import { boolean } from '@storybook/addon-knobs'; +import { array, boolean, select } from '@storybook/addon-knobs'; import { storiesOf } from '@storybook/react'; import React from 'react'; import { + AreaSeries, Axis, BarSeries, Chart, + CurveType, getAxisId, getSpecId, LineSeries, @@ -13,6 +15,7 @@ import { Settings, } from '../src/'; import * as TestDatasets from '../src/lib/series/utils/test_dataset'; +import { TSVB_DATASET } from '../src/lib/series/utils/test_dataset_tsvb'; storiesOf('Legend', module) .add('right', () => { @@ -211,4 +214,63 @@ storiesOf('Legend', module) /> ); + }) + .add('display values in legend elements', () => { + const showLegendDisplayValue = boolean('show display value in legend', true); + const legendPosition = select('legendPosition', { + right: Position.Right, + bottom: Position.Bottom, + left: Position.Left, + top: Position.Top, + }, Position.Right); + + const tsvbSeries = TSVB_DATASET.series; + + const namesArray = array('series names (in sort order)', [ + 'jpg', + 'php', + 'png', + 'css', + 'gif', + ]); + + const seriesComponents = tsvbSeries.map((series: any) => { + const nameIndex = namesArray.findIndex((name: string) => name === series.label); + const sortIndex = nameIndex > -1 ? nameIndex : undefined; + + return (); + }); + return ( + + + + Number(d).toFixed(2)} + /> + {seriesComponents} + + ); });