Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(legend): expose sorting function #1644

Merged
merged 6 commits into from
Apr 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions packages/charts/api/charts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1407,6 +1407,7 @@ export interface LegendSpec {
legendMaxDepth: number;
legendPosition: Position | LegendPositionConfig;
legendSize: number;
legendSort?: SeriesCompareFn;
legendStrategy?: LegendStrategy;
// (undocumented)
onLegendItemClick?: LegendItemListener;
Expand Down Expand Up @@ -2052,6 +2053,9 @@ export type SeriesColorAccessorFn = (seriesIdentifier: XYChartSeriesIdentifier)
// @public (undocumented)
export type SeriesColorsArray = string[];

// @public
export type SeriesCompareFn = (siA: SeriesIdentifier, siB: SeriesIdentifier) => number;

// @public
export type SeriesIdentifier = {
specId: SpecId;
Expand Down Expand Up @@ -2137,7 +2141,7 @@ export const Settings: (props: SFProps<SettingsSpec, keyof typeof settingsBuildP
// 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", "rotation" | "debug" | "tooltip" | "ariaLabelHeadingLevel" | "ariaUseDefaultSummary" | "legendPosition" | "legendMaxDepth" | "legendSize" | "showLegend" | "showLegendExtra" | "baseTheme" | "rendering" | "animateData" | "externalPointerEvents" | "resizeDebounce" | "pointerUpdateTrigger" | "brushAxis" | "minBrushDelta" | "allowBrushingLastHistogramBin", "ariaLabel" | "xDomain" | "theme" | "ariaDescription" | "ariaDescribedBy" | "ariaLabelledBy" | "ariaTableCaption" | "flatLegend" | "legendAction" | "legendColorPicker" | "legendStrategy" | "onLegendItemClick" | "onLegendItemMinusClick" | "onLegendItemOut" | "onLegendItemOver" | "onLegendItemPlusClick" | "orderOrdinalBinsBy" | "debugState" | "onProjectionClick" | "onElementClick" | "onElementOver" | "onElementOut" | "pointBuffer" | "onBrushEnd" | "onPointerUpdate" | "onRenderChange" | "onProjectionAreaChange" | "onAnnotationClick" | "pointerUpdateDebounce" | "roundHistogramBrushValues" | "noResults", never>;
export const settingsBuildProps: BuildProps<SettingsSpec, "id" | "chartType" | "specType", "rotation" | "debug" | "tooltip" | "ariaLabelHeadingLevel" | "ariaUseDefaultSummary" | "legendPosition" | "legendMaxDepth" | "legendSize" | "showLegend" | "showLegendExtra" | "baseTheme" | "rendering" | "animateData" | "externalPointerEvents" | "resizeDebounce" | "pointerUpdateTrigger" | "brushAxis" | "minBrushDelta" | "allowBrushingLastHistogramBin", "ariaLabel" | "xDomain" | "theme" | "ariaDescription" | "ariaDescribedBy" | "ariaLabelledBy" | "ariaTableCaption" | "flatLegend" | "legendAction" | "legendColorPicker" | "legendStrategy" | "onLegendItemClick" | "onLegendItemMinusClick" | "onLegendItemOut" | "onLegendItemOver" | "onLegendItemPlusClick" | "orderOrdinalBinsBy" | "debugState" | "onProjectionClick" | "onElementClick" | "onElementOver" | "onElementOut" | "pointBuffer" | "onBrushEnd" | "onPointerUpdate" | "onRenderChange" | "onProjectionAreaChange" | "onAnnotationClick" | "pointerUpdateDebounce" | "roundHistogramBrushValues" | "noResults" | "legendSort", never>;

// @public (undocumented)
export type SettingsProps = ComponentProps<typeof Settings>;
Expand Down Expand Up @@ -2269,7 +2273,6 @@ export function sortIndexAccessor(n: ArrayEntry): number;
// @public
export interface SortSeriesByConfig {
default?: SeriesCompareFn;
// Warning: (ae-forgotten-export) The symbol "SeriesCompareFn" needs to be exported by the entry point index.d.ts
legend?: SeriesCompareFn;
rendering?: SeriesCompareFn;
tooltip?: SeriesCompareFn;
Expand Down
18 changes: 9 additions & 9 deletions packages/charts/src/chart_types/xy_chart/legend/legend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import { Color } from '../../../common/colors';
import { LegendItem } from '../../../common/legend';
import { SeriesKey, SeriesIdentifier } from '../../../common/series_id';
import { ScaleType } from '../../../scales/constants';
import { TickFormatterOptions } from '../../../specs';
import { mergePartial, Rotation } from '../../../utils/common';
import { SettingsSpec, TickFormatterOptions } from '../../../specs';
import { mergePartial } from '../../../utils/common';
import { BandedAccessorType } from '../../../utils/geometry';
import { getLegendCompareFn } from '../../../utils/series_sort';
import { getLegendCompareFn, SeriesCompareFn } from '../../../utils/series_sort';
import { PointStyle, Theme } from '../../../utils/themes/theme';
import { getXScaleTypeFromSpec } from '../scales/get_api_scales';
import { getAxesSpecForSpecId, getSpecsById } from '../state/utils/spec';
Expand Down Expand Up @@ -100,11 +100,10 @@ export function computeLegend(
seriesColors: Map<SeriesKey, Color>,
specs: BasicSeriesSpec[],
axesSpecs: AxisSpec[],
showLegendExtra: boolean,
settingsSpec: SettingsSpec,
serialIdentifierDataSeriesMap: Record<string, DataSeries>,
deselectedDataSeries: SeriesIdentifier[] = [],
theme: Theme,
chartRotation: Rotation,
): LegendItem[] {
const legendItems: LegendItem[] = [];
const defaultColor = theme.colors.defaultVizColor;
Expand Down Expand Up @@ -133,7 +132,7 @@ export function computeLegend(
const labelY1 = banded ? getBandedLegendItemLabel(name, BandedAccessorType.Y1, postFixes) : name;

// Use this to get axis spec w/ tick formatter
const { yAxis } = getAxesSpecForSpecId(axesSpecs, spec.groupId, chartRotation);
const { yAxis } = getAxesSpecForSpecId(axesSpecs, spec.groupId, settingsSpec.rotation);
const formatter = spec.tickFormat ?? yAxis?.tickFormat ?? defaultTickFormatter;
const { hideInLegend } = spec;

Expand All @@ -151,7 +150,7 @@ export function computeLegend(
isSeriesHidden,
isItemHidden: hideInLegend,
isToggleable: true,
defaultExtra: getLegendExtra(showLegendExtra, xScaleType, formatter, 'y1', lastValue),
defaultExtra: getLegendExtra(settingsSpec.showLegendExtra, xScaleType, formatter, 'y1', lastValue),
path: [{ index: 0, value: seriesIdentifier.key }],
keys: [specId, spec.groupId, yAccessor, ...series.splitAccessors.values()],
pointStyle,
Expand All @@ -166,7 +165,7 @@ export function computeLegend(
isSeriesHidden,
isItemHidden: hideInLegend,
isToggleable: true,
defaultExtra: getLegendExtra(showLegendExtra, xScaleType, formatter, 'y0', lastValue),
defaultExtra: getLegendExtra(settingsSpec.showLegendExtra, xScaleType, formatter, 'y0', lastValue),
path: [{ index: 0, value: seriesIdentifier.key }],
keys: [specId, spec.groupId, yAccessor, ...series.splitAccessors.values()],
pointStyle,
Expand All @@ -179,9 +178,10 @@ export function computeLegend(
const bDs = serialIdentifierDataSeriesMap[b.key];
return defaultXYLegendSeriesSort(aDs, bDs);
});
const sortFn: SeriesCompareFn = settingsSpec.legendSort ?? legendSortFn;

return groupBy(
legendItems.sort((a, b) => legendSortFn(a.seriesIdentifiers[0], b.seriesIdentifiers[0])),
legendItems.sort((a, b) => sortFn(a.seriesIdentifiers[0], b.seriesIdentifiers[0])),
({ keys, childId }) => {
return [...keys, childId].join('__'); // childId is used for band charts
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,16 @@ export const computeLegendSelector = createCustomCachedSelector(
settings,
siDataSeriesMap: Record<string, DataSeries>,
): LegendItem[] => {
const lastValues = getLastValues(formattedDataSeries, xDomain);
return computeLegend(
formattedDataSeries,
lastValues,
getLastValues(formattedDataSeries, xDomain),
seriesColors,
seriesSpecs,
axesSpecs,
settings.showLegendExtra,
settings,
siDataSeriesMap,
deselectedDataSeries,
chartTheme,
settings.rotation,
markov00 marked this conversation as resolved.
Show resolved Hide resolved
);
},
);
1 change: 1 addition & 0 deletions packages/charts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export { CurveType } from './utils/curves';
export { ContinuousDomain, OrdinalDomain } from './utils/domain';
export { Dimensions, SimplePadding, Padding, PerSideDistance, Margins } from './utils/dimensions';
export { timeFormatter, niceTimeFormatter, niceTimeFormatByDay } from './utils/data/formatters';
export { SeriesCompareFn } from './utils/series_sort';
export { SeriesIdentifier, SeriesKey } from './common/series_id';
export { XYChartSeriesIdentifier, DataSeriesDatum, FilledValues } from './chart_types/xy_chart/utils/series';
export {
Expand Down
4 changes: 4 additions & 0 deletions packages/charts/src/specs/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,10 @@ export interface LegendSpec {
*/
legendAction?: LegendAction;
legendColorPicker?: LegendColorPicker;
/**
* A SeriesSortFn to sort the legend values (top-bottom)
*/
legendSort?: SeriesCompareFn;
}

/**
Expand Down
69 changes: 69 additions & 0 deletions storybook/stories/legend/15_legend_sort.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { boolean } from '@storybook/addon-knobs';
import React from 'react';

import { Axis, BarSeries, Chart, Position, ScaleType, Settings } from '@elastic/charts';

import { XYChartSeriesIdentifier } from '../../../packages/charts/src/chart_types/xy_chart/utils/series';
import { SeriesIdentifier } from '../../../packages/charts/src/common/series_id';
import { useBaseTheme } from '../../use_base_theme';

export const Example = () => {
const data: Array<[number, string, number]> = [
[2010, 'Apple', 10],
[2010, 'Orange', 6],
[2010, 'Banana', 4],
[2011, 'Apple', 9],
markov00 marked this conversation as resolved.
Show resolved Hide resolved
[2011, 'Orange', 6],
[2011, 'Banana', 2],
[2012, 'Apple', 7],
[2012, 'Orange', 3],
[2012, 'Banana', 3],
[2013, 'Apple', 12],
[2013, 'Orange', 10],
[2013, 'Banana', 5],
];
// the sorting value can be part of the dataset or externally defined
const categoricalIndex: Array<unknown> = ['Apple', 'Orange', 'Banana'];

const reverseSort = boolean('reverse', true);
const defaultSort = boolean('default sort', false);

const legendSort = (a: SeriesIdentifier, b: SeriesIdentifier) => {
// extract the value from the accessors to identify the current selected series
// the SeriesIdentifier can be casted to the chart type specific one
const categoryA = (a as XYChartSeriesIdentifier)?.splitAccessors?.get(1) ?? '';
const categoryB = (b as XYChartSeriesIdentifier)?.splitAccessors?.get(1) ?? '';
// find the index of each series
const catAIndex = categoricalIndex.indexOf(categoryA);
const catBIndex = categoricalIndex.indexOf(categoryB);
// compare the indices and return the order
return reverseSort ? catAIndex - catBIndex : catBIndex - catAIndex;
};
return (
<Chart>
<Settings showLegend baseTheme={useBaseTheme()} legendSort={defaultSort ? undefined : legendSort} />
<Axis id="bottom" position={Position.Bottom} title="[2010-2013] harvest" showOverlappingTicks />
<Axis id="left2" position={Position.Left} ticks={4} tickFormat={(d) => `${d} tons`} />

<BarSeries
id="bars"
xScaleType={ScaleType.Ordinal}
yScaleType={ScaleType.Linear}
xAccessor={0}
yAccessors={[2]}
splitSeriesAccessors={[1]}
stackAccessors={[0]}
data={data}
yNice
/>
</Chart>
);
};
1 change: 1 addition & 0 deletions storybook/stories/legend/legend.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ export { Example as piechartRepeatedLabels } from './10_sunburst_repeated_label.
export { Example as actions } from './11_legend_actions.story';
export { Example as margins } from './12_legend_margins.story';
export { Example as singleSeries } from './14_single_series.story';
export { Example as sortItems } from './15_legend_sort.story';