Skip to content

Commit

Permalink
feat(legend): add point shape styles to legend item (#1227)
Browse files Browse the repository at this point in the history
  • Loading branch information
rshen91 committed Jul 27, 2021
1 parent f3f8d35 commit 46be1d1
Show file tree
Hide file tree
Showing 56 changed files with 540 additions and 214 deletions.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 30 additions & 4 deletions packages/charts/src/chart_types/xy_chart/legend/legend.ts
Expand Up @@ -10,9 +10,10 @@ import { LegendItem } from '../../../common/legend';
import { SeriesKey, SeriesIdentifier } from '../../../common/series_id';
import { ScaleType } from '../../../scales/constants';
import { SortSeriesByConfig, TickFormatterOptions } from '../../../specs';
import { Color } from '../../../utils/common';
import { Color, MergeOptions, mergePartial } from '../../../utils/common';
import { BandedAccessorType } from '../../../utils/geometry';
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';
import { LastValues } from '../state/utils/types';
Expand All @@ -28,7 +29,15 @@ import {
isDataSeriesBanded,
getSeriesIdentifierFromDataSeries,
} from '../utils/series';
import { AxisSpec, BasicSeriesSpec, Postfixes, isAreaSeriesSpec, isBarSeriesSpec } from '../utils/specs';
import {
AxisSpec,
BasicSeriesSpec,
Postfixes,
isAreaSeriesSpec,
isBarSeriesSpec,
isBubbleSeriesSpec,
isLineSeriesSpec,
} from '../utils/specs';

/** @internal */
export interface FormattedLastValues {
Expand Down Expand Up @@ -79,20 +88,33 @@ export function getLegendExtra(
};
}

/** @internal */
function getPointStyle(spec: BasicSeriesSpec, theme: Theme): PointStyle | undefined {
const mergeOptions: MergeOptions = { mergeOptionalPartialValues: true };
if (isBubbleSeriesSpec(spec)) {
return mergePartial(theme.bubbleSeriesStyle.point, spec.bubbleSeriesStyle?.point, mergeOptions);
} else if (isLineSeriesSpec(spec)) {
return mergePartial(theme.lineSeriesStyle.point, spec.lineSeriesStyle?.point, mergeOptions);
} else if (isAreaSeriesSpec(spec)) {
return mergePartial(theme.areaSeriesStyle.point, spec.areaSeriesStyle?.point, mergeOptions);
}
}

/** @internal */
export function computeLegend(
dataSeries: DataSeries[],
lastValues: Map<SeriesKey, LastValues>,
seriesColors: Map<SeriesKey, Color>,
specs: BasicSeriesSpec[],
defaultColor: string,
axesSpecs: AxisSpec[],
showLegendExtra: boolean,
serialIdentifierDataSeriesMap: Record<string, DataSeries>,
deselectedDataSeries: SeriesIdentifier[] = [],
theme: Theme,
sortSeriesBy?: SeriesCompareFn | SortSeriesByConfig,
): LegendItem[] {
const legendItems: LegendItem[] = [];
const defaultColor = theme.colors.defaultVizColor;

dataSeries.forEach((series) => {
const { specId, yAccessor } = series;
Expand All @@ -109,7 +131,6 @@ export function computeLegend(
);

const color = seriesColors.get(dataSeriesKey) || defaultColor;

const hasSingleSeries = dataSeries.length === 1;
const name = getSeriesName(series, hasSingleSeries, false, spec);
const isSeriesHidden = deselectedDataSeries ? getSeriesIndex(deselectedDataSeries, series) >= 0 : false;
Expand All @@ -128,6 +149,9 @@ export function computeLegend(
const lastValue = lastValues.get(key);
const seriesIdentifier = getSeriesIdentifierFromDataSeries(series);
const xScaleType = getXScaleTypeFromSpec(spec.xScaleType);

const pointStyle = getPointStyle(spec, theme);

legendItems.push({
color,
label: labelY1,
Expand All @@ -139,6 +163,7 @@ export function computeLegend(
defaultExtra: getLegendExtra(showLegendExtra, xScaleType, formatter, 'y1', lastValue),
path: [{ index: 0, value: seriesIdentifier.key }],
keys: [specId, spec.groupId, yAccessor, ...series.splitAccessors.values()],
pointStyle,
});
if (banded) {
const labelY0 = getBandedLegendItemLabel(name, BandedAccessorType.Y0, postFixes);
Expand All @@ -153,6 +178,7 @@ export function computeLegend(
defaultExtra: getLegendExtra(showLegendExtra, xScaleType, formatter, 'y0', lastValue),
path: [{ index: 0, value: seriesIdentifier.key }],
keys: [specId, spec.groupId, yAccessor, ...series.splitAccessors.values()],
pointStyle,
});
}
});
Expand Down
Expand Up @@ -39,6 +39,7 @@ function getTransformForPanel(panel: Dimensions, rotation: Rotation, { left, top
return `translate(${left + panel.left + x}, ${top + panel.top + y}) rotate(${rotation})`;
}

/** @internal */
function renderPath(geom: PointGeometry) {
// keep the highlighter radius to a minimum
const radius = Math.max(geom.radius, DEFAULT_HIGHLIGHT_PADDING);
Expand Down
29 changes: 16 additions & 13 deletions packages/charts/src/chart_types/xy_chart/renderer/shapes_paths.ts
Expand Up @@ -6,6 +6,7 @@
* Side Public License, v 1.
*/

import { getRadians } from '../../../utils/common';
import { PointShape, TextureShape } from '../../../utils/themes/theme';

/** @internal */
Expand All @@ -14,30 +15,32 @@ export type SVGPath = string;
/** @internal */
export type SVGPathFn = (radius: number) => SVGPath;

/** @internal */
export const cross: SVGPathFn = (r: number) => {
const cross: SVGPathFn = (r: number) => {
return `M ${-r} 0 L ${r} 0 M 0 ${r} L 0 ${-r}`;
};

/** @internal */
export const triangle: SVGPathFn = (r: number) => {
const triangle: SVGPathFn = (r: number) => {
const h = (r * Math.sqrt(3)) / 2;
const hr = r / 2;
return `M ${-h} ${hr} L ${h} ${hr} L 0 ${-r} Z`;
};

/** @internal */
export const square: SVGPathFn = (r: number) => {
return `M ${-r} ${-r} L ${-r} ${r} L ${r} ${r} L ${r} ${-r} Z`;
/**
* Returns shape function based on rotation of square in degrees
*/
const square = (rotation = 0): SVGPathFn => (r: number) => {
const d = getRadians(rotation);
const s = Math.abs(Math.cos(d) + Math.sin(d));
// scaled r to account for rotation;
const sr = s > 0 ? r / s : r;
return `M ${-sr} ${-sr} L ${-sr} ${sr} L ${sr} ${sr} L ${sr} ${-sr} Z`;
};

/** @internal */
export const circle: SVGPathFn = (r: number) => {
const circle: SVGPathFn = (r: number) => {
return `M ${-r} 0 a ${r},${r} 0 1,0 ${r * 2},0 a ${r},${r} 0 1,0 ${-r * 2},0`;
};

/** @internal */
export const line: SVGPathFn = (r: number) => {
const line: SVGPathFn = (r: number) => {
return `M 0 ${-r} l 0 ${r * 2}`;
};

Expand All @@ -46,8 +49,8 @@ export const ShapeRendererFn: Record<PointShape, [SVGPathFn, number]> = {
[PointShape.Circle]: [circle, 0],
[PointShape.X]: [cross, 45],
[PointShape.Plus]: [cross, 0],
[PointShape.Diamond]: [square, 45],
[PointShape.Square]: [square, 0],
[PointShape.Diamond]: [square(45), 45],
[PointShape.Square]: [square(0), 0],
[PointShape.Triangle]: [triangle, 0],
};

Expand Down
Expand Up @@ -47,11 +47,11 @@ export const computeLegendSelector = createCustomCachedSelector(
lastValues,
seriesColors,
seriesSpecs,
chartTheme.colors.defaultVizColor,
axesSpecs,
settings.showLegendExtra,
siDataSeriesMap,
deselectedDataSeries,
chartTheme,
// @ts-ignore
settings.sortSeriesBy,
);
Expand Down
Expand Up @@ -20,7 +20,6 @@ import { Spec } from '../../../../specs';
import { BARCHART_1Y0G, BARCHART_1Y1G } from '../../../../utils/data_samples/test_dataset';
import { ContinuousDomain, Range } from '../../../../utils/domain';
import { SpecId } from '../../../../utils/ids';
import { PointShape } from '../../../../utils/themes/theme';
import { getSeriesIndex, XYChartSeriesIdentifier } from '../../utils/series';
import { BasicSeriesSpec, HistogramModeAlignments, SeriesColorAccessorFn } from '../../utils/specs';
import { computeSeriesDomainsSelector } from '../selectors/compute_series_domains';
Expand Down Expand Up @@ -209,7 +208,7 @@ describe('Chart State utils', () => {
const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState());
const actual = getCustomSeriesColors(formattedDataSeries);

expect([...actual.values()]).toEqualArrayOf(color);
expect([...actual.values()]).toContainEqual(color);
});

describe('with customSeriesColors array', () => {
Expand Down Expand Up @@ -557,7 +556,6 @@ describe('Chart State utils', () => {
opacity: 1,
radius: 2,
strokeWidth: 1,
shape: PointShape.Circle,
});
});
test('can compute area geometries with custom style', () => {
Expand Down Expand Up @@ -623,7 +621,6 @@ describe('Chart State utils', () => {
opacity: 1,
radius: 2,
strokeWidth: 1,
shape: PointShape.Circle,
});
});
test('can compute bars geometries counts', () => {
Expand Down
8 changes: 4 additions & 4 deletions packages/charts/src/chart_types/xy_chart/utils/texture.ts
Expand Up @@ -18,19 +18,19 @@ const getSpacing = ({ spacing }: TexturedStyles): Point => ({
y: typeof spacing === 'number' ? spacing : spacing?.y ?? 0,
});

const getPath = (textureStyle: TexturedStyles, size: number, stokeWith: number): [path: Path2D, rotation: number] => {
const getPath = (textureStyle: TexturedStyles, size: number, strokeWidth: number): [path: Path2D, rotation: number] => {
if ('path' in textureStyle) {
const path = typeof textureStyle.path === 'string' ? new Path2D(textureStyle.path) : textureStyle.path;

return [path, 0];
}
const [pathFn, rotation] = TextureRendererFn[textureStyle.shape];
// Prevents clipping shapes near edge
const stokeWidthPadding = [TextureShape.Circle, TextureShape.Square].includes(textureStyle.shape as any)
? stokeWith
const strokeWidthPadding = [TextureShape.Circle, TextureShape.Square].includes(textureStyle.shape as any)
? strokeWidth
: 0;

return [new Path2D(pathFn((size - stokeWidthPadding) / 2)), rotation];
return [new Path2D(pathFn((size - strokeWidthPadding) / 2)), rotation];
};

/** @internal */
Expand Down
4 changes: 4 additions & 0 deletions packages/charts/src/common/legend.ts
Expand Up @@ -7,8 +7,10 @@
*/

import { PrimitiveValue } from '../chart_types/partition_chart/layout/utils/group_by_rollup';
import { SeriesType } from '../specs';
import { LegendPath } from '../state/actions/legend';
import { Color } from '../utils/common';
import { PointStyle } from '../utils/themes/theme';
import { CategoryKey, CategoryLabel } from './category';
import { SeriesIdentifier } from './series_id';

Expand Down Expand Up @@ -36,6 +38,8 @@ export type LegendItem = {
// TODO: Remove when partition layers are toggleable
isToggleable?: boolean;
keys: Array<string | number>;
pointStyle?: PointStyle;
seriesType?: SeriesType;
};

/** @internal */
Expand Down
16 changes: 8 additions & 8 deletions packages/charts/src/components/__snapshots__/chart.test.tsx.snap
Expand Up @@ -27,15 +27,15 @@ exports[`Chart should render the legend name test 1`] = `
<LegendItem item={{...}} positionConfig={{...}} totalItems={1} extraValues={{...}} showExtra={false} onMouseOut={[undefined]} onMouseOver={[undefined]} onClick={[undefined]} clearTemporaryColorsAction={[Function (anonymous)]} setPersistedColorAction={[Function (anonymous)]} setTemporaryColorAction={[Function (anonymous)]} mouseOutAction={[Function (anonymous)]} mouseOverAction={[Function (anonymous)]} toggleDeselectSeriesAction={[Function (anonymous)]} colorPicker={[undefined]} action={[undefined]}>
<li className=\\"echLegendItem echLegendItem--vertical\\" onMouseEnter={[Function (anonymous)]} onMouseLeave={[Function (anonymous)]} style={[undefined]} data-ech-series-name=\\"test\\">
<div className=\\"background\\" />
<ForwardRef color=\\"#1EA593\\" seriesName=\\"test\\" isSeriesHidden={false} hasColorPicker={false} onClick={[undefined]}>
<ForwardRef color=\\"#1EA593\\" seriesName=\\"test\\" isSeriesHidden={false} hasColorPicker={false} onClick={[undefined]} pointStyle={[undefined]}>
<div className=\\"echLegendItem__color\\" title=\\"series color\\">
<Icon type=\\"dot\\" color=\\"#1EA593\\" aria-label=\\"series color: #1EA593\\">
<DotIcon className=\\"echIcon\\" color=\\"#1EA593\\" tabIndex={[undefined]} focusable=\\"false\\" aria-label=\\"series color: #1EA593\\">
<svg width={16} height={16} viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" className=\\"echIcon\\" color=\\"#1EA593\\" tabIndex={[undefined]} focusable=\\"false\\" aria-label=\\"series color: #1EA593\\">
<circle cx={8} cy={8} r={4} />
</svg>
</DotIcon>
</Icon>
<LegendIcon pointStyle={[undefined]} color=\\"#1EA593\\" ariaLabel=\\"series color: #1EA593\\">
<svg width={16} height={16} aria-label=\\"series color: #1EA593\\">
<g transform=\\"\\\\n translate(8, 8)\\\\n rotate(0)\\\\n \\">
<path d=\\"M -3.5 0 a 3.5,3.5 0 1,0 7,0 a 3.5,3.5 0 1,0 -7,0\\" stroke=\\"#1EA593\\" strokeWidth={1} fill=\\"#1EA593\\" opacity={1} />
</g>
</svg>
</LegendIcon>
</div>
</ForwardRef>
<Label label=\\"test\\" isToggleable={false} onClick={[undefined]} isSeriesHidden={false}>
Expand Down
1 change: 0 additions & 1 deletion packages/charts/src/components/icons/icon.tsx
Expand Up @@ -53,7 +53,6 @@ function IconComponent({ type, color, className, tabIndex, ...rest }: IconCompon
}

const classes = classNames('echIcon', className);

const Svg = (type && typeToIconMap[type]) || EmptyIcon;

/*
Expand Down

0 comments on commit 46be1d1

Please sign in to comment.