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(D3 plugin): line chart legend symbol #358

Merged
merged 3 commits into from
Dec 1, 2023
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
6 changes: 5 additions & 1 deletion src/plugins/d3/__stories__/Showcase.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ const ShowcaseStory = () => {
<BasicLine />
</Col>
<Col>
<Text variant="subheader-1">Line chart with data labels</Text>
<Text variant="subheader-1">With data labels</Text>
<LineWithDataLabels />
</Col>
<Col></Col>
</Row>
<Row space={1}>
<Text variant="header-2">Bar-x charts</Text>
Expand Down Expand Up @@ -99,6 +100,7 @@ const ShowcaseStory = () => {
<Text variant="subheader-1">Donut chart</Text>
<Donut />
</Col>
<Col></Col>
</Row>
<Row space={1}>
<Text variant="header-2">Scatter charts</Text>
Expand All @@ -108,6 +110,8 @@ const ShowcaseStory = () => {
<Text variant="subheader-1">Basic scatter</Text>
<BasicScatter />
</Col>
<Col></Col>
<Col></Col>
</Row>
<Row space={1}>
<Text variant="header-2">Combined chart</Text>
Expand Down
87 changes: 67 additions & 20 deletions src/plugins/d3/renderer/components/Legend.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import {select} from 'd3';
import {BaseType, select, line as lineGenerator} from 'd3';
import type {Selection} from 'd3';

import {block} from '../../../../utils/cn';
Expand Down Expand Up @@ -94,6 +94,71 @@ const appendPaginator = (args: {
paginationLine.attr('transform', transform);
};

const legendSymbolGenerator = lineGenerator<{x: number; y: number}>()
.x((d) => d.x)
.y((d) => d.y);

function renderLegendSymbol(args: {
selection: Selection<SVGGElement, LegendItem, BaseType, unknown>;
legend: PreparedLegend;
}) {
const {selection, legend} = args;
const line = selection.data();

const getXPosition = (i: number) => {
return line.slice(0, i).reduce((acc, legendItem) => {
return (
acc +
legendItem.symbol.width +
legendItem.symbol.padding +
legendItem.textWidth +
legend.itemDistance
);
}, 0);
};

selection.each(function (d, i) {
const element = select(this);
const x = getXPosition(i);
const className = b('item-symbol', {shape: d.symbol.shape, unselected: !d.visible});
const color = d.visible ? d.color : '';

switch (d.symbol.shape) {
case 'path': {
const y = legend.lineHeight / 2;
const points = [
{x: x, y},
{x: x + d.symbol.width, y},
];

element
.append('path')
.attr('d', legendSymbolGenerator(points))
.attr('fill', 'none')
.attr('stroke-width', d.symbol.strokeWidth)
.attr('class', className)
.style('stroke', color);

break;
}
case 'rect': {
const y = (legend.lineHeight - d.symbol.height) / 2;
element
.append('rect')
.attr('x', x)
.attr('y', y)
.attr('width', d.symbol.width)
.attr('height', d.symbol.height)
.attr('rx', d.symbol.radius)
.attr('class', className)
.style('fill', color);

break;
}
}
});
}

export const Legend = (props: Props) => {
const {boundsWidth, chartSeries, legend, items, config, onItemClick} = props;
const ref = React.useRef<SVGGElement>(null);
Expand Down Expand Up @@ -139,25 +204,7 @@ export const Legend = (props: Props) => {
}, 0);
};

legendItemTemplate
.append('rect')
.attr('x', function (_d, i) {
return getXPosition(i);
})
.attr('y', (legendItem) => {
return (legend.lineHeight - legendItem.symbol.height) / 2;
})
.attr('width', (legendItem) => {
return legendItem.symbol.width;
})
.attr('height', (legendItem) => legendItem.symbol.height)
.attr('rx', (legendItem) => legendItem.symbol.radius)
.attr('class', function (d) {
return b('item-shape', {unselected: !d.visible});
})
.style('fill', function (d) {
return d.visible ? d.color : '';
});
renderLegendSymbol({selection: legendItemTemplate, legend});

legendItemTemplate
.append('text')
Expand Down
10 changes: 6 additions & 4 deletions src/plugins/d3/renderer/components/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@
user-select: none;
}

&__item-shape {
fill: var(--g-color-base-misc-medium);

&_unselected {
&__item-symbol {
&_shape_rect#{&}_unselected {
fill: var(--g-color-text-hint);
}

&_shape_path#{&}_unselected {
stroke: var(--g-color-text-hint);
}
}

&__item-text {
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/d3/renderer/hooks/useSeries/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {BaseTextStyle} from '../../../../../types';

export const DEFAULT_LEGEND_SYMBOL_SIZE = 8;

export const DEFAULT_LEGEND_SYMBOL_PADDING = 5;

export const DEFAULT_DATALABELS_PADDING = 5;

export const DEFAULT_DATALABELS_STYLE: BaseTextStyle = {
Expand Down
3 changes: 2 additions & 1 deletion src/plugins/d3/renderer/hooks/useSeries/prepare-legend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import get from 'lodash/get';
import merge from 'lodash/merge';
import {select} from 'd3';

import type {ChartKitWidgetData} from '../../../../../types/widget-data';
import type {ChartKitWidgetData} from '../../../../../types';

import {legendDefaults} from '../../constants';
import {getHorisontalSvgTextHeight} from '../../utils';
Expand All @@ -24,6 +24,7 @@ export const getPreparedLegend = (args: {
const itemStyle = get(legend, 'itemStyle');
const computedItemStyle = merge(defaultItemStyle, itemStyle);
const lineHeight = getHorisontalSvgTextHeight({text: 'Tmp', style: computedItemStyle});

const height = enabled ? lineHeight : 0;

return {
Expand Down
38 changes: 32 additions & 6 deletions src/plugins/d3/renderer/hooks/useSeries/prepare-line-series.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,49 @@
import {ScaleOrdinal} from 'd3';
import get from 'lodash/get';

import {ChartKitWidgetSeriesOptions, LineSeries} from '../../../../../types';
import {PreparedLineSeries, PreparedLegend, PreparedSeries} from './types';
import {
ChartKitWidgetSeries,
ChartKitWidgetSeriesOptions,
LineSeries,
RectLegendSymbolOptions,
} from '../../../../../types';
import {PreparedLineSeries, PreparedLegend, PreparedSeries, PreparedLegendSymbol} from './types';

import {DEFAULT_DATALABELS_PADDING, DEFAULT_DATALABELS_STYLE} from './constants';
import {prepareLegendSymbol} from './utils';
import {
DEFAULT_DATALABELS_PADDING,
DEFAULT_DATALABELS_STYLE,
DEFAULT_LEGEND_SYMBOL_PADDING,
} from './constants';
import {getRandomCKId} from '../../../../../utils';

export const DEFAULT_LEGEND_SYMBOL_SIZE = 16;
export const DEFAULT_LINE_WIDTH = 1;

type PrepareLineSeriesArgs = {
colorScale: ScaleOrdinal<string, string>;
series: LineSeries[];
seriesOptions?: ChartKitWidgetSeriesOptions;
legend: PreparedLegend;
};

function prepareLineLegendSymbol(
series: ChartKitWidgetSeries,
seriesOptions?: ChartKitWidgetSeriesOptions,
): PreparedLegendSymbol {
const symbolOptions: RectLegendSymbolOptions = series.legend?.symbol || {};
const defaultLineWidth = get(seriesOptions, 'line.lineWidth', DEFAULT_LINE_WIDTH);

return {
shape: 'path',
width: symbolOptions?.width || DEFAULT_LEGEND_SYMBOL_SIZE,
padding: symbolOptions?.padding || DEFAULT_LEGEND_SYMBOL_PADDING,
strokeWidth: get(series, 'lineWidth', defaultLineWidth),
};
}

export function prepareLineSeries(args: PrepareLineSeriesArgs): PreparedSeries[] {
const {colorScale, series: seriesList, seriesOptions, legend} = args;
const defaultLineWidth = get(seriesOptions, 'line.lineWidth', 1);
const defaultLineWidth = get(seriesOptions, 'line.lineWidth', DEFAULT_LINE_WIDTH);

return seriesList.map<PreparedLineSeries>((series) => {
const id = getRandomCKId();
Expand All @@ -33,7 +59,7 @@ export function prepareLineSeries(args: PrepareLineSeriesArgs): PreparedSeries[]
visible: get(series, 'visible', true),
legend: {
enabled: get(series, 'legend.enabled', legend.enabled),
symbol: prepareLegendSymbol(series),
symbol: prepareLineLegendSymbol(series, seriesOptions),
},
data: series.data,
dataLabels: {
Expand Down
8 changes: 7 additions & 1 deletion src/plugins/d3/renderer/hooks/useSeries/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,20 @@ import {
LineSeriesData,
ConnectorShape,
ConnectorCurve,
PathLegendSymbolOptions,
} from '../../../../../types';
import type {SeriesOptionsDefaults} from '../../constants';

export type RectLegendSymbol = {
shape: 'rect';
} & Required<RectLegendSymbolOptions>;

export type PreparedLegendSymbol = RectLegendSymbol;
export type PathLegendSymbol = {
shape: 'path';
strokeWidth: number;
} & Required<PathLegendSymbolOptions>;

export type PreparedLegendSymbol = RectLegendSymbol | PathLegendSymbol;

export type PreparedLegend = Required<ChartKitWidgetLegend> & {
height: number;
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/d3/renderer/hooks/useSeries/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {PreparedLegendSymbol, PreparedSeries} from './types';
import {ChartKitWidgetSeries, RectLegendSymbolOptions} from '../../../../../types';
import {DEFAULT_LEGEND_SYMBOL_SIZE} from './constants';
import {DEFAULT_LEGEND_SYMBOL_PADDING, DEFAULT_LEGEND_SYMBOL_SIZE} from './constants';

export const getActiveLegendItems = (series: PreparedSeries[]) => {
return series.reduce<string[]>((acc, s) => {
Expand All @@ -25,6 +25,6 @@ export function prepareLegendSymbol(series: ChartKitWidgetSeries): PreparedLegen
width: symbolOptions?.width || DEFAULT_LEGEND_SYMBOL_SIZE,
height: symbolHeight,
radius: symbolOptions?.radius || symbolHeight / 2,
padding: symbolOptions?.padding || 5,
padding: symbolOptions?.padding || DEFAULT_LEGEND_SYMBOL_PADDING,
};
}
9 changes: 9 additions & 0 deletions src/types/widget-data/legend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,12 @@ export type RectLegendSymbolOptions = BaseLegendSymbol & {
*/
radius?: number;
};

export type PathLegendSymbolOptions = BaseLegendSymbol & {
/**
* The pixel width of the symbol for series types that use a path in the legend
*
* @default 16
* */
width?: number;
};
Loading