From 3f0c0d19febdf1282daa146078a99175750b15ab Mon Sep 17 00:00:00 2001 From: "Irina V. Kuzmina" Date: Tue, 15 Aug 2023 19:02:28 +0300 Subject: [PATCH 1/5] fix(D3 plugin): bar chart --- src/plugins/d3/__stories__/Bar.stories.tsx | 75 ++++++++++++ .../d3/renderer/hooks/useScales/index.ts | 6 +- .../d3/renderer/hooks/useShapes/bar.tsx | 114 ++++++++++++++++++ .../d3/renderer/hooks/useShapes/index.tsx | 23 +++- src/types/widget-data/bar.ts | 19 +++ src/types/widget-data/index.ts | 1 + src/types/widget-data/series.ts | 8 +- 7 files changed, 237 insertions(+), 9 deletions(-) create mode 100644 src/plugins/d3/__stories__/Bar.stories.tsx create mode 100644 src/plugins/d3/renderer/hooks/useShapes/bar.tsx create mode 100644 src/types/widget-data/bar.ts diff --git a/src/plugins/d3/__stories__/Bar.stories.tsx b/src/plugins/d3/__stories__/Bar.stories.tsx new file mode 100644 index 00000000..b5c4602e --- /dev/null +++ b/src/plugins/d3/__stories__/Bar.stories.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import {Meta, Story} from '@storybook/react'; +import {withKnobs, object} from '@storybook/addon-knobs'; +import {Button} from '@gravity-ui/uikit'; +import {settings} from '../../../libs'; +import {ChartKit} from '../../../components/ChartKit'; +import type {ChartKitRef} from '../../../types'; +import type {ChartKitWidgetData} from '../../../types/widget-data'; +import {D3Plugin} from '..'; +import penguins from './penguins.json'; + +const Template: Story = () => { + const [shown, setShown] = React.useState(false); + const chartkitRef = React.useRef(); + + const seriesData = penguins.map((item) => { + return { + category: item.island, + x: item.culmen_length_mm || 0, + y: item.body_mass_g || 0, + }; + }); + const categories = Array.from(new Set(penguins.map((item) => item.island))); + const data: ChartKitWidgetData = { + title: {text: 'Bar chart'}, + xAxis: { + type: 'category', + categories: categories, + labels: {enabled: true}, + }, + yAxis: [ + { + type: 'linear', + labels: {enabled: true}, + }, + ], + series: [ + { + type: 'bar', + visible: true, + data: seriesData.filter((item) => item.category === categories[0]), + name: 'First', + }, + { + type: 'bar', + visible: true, + data: seriesData.filter((item) => item.category !== categories[0]), + name: 'Second', + color: 'salmon', + }, + ], + legend: {enabled: true}, + tooltip: {enabled: false}, + }; + + if (!shown) { + settings.set({plugins: [D3Plugin]}); + return ; + } + + return ( +
+ +
+ ); +}; + +export const Bar = Template.bind({}); + +const meta: Meta = { + title: 'Plugins/D3', + decorators: [withKnobs], +}; + +export default meta; diff --git a/src/plugins/d3/renderer/hooks/useScales/index.ts b/src/plugins/d3/renderer/hooks/useScales/index.ts index 4987e3f1..d12ed6ba 100644 --- a/src/plugins/d3/renderer/hooks/useScales/index.ts +++ b/src/plugins/d3/renderer/hooks/useScales/index.ts @@ -42,7 +42,7 @@ export const useScales = (args: Args): ReturnValue => { const {boundsWidth, boundsHeight, series, xAxis, yAxis} = args; const scales = React.useMemo(() => { const xType = get(xAxis, 'type', 'linear'); - const xCatigories = get(xAxis, 'categories'); + const xCategories = get(xAxis, 'categories'); const xTimestamps = get(xAxis, 'timestamps'); const yType = get(yAxis[0], 'type', 'linear'); const yCatigories = get(yAxis[0], 'categories'); @@ -66,9 +66,9 @@ export const useScales = (args: Args): ReturnValue => { break; } case 'category': { - if (xCatigories) { + if (xCategories) { const filteredCategories = filterCategoriesByVisibleSeries( - xCatigories, + xCategories, visibleSeries, ); xScale = scaleBand().domain(filteredCategories).range([0, boundsWidth]); diff --git a/src/plugins/d3/renderer/hooks/useShapes/bar.tsx b/src/plugins/d3/renderer/hooks/useShapes/bar.tsx new file mode 100644 index 00000000..04ee315d --- /dev/null +++ b/src/plugins/d3/renderer/hooks/useShapes/bar.tsx @@ -0,0 +1,114 @@ +import React from 'react'; +import {ChartOptions} from '../useChartOptions/types'; +import {ChartScale} from '../useScales'; +import {OnSeriesMouseLeave, OnSeriesMouseMove} from '../useTooltip/types'; +import {BarSeries, BarSeriesData} from '../../../../../types/widget-data'; +import {ScaleBand, ScaleLinear, ScaleTime} from 'd3'; +import block from 'bem-cn-lite'; + +const DEFAULT_BAR_RECT_WIDTH = 50; + +const b = block('chartkit-d3-bar'); + +type Args = { + series: BarSeries; + xAxis: ChartOptions['xAxis']; + xScale: ChartScale; + yAxis: ChartOptions['yAxis']; + yScale: ChartScale; + onSeriesMouseMove?: OnSeriesMouseMove; + onSeriesMouseLeave?: OnSeriesMouseLeave; + key?: string; +}; + +const getRectProperties = (args: { + point: BarSeriesData; + xAxis: ChartOptions['xAxis']; + xScale: ChartScale; + yAxis: ChartOptions['yAxis']; + yScale: ChartScale; + minPointDistance: number; +}) => { + const {point, xAxis, xScale, yAxis, yScale, minPointDistance} = args; + let cx: string | number | undefined; + let cy: string | number | undefined; + let width: number; + let height: number; + + if (xAxis.type === 'category') { + const xBandScale = xScale as ScaleBand; + width = Math.min(xBandScale.bandwidth(), DEFAULT_BAR_RECT_WIDTH); + cx = (xBandScale(point.category as string) || 0) + xBandScale.step() / 2 - width / 2; + } else { + const xLinearScale = xScale as ScaleLinear | ScaleTime; + const [min, max] = xLinearScale.domain(); + const range = xLinearScale.range(); + const maxWidth = + ((range[1] - range[0]) * minPointDistance) / (Number(max) - Number(min)) - 1; + + width = Math.min(Math.max(maxWidth, 1), DEFAULT_BAR_RECT_WIDTH); + cx = xLinearScale(point.x as number) - width / 2; + } + + if (yAxis[0].type === 'category') { + const yBandScale = yScale as ScaleBand; + cy = (yBandScale(point.category as string) || 0) + yBandScale.step() / 2; + height = (yBandScale(yBandScale.domain()[0]) || 0) + yBandScale.step() - cy; + } else { + const yLinearScale = yScale as ScaleLinear | ScaleTime; + cy = yLinearScale(point.y as number); + height = yLinearScale(yLinearScale.domain()[0]) - cy; + } + + return {x: cx, y: cy, width, height}; +}; + +function minDiff(arr: number[]) { + let result = Infinity; + + for (let i = 0; i < arr.length - 1; i++) { + for (let j = i + 1; j < arr.length; j++) { + const diff = Math.abs(arr[i] - arr[j]); + if (diff < result) { + result = diff; + } + } + } + + return result; +} + +export function prepareBarSeries(args: Args) { + const {series, xAxis, xScale, yAxis, yScale, onSeriesMouseMove, onSeriesMouseLeave, key} = args; + const preparedData = series.data; + const minPointDistance = minDiff(preparedData.map((item) => Number(item.x))); + + return preparedData.map((point, i) => { + const rectProps = getRectProperties({ + point, + xAxis, + xScale, + yAxis, + yScale, + minPointDistance, + }); + + return ( + + ); + }); +} diff --git a/src/plugins/d3/renderer/hooks/useShapes/index.tsx b/src/plugins/d3/renderer/hooks/useShapes/index.tsx index 51b53669..55e05113 100644 --- a/src/plugins/d3/renderer/hooks/useShapes/index.tsx +++ b/src/plugins/d3/renderer/hooks/useShapes/index.tsx @@ -10,6 +10,7 @@ import type {ChartOptions} from '../useChartOptions/types'; import type {ChartScale} from '../useScales'; import type {ChartSeries} from '../useSeries'; import type {OnSeriesMouseMove, OnSeriesMouseLeave} from '../useTooltip/types'; +import {prepareBarSeries} from './bar'; type Args = { series: ChartSeries[]; @@ -47,7 +48,7 @@ const getPointProperties = (args: { if (xAxis.type === 'category') { const xBandScale = xScale as ScaleBand; - cx = xBandScale(point.category as string)! + xBandScale.step() / 2; + cx = (xBandScale(point.category as string) || 0) + xBandScale.step() / 2; } else { const xLinearScale = xScale as ScaleLinear | ScaleTime; cx = xLinearScale(point.x as number); @@ -55,10 +56,10 @@ const getPointProperties = (args: { if (yAxis[0].type === 'category') { const yBandScale = yScale as ScaleBand; - cy = yBandScale(point.category as string)! + yBandScale.step() / 2; + cy = (yBandScale(point.category as string) || 0) + yBandScale.step() / 2; } else { - const xLinearScale = yScale as ScaleLinear | ScaleTime; - cy = xLinearScale(point.y as number); + const yLinearScale = yScale as ScaleLinear | ScaleTime; + cy = yLinearScale(point.y as number); } return {r, cx, cy}; @@ -81,6 +82,20 @@ export const useShapes = (args: Args) => { return visibleSeries.reduce((acc, s) => { const randomKey = Math.random().toString(); switch (s.type) { + case 'bar': { + acc.push( + ...prepareBarSeries({ + series: s, + xAxis, + xScale, + yAxis, + yScale, + onSeriesMouseMove, + onSeriesMouseLeave, + }), + ); + break; + } case 'scatter': { const preparedData = xAxis.type === 'category' || yAxis[0]?.type === 'category' diff --git a/src/types/widget-data/bar.ts b/src/types/widget-data/bar.ts new file mode 100644 index 00000000..e90a0ee5 --- /dev/null +++ b/src/types/widget-data/bar.ts @@ -0,0 +1,19 @@ +import type {BaseSeries, BaseSeriesData} from './base'; + +export type BarSeriesData = BaseSeriesData & { + /** The x value of the point */ + x?: number; + /** The y value of the point */ + y?: number; + /** Corresponding value of axis category */ + category?: string; +}; + +export type BarSeries = BaseSeries & { + type: 'bar'; + data: BarSeriesData[]; + /** The name of the series (used in legend) */ + name: string; + /** The main color of the series (hex, rgba) */ + color?: string; +}; diff --git a/src/types/widget-data/index.ts b/src/types/widget-data/index.ts index 3c1d1a83..5576459b 100644 --- a/src/types/widget-data/index.ts +++ b/src/types/widget-data/index.ts @@ -11,6 +11,7 @@ export * from './chart'; export * from './legend'; export * from './pie'; export * from './scatter'; +export * from './bar'; export * from './series'; export * from './title'; export * from './tooltip'; diff --git a/src/types/widget-data/series.ts b/src/types/widget-data/series.ts index 2e2d6fd5..97670e86 100644 --- a/src/types/widget-data/series.ts +++ b/src/types/widget-data/series.ts @@ -1,6 +1,10 @@ import type {PieSeries, PieSeriesData} from './pie'; import type {ScatterSeries, ScatterSeriesData} from './scatter'; +import type {BarSeries, BarSeriesData} from './bar'; -export type ChartKitWidgetSeries = ScatterSeries | PieSeries; +export type ChartKitWidgetSeries = ScatterSeries | PieSeries | BarSeries; -export type ChartKitWidgetSeriesData = ScatterSeriesData | PieSeriesData; +export type ChartKitWidgetSeriesData = + | ScatterSeriesData + | PieSeriesData + | BarSeriesData; From cdc7d2dfd2c2ff5771f7cad4f02d82e068c5f1b3 Mon Sep 17 00:00:00 2001 From: "Irina V. Kuzmina" Date: Wed, 16 Aug 2023 16:41:01 +0300 Subject: [PATCH 2/5] fix bar --- src/plugins/d3/__stories__/Bar.stories.tsx | 153 +++++++++++++----- .../__stories__/LinearCategories.stories.tsx | 4 +- .../d3/__stories__/Timestamp.stories.tsx | 4 +- src/plugins/d3/renderer/components/Chart.tsx | 6 +- .../renderer/hooks/useChartOptions/chart.ts | 2 +- .../renderer/hooks/useChartOptions/types.ts | 1 + .../renderer/hooks/useChartOptions/y-axis.ts | 1 + .../d3/renderer/hooks/useScales/index.ts | 15 +- .../d3/renderer/hooks/useShapes/bar.tsx | 88 +++++----- .../d3/renderer/hooks/useShapes/index.tsx | 98 ++--------- .../d3/renderer/hooks/useShapes/scatter.tsx | 115 +++++++++++++ src/types/widget-data/bar.ts | 23 +++ src/types/widget-data/index.ts | 7 +- src/types/widget-data/series.ts | 17 ++ 14 files changed, 362 insertions(+), 172 deletions(-) create mode 100644 src/plugins/d3/renderer/hooks/useShapes/scatter.tsx diff --git a/src/plugins/d3/__stories__/Bar.stories.tsx b/src/plugins/d3/__stories__/Bar.stories.tsx index b5c4602e..bf3cf7d0 100644 --- a/src/plugins/d3/__stories__/Bar.stories.tsx +++ b/src/plugins/d3/__stories__/Bar.stories.tsx @@ -1,66 +1,145 @@ import React from 'react'; import {Meta, Story} from '@storybook/react'; -import {withKnobs, object} from '@storybook/addon-knobs'; +import {withKnobs} from '@storybook/addon-knobs'; import {Button} from '@gravity-ui/uikit'; import {settings} from '../../../libs'; import {ChartKit} from '../../../components/ChartKit'; import type {ChartKitRef} from '../../../types'; import type {ChartKitWidgetData} from '../../../types/widget-data'; import {D3Plugin} from '..'; -import penguins from './penguins.json'; const Template: Story = () => { const [shown, setShown] = React.useState(false); const chartkitRef = React.useRef(); - - const seriesData = penguins.map((item) => { - return { - category: item.island, - x: item.culmen_length_mm || 0, - y: item.body_mass_g || 0, - }; - }); - const categories = Array.from(new Set(penguins.map((item) => item.island))); - const data: ChartKitWidgetData = { - title: {text: 'Bar chart'}, - xAxis: { - type: 'category', - categories: categories, - labels: {enabled: true}, - }, + const chartBaseOptions = { + legend: {enabled: true}, + tooltip: {enabled: false}, yAxis: [ { type: 'linear', labels: {enabled: true}, + min: 0, }, ], - series: [ - { - type: 'bar', - visible: true, - data: seriesData.filter((item) => item.category === categories[0]), - name: 'First', - }, - { - type: 'bar', - visible: true, - data: seriesData.filter((item) => item.category !== categories[0]), - name: 'Second', - color: 'salmon', - }, - ], - legend: {enabled: true}, - tooltip: {enabled: false}, + series: { + data: [ + { + type: 'bar', + visible: true, + data: [ + { + category: 'A', + x: 10, + y: 100, + }, + { + category: 'B', + x: 12, + y: 80, + }, + ], + name: 'AB', + }, + { + type: 'bar', + visible: true, + data: [ + { + category: 'C', + x: 95.5, + y: 120, + }, + ], + name: 'C', + color: 'salmon', + }, + ], + }, }; + const categoryXAxis = { + ...chartBaseOptions, + title: {text: 'Category axis'}, + xAxis: { + type: 'category', + categories: ['A', 'B', 'C'], + labels: {enabled: true}, + }, + } as ChartKitWidgetData; + + const linearXAxis = { + ...chartBaseOptions, + title: {text: 'Linear axis'}, + xAxis: { + type: 'linear', + labels: {enabled: true}, + }, + } as ChartKitWidgetData; + + const dateTimeXAxis = { + ...chartBaseOptions, + title: {text: 'DateTime axis'}, + xAxis: { + type: 'datetime', + labels: {enabled: true}, + }, + series: { + data: [ + { + type: 'bar', + visible: true, + data: [ + { + x: Number(new Date(2022, 10, 10)), + y: 100, + }, + { + x: Number(new Date(2023, 2, 5)), + y: 80, + }, + ], + name: 'AB', + }, + { + type: 'bar', + visible: true, + data: [ + { + x: Number(new Date(2022, 11, 25)), + y: 120, + }, + ], + name: 'C', + color: 'salmon', + }, + ], + }, + } as ChartKitWidgetData; + if (!shown) { settings.set({plugins: [D3Plugin]}); return ; } return ( -
- +
+
+ +
+
+ +
+
+ +
); }; diff --git a/src/plugins/d3/__stories__/LinearCategories.stories.tsx b/src/plugins/d3/__stories__/LinearCategories.stories.tsx index 4533b63e..1c6cf6a7 100644 --- a/src/plugins/d3/__stories__/LinearCategories.stories.tsx +++ b/src/plugins/d3/__stories__/LinearCategories.stories.tsx @@ -89,7 +89,9 @@ const shapeScatterChartData = ( } return { - series, + series: { + data: series, + }, xAxis, yAxis: [yAxis], title: { diff --git a/src/plugins/d3/__stories__/Timestamp.stories.tsx b/src/plugins/d3/__stories__/Timestamp.stories.tsx index ded0c5af..5dd2daea 100644 --- a/src/plugins/d3/__stories__/Timestamp.stories.tsx +++ b/src/plugins/d3/__stories__/Timestamp.stories.tsx @@ -66,7 +66,9 @@ const shapeData = (data: Record[]): ChartKitWidgetData => { }; return { - series: [scatterSeries], + series: { + data: [scatterSeries], + }, xAxis: { type: 'datetime', timestamps: data.map((d) => d.x), diff --git a/src/plugins/d3/renderer/components/Chart.tsx b/src/plugins/d3/renderer/components/Chart.tsx index 0e65e1f4..7fd70cbf 100644 --- a/src/plugins/d3/renderer/components/Chart.tsx +++ b/src/plugins/d3/renderer/components/Chart.tsx @@ -34,7 +34,7 @@ export const Chart = ({width, height, data}: Props) => { // FIXME: add data validation const {series} = data; const svgRef = React.createRef(); - const hasAxisRelatedSeries = series.some(isAxisRelatedSeries); + const hasAxisRelatedSeries = series.data.some(isAxisRelatedSeries); const {chartHovered, handleMouseEnter, handleMouseLeave} = useChartEvents(); const {chart, legend, title, tooltip, xAxis, yAxis} = useChartOptions(data); const {boundsWidth, boundsHeight, legendHeight} = useChartDimensions({ @@ -46,8 +46,8 @@ export const Chart = ({width, height, data}: Props) => { xAxis, yAxis, }); - const {activeLegendItems, handleLegendItemClick} = useLegend({series}); - const {chartSeries} = useSeries({activeLegendItems, series}); + const {activeLegendItems, handleLegendItemClick} = useLegend({series: series.data}); + const {chartSeries} = useSeries({activeLegendItems, series: series.data}); const {xScale, yScale} = useScales({ boundsWidth, boundsHeight, diff --git a/src/plugins/d3/renderer/hooks/useChartOptions/chart.ts b/src/plugins/d3/renderer/hooks/useChartOptions/chart.ts index e4ece6a8..d4b1ffd3 100644 --- a/src/plugins/d3/renderer/hooks/useChartOptions/chart.ts +++ b/src/plugins/d3/renderer/hooks/useChartOptions/chart.ts @@ -67,7 +67,7 @@ export const getPreparedChart = (args: { const marginLeft = get(chart, 'margin.left', AXIS_WIDTH) + preparedY1Axis.labels.padding + - getAxisLabelMaxWidth({axis: preparedY1Axis, series}) + + getAxisLabelMaxWidth({axis: preparedY1Axis, series: series.data}) + (preparedY1Axis.title.height || 0); return { diff --git a/src/plugins/d3/renderer/hooks/useChartOptions/types.ts b/src/plugins/d3/renderer/hooks/useChartOptions/types.ts index 42f79753..ff2399f8 100644 --- a/src/plugins/d3/renderer/hooks/useChartOptions/types.ts +++ b/src/plugins/d3/renderer/hooks/useChartOptions/types.ts @@ -27,6 +27,7 @@ export type PreparedAxis = Omit & { text: string; style: BaseTextStyle; }; + min?: number; }; export type PreparedTitle = ChartKitWidgetData['title'] & { diff --git a/src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts b/src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts index 0bc497c4..4426475c 100644 --- a/src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts +++ b/src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts @@ -39,6 +39,7 @@ export const getPreparedYAxis = ({yAxis}: {yAxis: ChartKitWidgetData['yAxis']}): ? getHorisontalSvgTextDimensions({text: y1TitleText, style: y1TitleStyle}) : 0, }, + min: get(yAxis1, 'min'), }; return [preparedY1Axis]; diff --git a/src/plugins/d3/renderer/hooks/useScales/index.ts b/src/plugins/d3/renderer/hooks/useScales/index.ts index d12ed6ba..6d1c6335 100644 --- a/src/plugins/d3/renderer/hooks/useScales/index.ts +++ b/src/plugins/d3/renderer/hooks/useScales/index.ts @@ -45,7 +45,8 @@ export const useScales = (args: Args): ReturnValue => { const xCategories = get(xAxis, 'categories'); const xTimestamps = get(xAxis, 'timestamps'); const yType = get(yAxis[0], 'type', 'linear'); - const yCatigories = get(yAxis[0], 'categories'); + const yMin = get(yAxis[0], 'min'); + const yCategories = get(yAxis[0], 'categories'); const yTimestamps = get(xAxis, 'timestamps'); let visibleSeries = getOnlyVisibleSeries(series); // Reassign to all series in case of all series unselected, @@ -102,16 +103,20 @@ export const useScales = (args: Args): ReturnValue => { const domain = getDomainDataYBySeries(visibleSeries); if (isNumericalArrayData(domain)) { - const [yMin, yMax] = extent(domain) as [number, number]; - yScale = scaleLinear().domain([yMin, yMax]).range([boundsHeight, 0]).nice(); + const [domainYMin, yMax] = extent(domain) as [number, number]; + const yMinValue = typeof yMin === 'number' ? yMin : domainYMin; + yScale = scaleLinear() + .domain([yMinValue, yMax]) + .range([boundsHeight, 0]) + .nice(); } break; } case 'category': { - if (yCatigories) { + if (yCategories) { const filteredCategories = filterCategoriesByVisibleSeries( - yCatigories, + yCategories, visibleSeries, ); yScale = scaleBand().domain(filteredCategories).range([boundsHeight, 0]); diff --git a/src/plugins/d3/renderer/hooks/useShapes/bar.tsx b/src/plugins/d3/renderer/hooks/useShapes/bar.tsx index 04ee315d..0303ffd3 100644 --- a/src/plugins/d3/renderer/hooks/useShapes/bar.tsx +++ b/src/plugins/d3/renderer/hooks/useShapes/bar.tsx @@ -7,18 +7,19 @@ import {ScaleBand, ScaleLinear, ScaleTime} from 'd3'; import block from 'bem-cn-lite'; const DEFAULT_BAR_RECT_WIDTH = 50; +const DEFAULT_LINEAR_BAR_RECT_WIDTH = 20; +const MIN_RECT_GAP = 1; const b = block('chartkit-d3-bar'); type Args = { - series: BarSeries; + series: BarSeries[]; xAxis: ChartOptions['xAxis']; xScale: ChartScale; yAxis: ChartOptions['yAxis']; yScale: ChartScale; onSeriesMouseMove?: OnSeriesMouseMove; onSeriesMouseLeave?: OnSeriesMouseLeave; - key?: string; }; const getRectProperties = (args: { @@ -37,27 +38,26 @@ const getRectProperties = (args: { if (xAxis.type === 'category') { const xBandScale = xScale as ScaleBand; - width = Math.min(xBandScale.bandwidth(), DEFAULT_BAR_RECT_WIDTH); + const maxWidth = xBandScale.bandwidth() - MIN_RECT_GAP; + width = Math.min(maxWidth, DEFAULT_BAR_RECT_WIDTH); cx = (xBandScale(point.category as string) || 0) + xBandScale.step() / 2 - width / 2; } else { const xLinearScale = xScale as ScaleLinear | ScaleTime; const [min, max] = xLinearScale.domain(); const range = xLinearScale.range(); const maxWidth = - ((range[1] - range[0]) * minPointDistance) / (Number(max) - Number(min)) - 1; + ((range[1] - range[0]) * minPointDistance) / (Number(max) - Number(min)) - MIN_RECT_GAP; - width = Math.min(Math.max(maxWidth, 1), DEFAULT_BAR_RECT_WIDTH); + width = Math.min(Math.max(maxWidth, 1), DEFAULT_LINEAR_BAR_RECT_WIDTH); cx = xLinearScale(point.x as number) - width / 2; } - if (yAxis[0].type === 'category') { - const yBandScale = yScale as ScaleBand; - cy = (yBandScale(point.category as string) || 0) + yBandScale.step() / 2; - height = (yBandScale(yBandScale.domain()[0]) || 0) + yBandScale.step() - cy; - } else { - const yLinearScale = yScale as ScaleLinear | ScaleTime; + if (yAxis[0].type === 'linear') { + const yLinearScale = yScale as ScaleLinear; cy = yLinearScale(point.y as number); height = yLinearScale(yLinearScale.domain()[0]) - cy; + } else { + throw Error(`The "${yAxis[0].type}" type for the Y axis is not supported`); } return {x: cx, y: cy, width, height}; @@ -79,36 +79,42 @@ function minDiff(arr: number[]) { } export function prepareBarSeries(args: Args) { - const {series, xAxis, xScale, yAxis, yScale, onSeriesMouseMove, onSeriesMouseLeave, key} = args; - const preparedData = series.data; - const minPointDistance = minDiff(preparedData.map((item) => Number(item.x))); - - return preparedData.map((point, i) => { - const rectProps = getRectProperties({ - point, - xAxis, - xScale, - yAxis, - yScale, - minPointDistance, + const {series, xAxis, xScale, yAxis, yScale, onSeriesMouseMove, onSeriesMouseLeave} = args; + const seriesData = series.map(({data}) => data).flat(2); + const minPointDistance = minDiff(seriesData.map((item) => Number(item.x))); + + return series.reduce((result, item) => { + const randomKey = Math.random().toString(); + + item.data.forEach((point, i) => { + const rectProps = getRectProperties({ + point, + xAxis, + xScale, + yAxis, + yScale, + minPointDistance, + }); + + result.push( + , + ); }); - return ( - - ); - }); + return result; + }, []); } diff --git a/src/plugins/d3/renderer/hooks/useShapes/index.tsx b/src/plugins/d3/renderer/hooks/useShapes/index.tsx index 55e05113..8624d0d4 100644 --- a/src/plugins/d3/renderer/hooks/useShapes/index.tsx +++ b/src/plugins/d3/renderer/hooks/useShapes/index.tsx @@ -1,9 +1,7 @@ import React from 'react'; -import block from 'bem-cn-lite'; -import {pointer} from 'd3'; -import type {ScaleBand, ScaleLinear, ScaleTime} from 'd3'; +import {group} from 'd3'; -import type {ScatterSeriesData} from '../../../../../types/widget-data'; +import type {BarSeries, ScatterSeries} from '../../../../../types/widget-data'; import {getOnlyVisibleSeries} from '../../utils'; import type {ChartOptions} from '../useChartOptions/types'; @@ -11,6 +9,7 @@ import type {ChartScale} from '../useScales'; import type {ChartSeries} from '../useSeries'; import type {OnSeriesMouseMove, OnSeriesMouseLeave} from '../useTooltip/types'; import {prepareBarSeries} from './bar'; +import {prepareScatterSeries} from './scatter'; type Args = { series: ChartSeries[]; @@ -23,48 +22,6 @@ type Args = { onSeriesMouseLeave?: OnSeriesMouseLeave; }; -const b = block('chartkit-d3-scatter'); -const DEFAULT_SCATTER_POINT_RADIUS = 4; - -const prepareCategoricalScatterData = (data: ScatterSeriesData[]) => { - return data.filter((d) => typeof d.category === 'string'); -}; - -const prepareLinearScatterData = (data: ScatterSeriesData[]) => { - return data.filter((d) => typeof d.x === 'number' && typeof d.y === 'number'); -}; - -const getPointProperties = (args: { - point: ScatterSeriesData; - xAxis: ChartOptions['xAxis']; - xScale: ChartScale; - yAxis: ChartOptions['yAxis']; - yScale: ChartScale; -}) => { - const {point, xAxis, xScale, yAxis, yScale} = args; - const r = point.radius || DEFAULT_SCATTER_POINT_RADIUS; - let cx: string | number | undefined; - let cy: string | number | undefined; - - if (xAxis.type === 'category') { - const xBandScale = xScale as ScaleBand; - cx = (xBandScale(point.category as string) || 0) + xBandScale.step() / 2; - } else { - const xLinearScale = xScale as ScaleLinear | ScaleTime; - cx = xLinearScale(point.x as number); - } - - if (yAxis[0].type === 'category') { - const yBandScale = yScale as ScaleBand; - cy = (yBandScale(point.category as string) || 0) + yBandScale.step() / 2; - } else { - const yLinearScale = yScale as ScaleLinear | ScaleTime; - cy = yLinearScale(point.y as number); - } - - return {r, cx, cy}; -}; - export const useShapes = (args: Args) => { const { series, @@ -78,14 +35,15 @@ export const useShapes = (args: Args) => { } = args; const shapes = React.useMemo(() => { const visibleSeries = getOnlyVisibleSeries(series); + const groupedSeries = group(visibleSeries, (item) => item.type); - return visibleSeries.reduce((acc, s) => { - const randomKey = Math.random().toString(); - switch (s.type) { + return Array.from(groupedSeries).reduce((acc, item) => { + const [seriesType, chartSeries] = item; + switch (seriesType) { case 'bar': { acc.push( ...prepareBarSeries({ - series: s, + series: chartSeries as BarSeries[], xAxis, xScale, yAxis, @@ -97,38 +55,16 @@ export const useShapes = (args: Args) => { break; } case 'scatter': { - const preparedData = - xAxis.type === 'category' || yAxis[0]?.type === 'category' - ? prepareCategoricalScatterData(s.data) - : prepareLinearScatterData(s.data); acc.push( - ...preparedData.map((point, i) => { - const pointProps = getPointProperties({ - point, - xAxis, - xScale, - yAxis, - yScale, - }); - - return ( - - ); + ...prepareScatterSeries({ + series: chartSeries as ScatterSeries[], + xAxis, + xScale, + yAxis, + yScale, + onSeriesMouseMove, + onSeriesMouseLeave, + svgContainer, }), ); break; diff --git a/src/plugins/d3/renderer/hooks/useShapes/scatter.tsx b/src/plugins/d3/renderer/hooks/useShapes/scatter.tsx new file mode 100644 index 00000000..238e8be6 --- /dev/null +++ b/src/plugins/d3/renderer/hooks/useShapes/scatter.tsx @@ -0,0 +1,115 @@ +import {pointer, ScaleBand, ScaleLinear, ScaleTime} from 'd3'; +import React from 'react'; +import {ChartOptions} from '../useChartOptions/types'; +import {ChartScale} from '../useScales'; +import {OnSeriesMouseLeave, OnSeriesMouseMove} from '../useTooltip/types'; +import block from 'bem-cn-lite'; +import {ScatterSeries, ScatterSeriesData} from '../../../../../types/widget-data'; + +type PrepareScatterSeriesArgs = { + series: ScatterSeries[]; + xAxis: ChartOptions['xAxis']; + xScale: ChartScale; + yAxis: ChartOptions['yAxis']; + yScale: ChartScale; + svgContainer: SVGSVGElement | null; + onSeriesMouseMove?: OnSeriesMouseMove; + onSeriesMouseLeave?: OnSeriesMouseLeave; + key?: string; +}; + +const b = block('chartkit-d3-scatter'); +const DEFAULT_SCATTER_POINT_RADIUS = 4; + +const prepareCategoricalScatterData = (data: ScatterSeriesData[]) => { + return data.filter((d) => typeof d.category === 'string'); +}; + +const prepareLinearScatterData = (data: ScatterSeriesData[]) => { + return data.filter((d) => typeof d.x === 'number' && typeof d.y === 'number'); +}; + +const getPointProperties = (args: { + point: ScatterSeriesData; + xAxis: ChartOptions['xAxis']; + xScale: ChartScale; + yAxis: ChartOptions['yAxis']; + yScale: ChartScale; +}) => { + const {point, xAxis, xScale, yAxis, yScale} = args; + const r = point.radius || DEFAULT_SCATTER_POINT_RADIUS; + let cx: string | number | undefined; + let cy: string | number | undefined; + + if (xAxis.type === 'category') { + const xBandScale = xScale as ScaleBand; + cx = (xBandScale(point.category as string) || 0) + xBandScale.step() / 2; + } else { + const xLinearScale = xScale as ScaleLinear | ScaleTime; + cx = xLinearScale(point.x as number); + } + + if (yAxis[0].type === 'category') { + const yBandScale = yScale as ScaleBand; + cy = (yBandScale(point.category as string) || 0) + yBandScale.step() / 2; + } else { + const yLinearScale = yScale as ScaleLinear | ScaleTime; + cy = yLinearScale(point.y as number); + } + + return {r, cx, cy}; +}; + +export function prepareScatterSeries(args: PrepareScatterSeriesArgs) { + const { + series, + xAxis, + xScale, + yAxis, + yScale, + onSeriesMouseMove, + onSeriesMouseLeave, + key, + svgContainer, + } = args; + + return series.reduce((result, s) => { + const preparedData = + xAxis.type === 'category' || yAxis[0]?.type === 'category' + ? prepareCategoricalScatterData(s.data) + : prepareLinearScatterData(s.data); + + result.push( + ...preparedData.map((point, i) => { + const pointProps = getPointProperties({ + point, + xAxis, + xScale, + yAxis, + yScale, + }); + + return ( + + ); + }), + ); + + return result; + }, []); +} diff --git a/src/types/widget-data/bar.ts b/src/types/widget-data/bar.ts index e90a0ee5..3ebc4028 100644 --- a/src/types/widget-data/bar.ts +++ b/src/types/widget-data/bar.ts @@ -1,4 +1,5 @@ import type {BaseSeries, BaseSeriesData} from './base'; +import type {ChartKitWidgetSeriesOptions} from './series'; export type BarSeriesData = BaseSeriesData & { /** The x value of the point */ @@ -12,8 +13,30 @@ export type BarSeriesData = BaseSeriesData & { export type BarSeries = BaseSeries & { type: 'bar'; data: BarSeriesData[]; + /** The name of the series (used in legend) */ name: string; + /** The main color of the series (hex, rgba) */ color?: string; + + // todo + stacking?: 'normal' | 'percent'; + + // todo + /** This option allows grouping series in a stacked chart */ + stackId?: string; + + // todo + /** Whether to group non-stacked columns or to let them render independent of each other. + * When false columns will be laid out individually and overlap each other. + * + * @default true + * */ + grouping?: boolean; + + dataLabels?: ChartKitWidgetSeriesOptions['dataLabels'] & { + /** Whether to align the data label inside the box or to the actual value point */ + inside?: boolean; + }; }; diff --git a/src/types/widget-data/index.ts b/src/types/widget-data/index.ts index 5576459b..969231ef 100644 --- a/src/types/widget-data/index.ts +++ b/src/types/widget-data/index.ts @@ -1,7 +1,7 @@ import type {ChartKitWidgetAxis} from './axis'; import type {ChartKitWidgetChart} from './chart'; import type {ChartKitWidgetLegend} from './legend'; -import type {ChartKitWidgetSeries} from './series'; +import type {ChartKitWidgetSeries, ChartKitWidgetSeriesOptions} from './series'; import type {ChartKitWidgetTitle} from './title'; import type {ChartKitWidgetTooltip} from './tooltip'; @@ -19,7 +19,10 @@ export * from './tooltip'; export type ChartKitWidgetData = { chart?: ChartKitWidgetChart; legend?: ChartKitWidgetLegend; - series: ChartKitWidgetSeries[]; + series: { + data: ChartKitWidgetSeries[]; + options?: ChartKitWidgetSeriesOptions; + }; title?: ChartKitWidgetTitle; tooltip?: ChartKitWidgetTooltip; xAxis?: ChartKitWidgetAxis; diff --git a/src/types/widget-data/series.ts b/src/types/widget-data/series.ts index 97670e86..660be5aa 100644 --- a/src/types/widget-data/series.ts +++ b/src/types/widget-data/series.ts @@ -1,6 +1,7 @@ import type {PieSeries, PieSeriesData} from './pie'; import type {ScatterSeries, ScatterSeriesData} from './scatter'; import type {BarSeries, BarSeriesData} from './bar'; +import React from 'react'; export type ChartKitWidgetSeries = ScatterSeries | PieSeries | BarSeries; @@ -8,3 +9,19 @@ export type ChartKitWidgetSeriesData = | ScatterSeriesData | PieSeriesData | BarSeriesData; + +export type DataLabelRendererData = { + data: ChartKitWidgetSeriesData; +}; + +export type ChartKitWidgetSeriesOptions = { + // todo + /** Individual data label for each point. */ + dataLabels?: { + /** Enable or disable the data labels */ + enabled?: boolean; + + /** Callback function to render the data label */ + renderer?: (args: DataLabelRendererData) => React.SVGTextElementAttributes; + }; +}; From 2e99ed0d83d9a2bc5ab401d080035a78e31c14e1 Mon Sep 17 00:00:00 2001 From: "Irina V. Kuzmina" Date: Wed, 16 Aug 2023 16:43:02 +0300 Subject: [PATCH 3/5] add min options to axis --- src/types/widget-data/axis.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/types/widget-data/axis.ts b/src/types/widget-data/axis.ts index a4c330dc..f094f007 100644 --- a/src/types/widget-data/axis.ts +++ b/src/types/widget-data/axis.ts @@ -22,4 +22,7 @@ export type ChartKitWidgetAxis = { title?: { text?: string; }; + + /** The minimum value of the axis. If undefined the min value is automatically calculate */ + min?: number; }; From a019e1d70008172e71913bf60d604367cb5200d2 Mon Sep 17 00:00:00 2001 From: "Irina V. Kuzmina" Date: Wed, 16 Aug 2023 16:50:29 +0300 Subject: [PATCH 4/5] fix getPreparedLegend --- src/plugins/d3/renderer/hooks/useChartOptions/legend.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/plugins/d3/renderer/hooks/useChartOptions/legend.ts b/src/plugins/d3/renderer/hooks/useChartOptions/legend.ts index 899ca737..a1f9ee33 100644 --- a/src/plugins/d3/renderer/hooks/useChartOptions/legend.ts +++ b/src/plugins/d3/renderer/hooks/useChartOptions/legend.ts @@ -1,5 +1,3 @@ -import get from 'lodash/get'; - import type {ChartKitWidgetData} from '../../../../../types/widget-data'; import type {PreparedLegend} from './types'; @@ -9,5 +7,9 @@ export const getPreparedLegend = (args: { series: ChartKitWidgetData['series']; }): PreparedLegend => { const {legend, series} = args; - return {enabled: get(legend, 'enabled', true) && series.length > 1}; + const enabled = legend?.enabled; + + return { + enabled: typeof enabled === 'boolean' ? enabled : series.data.length > 1, + }; }; From f0c7eb3b6de8686e6d45f27bc854b4c2bde085df Mon Sep 17 00:00:00 2001 From: "Irina V. Kuzmina" Date: Wed, 16 Aug 2023 18:06:17 +0300 Subject: [PATCH 5/5] fix stories --- src/plugins/d3/__stories__/Bar.stories.tsx | 154 ------------------ .../d3/__stories__/bar/category.stories.tsx | 89 ++++++++++ .../d3/__stories__/bar/datetime.stories.tsx | 85 ++++++++++ .../d3/__stories__/bar/linear.stories.tsx | 88 ++++++++++ .../LinearCategories.stories.tsx | 14 +- .../{ => scatter}/Timestamp.stories.tsx | 12 +- 6 files changed, 275 insertions(+), 167 deletions(-) delete mode 100644 src/plugins/d3/__stories__/Bar.stories.tsx create mode 100644 src/plugins/d3/__stories__/bar/category.stories.tsx create mode 100644 src/plugins/d3/__stories__/bar/datetime.stories.tsx create mode 100644 src/plugins/d3/__stories__/bar/linear.stories.tsx rename src/plugins/d3/__stories__/{ => scatter}/LinearCategories.stories.tsx (93%) rename src/plugins/d3/__stories__/{ => scatter}/Timestamp.stories.tsx (90%) diff --git a/src/plugins/d3/__stories__/Bar.stories.tsx b/src/plugins/d3/__stories__/Bar.stories.tsx deleted file mode 100644 index bf3cf7d0..00000000 --- a/src/plugins/d3/__stories__/Bar.stories.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import React from 'react'; -import {Meta, Story} from '@storybook/react'; -import {withKnobs} from '@storybook/addon-knobs'; -import {Button} from '@gravity-ui/uikit'; -import {settings} from '../../../libs'; -import {ChartKit} from '../../../components/ChartKit'; -import type {ChartKitRef} from '../../../types'; -import type {ChartKitWidgetData} from '../../../types/widget-data'; -import {D3Plugin} from '..'; - -const Template: Story = () => { - const [shown, setShown] = React.useState(false); - const chartkitRef = React.useRef(); - const chartBaseOptions = { - legend: {enabled: true}, - tooltip: {enabled: false}, - yAxis: [ - { - type: 'linear', - labels: {enabled: true}, - min: 0, - }, - ], - series: { - data: [ - { - type: 'bar', - visible: true, - data: [ - { - category: 'A', - x: 10, - y: 100, - }, - { - category: 'B', - x: 12, - y: 80, - }, - ], - name: 'AB', - }, - { - type: 'bar', - visible: true, - data: [ - { - category: 'C', - x: 95.5, - y: 120, - }, - ], - name: 'C', - color: 'salmon', - }, - ], - }, - }; - - const categoryXAxis = { - ...chartBaseOptions, - title: {text: 'Category axis'}, - xAxis: { - type: 'category', - categories: ['A', 'B', 'C'], - labels: {enabled: true}, - }, - } as ChartKitWidgetData; - - const linearXAxis = { - ...chartBaseOptions, - title: {text: 'Linear axis'}, - xAxis: { - type: 'linear', - labels: {enabled: true}, - }, - } as ChartKitWidgetData; - - const dateTimeXAxis = { - ...chartBaseOptions, - title: {text: 'DateTime axis'}, - xAxis: { - type: 'datetime', - labels: {enabled: true}, - }, - series: { - data: [ - { - type: 'bar', - visible: true, - data: [ - { - x: Number(new Date(2022, 10, 10)), - y: 100, - }, - { - x: Number(new Date(2023, 2, 5)), - y: 80, - }, - ], - name: 'AB', - }, - { - type: 'bar', - visible: true, - data: [ - { - x: Number(new Date(2022, 11, 25)), - y: 120, - }, - ], - name: 'C', - color: 'salmon', - }, - ], - }, - } as ChartKitWidgetData; - - if (!shown) { - settings.set({plugins: [D3Plugin]}); - return ; - } - - return ( -
-
- -
-
- -
-
- -
-
- ); -}; - -export const Bar = Template.bind({}); - -const meta: Meta = { - title: 'Plugins/D3', - decorators: [withKnobs], -}; - -export default meta; diff --git a/src/plugins/d3/__stories__/bar/category.stories.tsx b/src/plugins/d3/__stories__/bar/category.stories.tsx new file mode 100644 index 00000000..22026c60 --- /dev/null +++ b/src/plugins/d3/__stories__/bar/category.stories.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import {Meta, Story} from '@storybook/react'; +import {object, withKnobs} from '@storybook/addon-knobs'; +import {Button} from '@gravity-ui/uikit'; +import {settings} from '../../../../libs'; +import {ChartKit} from '../../../../components/ChartKit'; +import type {ChartKitRef} from '../../../../types'; +import type {ChartKitWidgetData} from '../../../../types/widget-data'; +import {D3Plugin} from '../..'; + +const Template: Story = () => { + const [shown, setShown] = React.useState(false); + const chartkitRef = React.useRef(); + const data: ChartKitWidgetData = { + legend: {enabled: true}, + tooltip: {enabled: false}, + yAxis: [ + { + type: 'linear', + labels: {enabled: true}, + min: 0, + }, + ], + series: { + data: [ + { + type: 'bar', + visible: true, + data: [ + { + category: 'A', + x: 10, + y: 100, + }, + { + category: 'B', + x: 12, + y: 80, + }, + ], + name: 'AB', + }, + { + type: 'bar', + visible: true, + data: [ + { + category: 'C', + x: 95.5, + y: 120, + }, + ], + name: 'C', + }, + ], + }, + title: {text: 'Category axis'}, + xAxis: { + type: 'category', + categories: ['A', 'B', 'C'], + labels: {enabled: true}, + }, + }; + + if (!shown) { + settings.set({plugins: [D3Plugin]}); + return ; + } + + return ( +
+ ('data', data)} /> +
+ ); +}; + +export const CategoryXAxis = Template.bind({}); + +const meta: Meta = { + title: 'Plugins/D3/Bar', + decorators: [withKnobs], +}; + +export default meta; diff --git a/src/plugins/d3/__stories__/bar/datetime.stories.tsx b/src/plugins/d3/__stories__/bar/datetime.stories.tsx new file mode 100644 index 00000000..5de3fbd2 --- /dev/null +++ b/src/plugins/d3/__stories__/bar/datetime.stories.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import {Meta, Story} from '@storybook/react'; +import {object, withKnobs} from '@storybook/addon-knobs'; +import {Button} from '@gravity-ui/uikit'; +import {settings} from '../../../../libs'; +import {ChartKit} from '../../../../components/ChartKit'; +import type {ChartKitRef} from '../../../../types'; +import type {ChartKitWidgetData} from '../../../../types/widget-data'; +import {D3Plugin} from '../..'; + +const Template: Story = () => { + const [shown, setShown] = React.useState(false); + const chartkitRef = React.useRef(); + const data: ChartKitWidgetData = { + title: {text: 'DateTime axis'}, + xAxis: { + type: 'datetime', + labels: {enabled: true}, + }, + legend: {enabled: true}, + tooltip: {enabled: false}, + yAxis: [ + { + type: 'linear', + labels: {enabled: true}, + min: 0, + }, + ], + series: { + data: [ + { + type: 'bar', + visible: true, + data: [ + { + x: Number(new Date(2022, 10, 10)), + y: 100, + }, + { + x: Number(new Date(2023, 2, 5)), + y: 80, + }, + ], + name: 'AB', + }, + { + type: 'bar', + visible: true, + data: [ + { + x: Number(new Date(2022, 11, 25)), + y: 120, + }, + ], + name: 'C', + }, + ], + }, + }; + + if (!shown) { + settings.set({plugins: [D3Plugin]}); + return ; + } + + return ( +
+ ('data', data)} /> +
+ ); +}; + +export const DatetimeXAxis = Template.bind({}); + +const meta: Meta = { + title: 'Plugins/D3/Bar', + decorators: [withKnobs], +}; + +export default meta; diff --git a/src/plugins/d3/__stories__/bar/linear.stories.tsx b/src/plugins/d3/__stories__/bar/linear.stories.tsx new file mode 100644 index 00000000..07a3bc79 --- /dev/null +++ b/src/plugins/d3/__stories__/bar/linear.stories.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import {Meta, Story} from '@storybook/react'; +import {withKnobs, object} from '@storybook/addon-knobs'; +import {Button} from '@gravity-ui/uikit'; +import {settings} from '../../../../libs'; +import {ChartKit} from '../../../../components/ChartKit'; +import type {ChartKitRef} from '../../../../types'; +import type {ChartKitWidgetData} from '../../../../types/widget-data'; +import {D3Plugin} from '../..'; + +const Template: Story = () => { + const [shown, setShown] = React.useState(false); + const chartkitRef = React.useRef(); + const data: ChartKitWidgetData = { + series: { + data: [ + { + type: 'bar', + visible: true, + data: [ + { + category: 'A', + x: 10, + y: 100, + }, + { + category: 'B', + x: 12, + y: 80, + }, + ], + name: 'AB', + }, + { + type: 'bar', + visible: true, + data: [ + { + category: 'C', + x: 95.5, + y: 120, + }, + ], + name: 'C', + }, + ], + }, + title: {text: 'Linear axis'}, + xAxis: { + type: 'linear', + labels: {enabled: true}, + }, + yAxis: [ + { + type: 'linear', + labels: {enabled: true}, + min: 0, + }, + ], + legend: {enabled: true}, + tooltip: {enabled: false}, + }; + + if (!shown) { + settings.set({plugins: [D3Plugin]}); + return ; + } + + return ( +
+ ('data', data)} /> +
+ ); +}; + +export const LinearXAxis = Template.bind({}); + +const meta: Meta = { + title: 'Plugins/D3/Bar', + decorators: [withKnobs], +}; + +export default meta; diff --git a/src/plugins/d3/__stories__/LinearCategories.stories.tsx b/src/plugins/d3/__stories__/scatter/LinearCategories.stories.tsx similarity index 93% rename from src/plugins/d3/__stories__/LinearCategories.stories.tsx rename to src/plugins/d3/__stories__/scatter/LinearCategories.stories.tsx index 1c6cf6a7..5f02abf4 100644 --- a/src/plugins/d3/__stories__/LinearCategories.stories.tsx +++ b/src/plugins/d3/__stories__/scatter/LinearCategories.stories.tsx @@ -3,17 +3,17 @@ import random from 'lodash/random'; import {Meta, Story} from '@storybook/react'; import {withKnobs, select, radios, text} from '@storybook/addon-knobs'; import {Button} from '@gravity-ui/uikit'; -import {settings} from '../../../libs'; -import {ChartKit} from '../../../components/ChartKit'; -import type {ChartKitRef} from '../../../types'; +import {settings} from '../../../../libs'; +import {ChartKit} from '../../../../components/ChartKit'; +import type {ChartKitRef} from '../../../../types'; import type { ChartKitWidgetAxis, ChartKitWidgetData, ScatterSeries, ScatterSeriesData, -} from '../../../types/widget-data'; -import {D3Plugin} from '..'; -import penguins from './penguins.json'; +} from '../../../../types/widget-data'; +import {D3Plugin} from '../..'; +import penguins from '../penguins.json'; const shapeScatterSeriesData = (args: {data: Record[]; groupBy: string; map: any}) => { const {data, groupBy, map} = args; @@ -156,7 +156,7 @@ const Template: Story = () => { export const LinearAndCategories = Template.bind({}); const meta: Meta = { - title: 'Plugins/D3', + title: 'Plugins/D3/Scatter', decorators: [withKnobs], }; diff --git a/src/plugins/d3/__stories__/Timestamp.stories.tsx b/src/plugins/d3/__stories__/scatter/Timestamp.stories.tsx similarity index 90% rename from src/plugins/d3/__stories__/Timestamp.stories.tsx rename to src/plugins/d3/__stories__/scatter/Timestamp.stories.tsx index 5dd2daea..a31cdb61 100644 --- a/src/plugins/d3/__stories__/Timestamp.stories.tsx +++ b/src/plugins/d3/__stories__/scatter/Timestamp.stories.tsx @@ -3,15 +3,15 @@ import random from 'lodash/random'; import {Meta, Story} from '@storybook/react'; import {dateTime} from '@gravity-ui/date-utils'; import {Button} from '@gravity-ui/uikit'; -import {settings} from '../../../libs'; -import {ChartKit} from '../../../components/ChartKit'; -import type {ChartKitRef} from '../../../types'; +import {settings} from '../../../../libs'; +import {ChartKit} from '../../../../components/ChartKit'; +import type {ChartKitRef} from '../../../../types'; import type { ChartKitWidgetData, ScatterSeries, ScatterSeriesData, -} from '../../../types/widget-data'; -import {D3Plugin} from '..'; +} from '../../../../types/widget-data'; +import {D3Plugin} from '../..'; const rowData: ScatterSeriesData[] = [ { @@ -105,7 +105,7 @@ const Template: Story = () => { export const Timestamp = Template.bind({}); const meta: Meta = { - title: 'Plugins/D3', + title: 'Plugins/D3/Scatter', }; export default meta;