Skip to content

Commit

Permalink
feat(Legend): add legend stats
Browse files Browse the repository at this point in the history
  • Loading branch information
mbondyra committed May 28, 2024
1 parent 96b0779 commit f694948
Show file tree
Hide file tree
Showing 45 changed files with 1,471 additions and 351 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions packages/charts/api/charts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1817,6 +1817,7 @@ export type LegendItemListener = (series: SeriesIdentifier[]) => void;
export type LegendItemValue = {
value: PrimitiveValue;
label: string;
type: LegendValue;
};

// @public (undocumented)
Expand Down Expand Up @@ -1854,6 +1855,7 @@ export interface LegendSpec {
legendSize: number;
legendSort?: SeriesCompareFn;
legendStrategy?: LegendStrategy;
legendTitle?: string;
legendValues: Array<LegendValue>;
// (undocumented)
onLegendItemClick?: LegendItemListener;
Expand Down Expand Up @@ -1892,7 +1894,6 @@ export interface LegendStyle {

// @public (undocumented)
export const LegendValue: Readonly<{
None: "none";
CurrentAndLastValue: "currentAndLastValue";
LastValue: "lastValue";
LastNonNullValue: "lastNonNullValue";
Expand Down Expand Up @@ -2751,7 +2752,7 @@ export const Settings: (props: SFProps<SettingsSpec, keyof (typeof settingsBuild
// Warning: (ae-forgotten-export) The symbol "BuildProps" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
export const settingsBuildProps: BuildProps<SettingsSpec, "id" | "chartType" | "specType", "debug" | "locale" | "rotation" | "baseTheme" | "rendering" | "animateData" | "externalPointerEvents" | "pointBuffer" | "pointerUpdateTrigger" | "brushAxis" | "minBrushDelta" | "allowBrushingLastHistogramBin" | "ariaLabelHeadingLevel" | "ariaUseDefaultSummary" | "dow" | "showLegend" | "legendPosition" | "legendValues" | "legendMaxDepth" | "legendSize" | "flatLegend", "ariaDescription" | "ariaLabel" | "xDomain" | "theme" | "debugState" | "onProjectionClick" | "onElementClick" | "onElementOver" | "onElementOut" | "onBrushEnd" | "onPointerUpdate" | "onResize" | "onRenderChange" | "onWillRender" | "onProjectionAreaChange" | "onAnnotationClick" | "resizeDebounce" | "pointerUpdateDebounce" | "roundHistogramBrushValues" | "orderOrdinalBinsBy" | "noResults" | "ariaLabelledBy" | "ariaDescribedBy" | "ariaTableCaption" | "legendStrategy" | "onLegendItemOver" | "onLegendItemOut" | "onLegendItemClick" | "onLegendItemPlusClick" | "onLegendItemMinusClick" | "legendAction" | "legendColorPicker" | "legendSort" | "customLegend", never>;
export const settingsBuildProps: BuildProps<SettingsSpec, "id" | "chartType" | "specType", "debug" | "locale" | "rotation" | "baseTheme" | "rendering" | "animateData" | "externalPointerEvents" | "pointBuffer" | "pointerUpdateTrigger" | "brushAxis" | "minBrushDelta" | "allowBrushingLastHistogramBin" | "ariaLabelHeadingLevel" | "ariaUseDefaultSummary" | "dow" | "showLegend" | "legendPosition" | "legendValues" | "legendMaxDepth" | "legendSize" | "flatLegend", "ariaDescription" | "ariaLabel" | "xDomain" | "theme" | "debugState" | "onProjectionClick" | "onElementClick" | "onElementOver" | "onElementOut" | "onBrushEnd" | "onPointerUpdate" | "onResize" | "onRenderChange" | "onWillRender" | "onProjectionAreaChange" | "onAnnotationClick" | "resizeDebounce" | "pointerUpdateDebounce" | "roundHistogramBrushValues" | "orderOrdinalBinsBy" | "noResults" | "ariaLabelledBy" | "ariaDescribedBy" | "ariaTableCaption" | "legendStrategy" | "onLegendItemOver" | "onLegendItemOut" | "onLegendItemClick" | "onLegendItemPlusClick" | "onLegendItemMinusClick" | "legendAction" | "legendColorPicker" | "legendSort" | "customLegend" | "legendTitle", never>;

// @public (undocumented)
export type SettingsProps = ComponentProps<typeof Settings>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ exports[`Retain hierarchy even with arbitrary names getLegendItems all distinct
"values": [
{
"label": "2",
"type": "value",
"value": 2,
},
],
Expand Down Expand Up @@ -68,6 +69,7 @@ exports[`Retain hierarchy even with arbitrary names getLegendItems all distinct
"values": [
{
"label": "1",
"type": "value",
"value": 1,
},
],
Expand Down Expand Up @@ -105,6 +107,7 @@ exports[`Retain hierarchy even with arbitrary names getLegendItems all distinct
"values": [
{
"label": "1",
"type": "value",
"value": 1,
},
],
Expand Down Expand Up @@ -138,6 +141,7 @@ exports[`Retain hierarchy even with arbitrary names getLegendItems all distinct
"values": [
{
"label": "2",
"type": "value",
"value": 2,
},
],
Expand Down Expand Up @@ -175,6 +179,7 @@ exports[`Retain hierarchy even with arbitrary names getLegendItems all distinct
"values": [
{
"label": "1",
"type": "value",
"value": 1,
},
],
Expand Down Expand Up @@ -212,6 +217,7 @@ exports[`Retain hierarchy even with arbitrary names getLegendItems all distinct
"values": [
{
"label": "1",
"type": "value",
"value": 1,
},
],
Expand Down Expand Up @@ -245,6 +251,7 @@ exports[`Retain hierarchy even with arbitrary names getLegendItems all distinct
"values": [
{
"label": "2",
"type": "value",
"value": 2,
},
],
Expand Down Expand Up @@ -282,6 +289,7 @@ exports[`Retain hierarchy even with arbitrary names getLegendItems all distinct
"values": [
{
"label": "1",
"type": "value",
"value": 1,
},
],
Expand Down Expand Up @@ -319,6 +327,7 @@ exports[`Retain hierarchy even with arbitrary names getLegendItems all distinct
"values": [
{
"label": "1",
"type": "value",
"value": 1,
},
],
Expand Down Expand Up @@ -357,6 +366,7 @@ exports[`Retain hierarchy even with arbitrary names getLegendItems special case:
"values": [
{
"label": "1",
"type": "value",
"value": 1,
},
],
Expand Down Expand Up @@ -394,6 +404,7 @@ exports[`Retain hierarchy even with arbitrary names getLegendItems special case:
"values": [
{
"label": "1",
"type": "value",
"value": 1,
},
],
Expand Down Expand Up @@ -432,6 +443,7 @@ exports[`Retain hierarchy even with arbitrary names getLegendItems special case:
"values": [
{
"label": "1",
"type": "value",
"value": 1,
},
],
Expand Down Expand Up @@ -469,6 +481,7 @@ exports[`Retain hierarchy even with arbitrary names getLegendItems special case:
"values": [
{
"label": "1",
"type": "value",
"value": 1,
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import { isMosaic, isSunburst, isTreemap, isWaffle } from './viewmodel';
import { LegendItemExtraValues } from '../../../../common/legend';
import { LegendItemExtraValues, LegendValue } from '../../../../common/legend';
import { SeriesKey } from '../../../../common/series_id';
import { Relation } from '../../../../common/text_utils';
import { LegendPath } from '../../../../state/actions/legend';
Expand Down Expand Up @@ -118,7 +118,7 @@ export function getExtraValueMap(
const { value, path, [CHILDREN_KEY]: children } = arrayNode;
const values: LegendItemExtraValues = new Map();
const label = valueFormatter ? valueFormatter(value) : `${value}`;
values.set(key, { label, value });
values.set(key, { label, value, type: LegendValue.Value });
keys.set(path.map(({ index }) => index).join('__'), values);
if (depth < maxDepth) getExtraValueMap(layers, valueFormatter, children, maxDepth, depth + 1, keys);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { getPartitionSpecs } from './get_partition_specs';
import { getTrees } from './tree';
import { RGBATupleToString } from '../../../../common/color_library_wrappers';
import { Colors } from '../../../../common/colors';
import { LegendItem } from '../../../../common/legend';
import { LegendItem, LegendValue } from '../../../../common/legend';
import { SeriesIdentifier } from '../../../../common/series_id';
import { createCustomCachedSelector } from '../../../../state/create_selector';
import { getLegendConfigSelector } from '../../../../state/selectors/get_legend_config_selector';
Expand Down Expand Up @@ -127,6 +127,7 @@ function walkTree(
{
value: node[AGGREGATE_KEY],
label: valueFormatter(node[AGGREGATE_KEY]),
type: LegendValue.Value,
},
],
},
Expand Down
39 changes: 13 additions & 26 deletions packages/charts/src/chart_types/xy_chart/legend/legend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import { Color } from '../../../common/colors';
import { LegendItem, LegendValue } from '../../../common/legend';
import { LegendItem } from '../../../common/legend';
import { SeriesKey, SeriesIdentifier } from '../../../common/series_id';
import { SettingsSpec } from '../../../specs';
import { isDefined, mergePartial } from '../../../utils/common';
Expand All @@ -16,7 +16,7 @@ import { getLegendCompareFn, SeriesCompareFn } from '../../../utils/series_sort'
import { PointStyle, Theme } from '../../../utils/themes/theme';
import { XDomain } from '../domains/types';
import { isDatumFilled } from '../rendering/utils';
import { getLegendValue } from '../state/utils/get_last_value';
import { getLegendValues } from '../state/utils/get_legend_values';
import { getAxesSpecForSpecId, getSpecsById } from '../state/utils/spec';
import { Y0_ACCESSOR_POSTFIX, Y1_ACCESSOR_POSTFIX } from '../tooltip/tooltip';
import { defaultTickFormatter } from '../utils/axis_utils';
Expand Down Expand Up @@ -109,7 +109,7 @@ export function computeLegend(
const legendItems: LegendItem[] = [];
const defaultColor = theme.colors.defaultVizColor;

const legendValueMode = settingsSpec.legendValues[0] ?? LegendValue.None;
const legendValues = settingsSpec.legendValues ?? [];

dataSeries.forEach((series) => {
const { specId, yAccessor } = series;
Expand Down Expand Up @@ -140,8 +140,7 @@ export function computeLegend(

const pointStyle = getPointStyle(spec, theme);

const itemValue = getLegendValue(series, xDomain, legendValueMode, y1Accessor(series.stackMode));
const formattedItemValue = itemValue !== null ? formatter(itemValue) : '';
const legendValuesItems = getLegendValues(series, xDomain, legendValues, y1Accessor(series.stackMode), formatter);

legendItems.push({
depth: 0,
Expand All @@ -152,22 +151,19 @@ export function computeLegend(
isSeriesHidden,
isItemHidden: hideInLegend,
isToggleable: true,
values:
itemValue !== null
? [
{
value: itemValue,
label: formattedItemValue,
},
]
: [],
values: legendValuesItems,
path: [{ index: 0, value: seriesIdentifier.key }],
keys: [specId, spec.groupId, yAccessor, ...series.splitAccessors.values()],
pointStyle,
});
if (banded) {
const bandedItemValue = getLegendValue(series, xDomain, legendValueMode, y0Accessor(series.stackMode));
const bandedFormattedItemValue = bandedItemValue !== null ? formatter(bandedItemValue) : '';
const bandedLegendValuesItems = getLegendValues(
series,
xDomain,
legendValues,
y0Accessor(series.stackMode),
formatter,
);

const labelY0 = getBandedLegendItemLabel(name, BandedAccessorType.Y0, postFixes);
legendItems.push({
Expand All @@ -179,15 +175,7 @@ export function computeLegend(
isSeriesHidden,
isItemHidden: hideInLegend,
isToggleable: true,
values:
bandedItemValue !== null
? [
{
value: bandedItemValue,
label: bandedFormattedItemValue,
},
]
: [],
values: bandedLegendValuesItems,
path: [{ index: 0, value: seriesIdentifier.key }],
keys: [specId, spec.groupId, yAccessor, ...series.splitAccessors.values()],
pointStyle,
Expand All @@ -201,7 +189,6 @@ export function computeLegend(
return defaultXYLegendSeriesSort(aDs, bDs);
});
const sortFn: SeriesCompareFn = settingsSpec.legendSort ?? legendSortFn;

return groupBy(
legendItems.sort((a, b) =>
a.seriesIdentifiers[0] && b.seriesIdentifiers[0] ? sortFn(a.seriesIdentifiers[0], b.seriesIdentifiers[0]) : 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ describe('Type Checks', () => {
specId: 'bars',
},
],
values: [{ value: 6, label: '6.00' }],
values: [{ value: 6, label: '6.00', type: 'currentAndLastValue' }],
isSeriesHidden: true,
path: [],
keys: [],
Expand All @@ -149,7 +149,7 @@ describe('Type Checks', () => {
specId: 'bars',
},
],
values: [{ value: 2, label: '2.00' }],
values: [{ value: 2, label: '2.00', type: 'currentAndLastValue' }],
isSeriesHidden: true,
path: [],
keys: [],
Expand All @@ -169,7 +169,7 @@ describe('Type Checks', () => {
specId: 'bars',
},
],
values: [{ value: 6, label: '6.00' }],
values: [{ value: 6, label: '6.00', type: 'currentAndLastValue' }],
isSeriesHidden: false,
path: [],
keys: [],
Expand All @@ -184,7 +184,7 @@ describe('Type Checks', () => {
specId: 'bars',
},
],
values: [{ value: 2, label: '2.00' }],
values: [{ value: 2, label: '2.00', type: 'currentAndLastValue' }],
isSeriesHidden: true,
path: [],
keys: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,29 @@ import { LegendValue } from '../../../../common/legend';
import { ScaleType } from '../../../../scales/constants';
import { XDomain } from '../../domains/types';
import { DataSeries, DataSeriesDatum } from '../../utils/series';
import { TickFormatter } from '../../utils/specs';

/**
* This method return legend values from a DataSeries that correspond to the type of value requested.
* It in general compute the last, min, max, avg, sum of the value in a series.
* @internal
*/
export function getLegendValues(
series: DataSeries,
xDomain: XDomain,
types: LegendValue[],
valueAccessor: (d: DataSeriesDatum) => number | null,
formatter: TickFormatter<any> | ((tick: unknown) => string),
) {
return types.map((type) => {
const value = getLegendValue(series, xDomain, type, valueAccessor);
return {
type,
label: typeof value === 'number' ? formatter(value) : '',
value,
};
});
}

/**
* This method return a value from a DataSeries that correspond to the type of value requested.
Expand Down Expand Up @@ -80,7 +103,6 @@ export function getLegendValue(
case LegendValue.DifferencePercent:
return differencePercent(series.data, valueAccessor);
default:
case LegendValue.None:
return null;
}
}
4 changes: 2 additions & 2 deletions packages/charts/src/chart_types/xy_chart/tooltip/tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import { LegendItemExtraValues } from '../../../common/legend';
import { LegendItemExtraValues, LegendValue } from '../../../common/legend';
import { SeriesKey } from '../../../common/series_id';
import { TooltipValue } from '../../../specs';
import { PointerValue } from '../../../state/types';
Expand All @@ -29,7 +29,7 @@ export function getLegendItemExtraValues(tooltipValues: TooltipValue[]): Map<Ser
tooltipValues.forEach(({ formattedValue, value, seriesIdentifier, valueAccessor }) => {
const current: LegendItemExtraValues = seriesTooltipValues.get(seriesIdentifier.key) ?? new Map();
if (valueAccessor === BandedAccessorType.Y0 || valueAccessor === BandedAccessorType.Y1) {
current.set(valueAccessor, { label: formattedValue, value });
current.set(valueAccessor, { label: formattedValue, value, type: LegendValue.CurrentAndLastValue });
}
seriesTooltipValues.set(seriesIdentifier.key, current);
});
Expand Down
Loading

0 comments on commit f694948

Please sign in to comment.