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): display series value (dependent on hover) & sort in legend #155

Merged
merged 19 commits into from
Apr 10, 2019
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d6be7c3
feat(legend): display last series value in legend
emmacunningham Apr 4, 2019
8bbbaad
feat(legend): format lastValue with axisSpec tickFormatter
emmacunningham Apr 5, 2019
343b883
feat(series): sort seriesColors by last value in series
emmacunningham Apr 6, 2019
d7536ef
test(series): test seriesColors sorting
emmacunningham Apr 6, 2019
ced7a3b
feat(legend): display tooltipValue in legendItem
emmacunningham Apr 6, 2019
05f8d5e
test(chart_state): add test for legendItemsTooltipValues
emmacunningham Apr 6, 2019
1434cab
feat(legend): adjust width of legend item display value
emmacunningham Apr 7, 2019
a8daded
feat(settings): add isLegendItemsSortDesc to control sort order
emmacunningham Apr 8, 2019
91a9acd
feat(settings): add showLegendDisplayValue
emmacunningham Apr 8, 2019
2d0a815
test: fix tests with isSortDesc argument
emmacunningham Apr 8, 2019
f4d6c67
fix: remove isLegendItemsSortDesc
emmacunningham Apr 8, 2019
fe93220
test: revert "test: fix tests with isSortDesc argument"
emmacunningham Apr 8, 2019
a5dc995
fix(series): remove internal series sorting
emmacunningham Apr 8, 2019
aa47cda
feat(series): add sortIndex to SeriesSpec & sort legend items
emmacunningham Apr 9, 2019
d853eab
test(series): add tests for series colors sorting
emmacunningham Apr 9, 2019
150f69c
refactor(series): sort colorsValuesMap using specSortIndex
emmacunningham Apr 9, 2019
27798cc
fix(legend): align displayValue to the right
emmacunningham Apr 9, 2019
e235e15
feat(legend): hide displayValue if !isSeriesVisible
emmacunningham Apr 10, 2019
c87f7c4
docs(legend): update story name
emmacunningham Apr 10, 2019
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
15 changes: 12 additions & 3 deletions src/components/_legend.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -108,10 +110,17 @@ $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;
}

.elasticChartsLegend__toggle {
Expand Down
16 changes: 12 additions & 4 deletions src/components/legend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class LegendComponent extends React.Component<ReactiveChartProps> {
responsive={false}
>
{[...legendItems.values()].map((item) => {
const { color, label, isSeriesVisible, isLegendItemVisible } = item;
const { isLegendItemVisible } = item;

const legendItemProps = {
key: item.key,
Expand All @@ -81,7 +81,7 @@ class LegendComponent extends React.Component<ReactiveChartProps> {

return (
<EuiFlexItem {...legendItemProps}>
{this.renderLegendElement({ color, label, isSeriesVisible }, item.key)}
{this.renderLegendElement(item, item.key)}
</EuiFlexItem>
);
})}
Expand All @@ -100,10 +100,18 @@ class LegendComponent extends React.Component<ReactiveChartProps> {
}

private renderLegendElement = (
{ color, label, isSeriesVisible }: Partial<LegendItem>,
{ 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 <LegendElement {...props} />;
}
Expand Down
31 changes: 27 additions & 4 deletions src/components/legend_element.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ interface LegendElementProps {
color: string | undefined;
label: string | undefined;
isSeriesVisible?: boolean;
displayValue: string;
}

interface LegendElementState {
Expand Down Expand Up @@ -50,18 +51,37 @@ class LegendElementComponent extends React.Component<LegendElementProps, LegendE
});
}

renderDisplayValue(displayValue: string, show: boolean) {
if (!show) {
return;
}

return (
<EuiText
size="xs"
className="eui-textTruncate elasticChartsLegendListItem__displayValue"
title={displayValue}
>
{displayValue}
</EuiText>
);
}

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 = {
Expand Down Expand Up @@ -90,11 +110,11 @@ class LegendElementComponent extends React.Component<LegendElementProps, LegendE
<EuiFlexItem grow={false}>
{this.renderVisibilityButton(legendItemKey, isSeriesVisible)}
</EuiFlexItem>
<EuiFlexItem grow={false} className={titleClassNames} onClick={onTitleClick}>
<EuiFlexItem grow={false} onClick={onTitleClick}>
<EuiPopover
id="contentPanel"
button={
<EuiText size="xs" className="eui-textTruncate elasticChartsLegendListItem__title">
<EuiText size="xs" className={titleClassNames}>
{label}
</EuiText>
}
Expand All @@ -111,6 +131,9 @@ class LegendElementComponent extends React.Component<LegendElementProps, LegendE
</EuiContextMenuPanel>
</EuiPopover>
</EuiFlexItem>
<EuiFlexItem grow={true} className="elasticChartsLegendListItem__displayValue">
{this.renderDisplayValue(displayValue, showLegendDisplayValue)}
</EuiFlexItem>
</EuiFlexGroup>
);
}
Expand Down
40 changes: 31 additions & 9 deletions src/lib/series/legend.test.ts
Original file line number Diff line number Diff line change
@@ -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'),
Expand Down Expand Up @@ -46,6 +46,22 @@ const spec2: BasicSeriesSpec = {
hideInLegend: false,
};

const axesSpecs = new Map<AxisId, AxisSpec>();
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<string, DataSeriesColorsValues>();
const seriesColorMap = new Map<string, string>();
Expand All @@ -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',
Expand All @@ -70,14 +86,15 @@ describe('Legends', () => {
isSeriesVisible: true,
isLegendItemVisible: true,
key: 'colorSeries1a',
displayValue: {},
},
];
expect(Array.from(legend.values())).toEqual(expected);
});
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',
Expand All @@ -86,6 +103,7 @@ describe('Legends', () => {
isSeriesVisible: true,
isLegendItemVisible: true,
key: 'colorSeries1a',
displayValue: {},
},
{
color: 'blue',
Expand All @@ -94,14 +112,15 @@ describe('Legends', () => {
isSeriesVisible: true,
isLegendItemVisible: true,
key: 'colorSeries1b',
displayValue: {},
},
];
expect(Array.from(legend.values())).toEqual(expected);
});
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',
Expand All @@ -110,6 +129,7 @@ describe('Legends', () => {
isSeriesVisible: true,
isLegendItemVisible: true,
key: 'colorSeries1a',
displayValue: {},
},
{
color: 'green',
Expand All @@ -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<string, string>();
const legend = computeLegend(seriesColor, emptyColorMap, specs, 'violet');
const legend = computeLegend(seriesColor, emptyColorMap, specs, 'violet', axesSpecs);
const expected = [
{
color: 'violet',
Expand All @@ -139,6 +160,7 @@ describe('Legends', () => {
isSeriesVisible: true,
isLegendItemVisible: true,
key: 'colorSeries1a',
displayValue: {},
},
];
expect(Array.from(legend.values())).toEqual(expected);
Expand All @@ -152,7 +174,7 @@ describe('Legends', () => {
const emptyColorMap = new Map<string, string>();
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);

Expand All @@ -167,7 +189,7 @@ describe('Legends', () => {
const emptyColorMap = new Map<string, string>();
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]);
Expand Down
28 changes: 23 additions & 5 deletions src/lib/series/legend.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -10,16 +11,25 @@ export interface LegendItem {
value: DataSeriesColorsValues;
isSeriesVisible?: boolean;
isLegendItemVisible?: boolean;
displayValue: {
raw: any;
formatted: any;
};
}

export function computeLegend(
seriesColor: Map<string, DataSeriesColorsValues>,
seriesColorMap: Map<string, string>,
specs: Map<SpecId, BasicSeriesSpec>,
defaultColor: string,
axesSpecs: Map<AxisId, AxisSpec>,
deselectedDataSeries?: DataSeriesColorsValues[] | null,
): Map<string, LegendItem> {
const legendItems: Map<string, LegendItem> = 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;
Expand All @@ -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, {
Expand All @@ -42,6 +56,10 @@ export function computeLegend(
value: series,
isSeriesVisible,
isLegendItemVisible: !hideInLegend,
displayValue: {
raw: series.lastValue,
formatted: formatter(series.lastValue),
},
});
});
return legendItems;
Expand Down
Loading