From 87a195ddd74f093beb0c9edaa3d6a178dd987487 Mon Sep 17 00:00:00 2001 From: "ilia.brauer" Date: Tue, 8 Sep 2026 13:53:32 +0200 Subject: [PATCH 1/5] [d3-chart] init dots redesign --- semcore/d3-chart/src/Dots.jsx | 90 +++++++++++++++---- .../src/component/Chart/AbstractChart.type.ts | 9 +- .../src/component/Chart/AreaChart.tsx | 10 ++- semcore/d3-chart/src/component/Chart/index.ts | 8 ++ semcore/d3-chart/src/index.js | 1 + semcore/d3-chart/src/style/dot.shadow.css | 23 ++++- stories/components/d3-chart/__mocks__/area.ts | 8 +- 7 files changed, 123 insertions(+), 26 deletions(-) diff --git a/semcore/d3-chart/src/Dots.jsx b/semcore/d3-chart/src/Dots.jsx index 45cb3ef953..6a1c96cb92 100644 --- a/semcore/d3-chart/src/Dots.jsx +++ b/semcore/d3-chart/src/Dots.jsx @@ -3,12 +3,15 @@ import trottle from '@semcore/core/lib/utils/rafTrottle'; import { bisector } from 'd3-array'; import React from 'react'; +import { HIGHLIGHT, GOOD, BAD, INSIGHTFUL } from './component/Chart/AbstractChart.type'; import createElement from './createElement'; import { PatternSymbol, getPatternSymbolSize } from './Pattern'; import style from './style/dot.shadow.css'; import { eventToPoint, invert, interpolateValue, getChartDefaultColorName } from './utils'; function Dots(props) { + const SDotHighlight = 'circle'; + const SDotBorder = 'circle'; const { Element: SDot, styles, @@ -27,6 +30,7 @@ function Dots(props) { resolveColor, patterns, onClick, + uid, } = props; const bisect = bisector((d) => d[x]).center; const [activeIndex, setActiveIndex] = React.useState(null); @@ -67,6 +71,22 @@ function Dots(props) { onClick(index, e); }, [scale, data, onClick]); + const resolveHighlightColor = React.useCallback((highlight) => { + switch (highlight) { + case GOOD: { + return resolveColor('--intergalalctic-chart-data-success'); + } + case BAD: { + return resolveColor('--intergalalctic-chart-data-critical'); + } + case INSIGHTFUL: { + return `url(#dotGradient_${uid})`; + } + default: + return ''; + } + }, []); + React.useEffect(() => { const unsubscribeMouseMoveRoot = eventEmitter.subscribe('onMouseMoveChart', (e) => { e.persist(); @@ -95,8 +115,8 @@ function Dots(props) { const active = i === activeIndex; const visible = typeof display === 'function' - ? display(i, i === activeIndex, !isPrev && !isNext) - : display || i === activeIndex || (!isPrev && !isNext); + ? display(i, active, !isPrev && !isNext, d) + : display || active || (!isPrev && !isNext); const radius = radiusBase * (active ? 5 / 4 : 1); if (!d3.defined()(d)) return acc; if (!visible) return acc; @@ -104,22 +124,48 @@ function Dots(props) { if (!patterns) { acc.push( sstyled(styles)( - , + + {d[HIGHLIGHT] && ( + <> + + + + )} + + , ), ); } else { @@ -145,8 +191,16 @@ function Dots(props) { return acc; }, []); const SDots = 'g'; + const SStopFrom = 'stop'; + const SStopTo = 'stop'; return sstyled(styles)( + + + + + + ; +export type ObjectData = Record & { + [HIGHLIGHT]?: typeof GOOD | typeof BAD | typeof INSIGHTFUL; +}; export type ListData = ObjectData[]; /** diff --git a/semcore/d3-chart/src/component/Chart/AreaChart.tsx b/semcore/d3-chart/src/component/Chart/AreaChart.tsx index 2c9a0203c0..86c5ac883c 100644 --- a/semcore/d3-chart/src/component/Chart/AreaChart.tsx +++ b/semcore/d3-chart/src/component/Chart/AreaChart.tsx @@ -7,6 +7,8 @@ import React from 'react'; import { Area, minMax, HoverLine, StackedArea } from '../..'; import type { ChartState } from './AbstractChart'; import { AbstractChart } from './AbstractChart'; +import type { ObjectData } from './AbstractChart.type'; +import { HIGHLIGHT } from './AbstractChart.type'; import type { AreaChartData, AreaChartProps, AreaChartType, AreaChartDefaultProps } from './AreaChart.type'; import { localizedMessages } from '../../translations/__intergalactic-dynamic-locales'; @@ -84,7 +86,7 @@ class AreaChartComponent extends AbstractChart< curve={curve} onClick={onClickArea} > - {showDots && } + ) ); @@ -105,7 +107,7 @@ class AreaChartComponent extends AbstractChart< curve={curve} onClick={onClickArea} > - {showDots && } + ) ); @@ -134,6 +136,10 @@ class AreaChartComponent extends AbstractChart< protected getLegendAriaLabel(): string { return this.asProps.getI18nText('legendForChart', { chartType: 'Area' }); } + + protected displayDots = (i: number, isActive: boolean, noAround: boolean, data: ObjectData): boolean => { + return isActive || noAround || Boolean(data[HIGHLIGHT]); + }; } /** diff --git a/semcore/d3-chart/src/component/Chart/index.ts b/semcore/d3-chart/src/component/Chart/index.ts index cd3ee5bbc3..f005f2a441 100644 --- a/semcore/d3-chart/src/component/Chart/index.ts +++ b/semcore/d3-chart/src/component/Chart/index.ts @@ -1,3 +1,4 @@ +import { GOOD, BAD, INSIGHTFUL, HIGHLIGHT } from './AbstractChart.type'; import { AreaChart } from './AreaChart'; import { BarChart } from './BarChart'; import { BubbleChart } from './BubbleChart'; @@ -23,3 +24,10 @@ export default { Cigarette: CigaretteChart, CompactHorizontalBar: CompactHorizontalBarChart, }; + +export { + GOOD, + BAD, + INSIGHTFUL, + HIGHLIGHT, +}; diff --git a/semcore/d3-chart/src/index.js b/semcore/d3-chart/src/index.js index e97384de3f..d2b3b57c61 100644 --- a/semcore/d3-chart/src/index.js +++ b/semcore/d3-chart/src/index.js @@ -1,6 +1,7 @@ export { default as Plot } from './Plot'; export { ChartLegend, ChartLegendTable } from './component/ChartLegend'; export { default as Chart } from './component/Chart'; +export * from './component/Chart'; export { default as StackGroupBar } from './component/StackGroupBar/StackGroupBar'; export { SvgElement } from './component/SvgElement'; export { XAxis, YAxis } from './Axis'; diff --git a/semcore/d3-chart/src/style/dot.shadow.css b/semcore/d3-chart/src/style/dot.shadow.css index b18b4d76f2..1338f1cd89 100644 --- a/semcore/d3-chart/src/style/dot.shadow.css +++ b/semcore/d3-chart/src/style/dot.shadow.css @@ -22,6 +22,27 @@ SDot[color] { fill: var(--color); } +SDotHighlight[color] { + opacity: 0.4; + fill: var(--color); +} + +SDotBorder { + fill: none; + stroke-width: 1px; + stroke: var(--intergalactic-chart-grid-line, oklch(0.95 0.002 180)); + opacity: 0.5; + stroke-dasharray: 3; +} + SDot[transparent] { opacity: 0.3; -} \ No newline at end of file +} + +SStopFrom { + stop-color: var(--violet-300, undefined); +} + +SStopTo { + stop-color: var(--green-100, undefined); +} diff --git a/stories/components/d3-chart/__mocks__/area.ts b/stories/components/d3-chart/__mocks__/area.ts index 43376283a3..e7ee0e6daf 100644 --- a/stories/components/d3-chart/__mocks__/area.ts +++ b/stories/components/d3-chart/__mocks__/area.ts @@ -1,16 +1,16 @@ -import { interpolateValue } from '@semcore/ui/d3-chart'; +import { interpolateValue, GOOD, HIGHLIGHT, BAD, INSIGHTFUL } from '@semcore/ui/d3-chart'; export default { Default: [ { time: new Date(2025, 0, 1), line: 3 }, { time: new Date(2025, 0, 5), line: 7 }, { time: new Date(2025, 0, 10), line: 1 }, - { time: new Date(2025, 0, 15), line: 9 }, + { time: new Date(2025, 0, 15), line: 9, [HIGHLIGHT]: GOOD }, { time: new Date(2025, 0, 20), line: 4 }, - { time: new Date(2025, 0, 25), line: 6 }, + { time: new Date(2025, 0, 25), line: 6, [HIGHLIGHT]: INSIGHTFUL }, { time: new Date(2025, 0, 30), line: 2 }, { time: new Date(2025, 1, 4), line: 8 }, - { time: new Date(2025, 1, 9), line: 0 }, + { time: new Date(2025, 1, 9), line: 0, [HIGHLIGHT]: BAD }, { time: new Date(2025, 1, 14), line: 5 }, ], Interpolation: [ From 12d10a3e21bff8b5f46637b890a73b56fe24046e Mon Sep 17 00:00:00 2001 From: "ilia.brauer" Date: Wed, 9 Sep 2026 14:38:35 +0200 Subject: [PATCH 2/5] [d3-chart] dots + area redesign --- semcore/d3-chart/src/Area.jsx | 112 +++++++++++++++++- semcore/d3-chart/src/Dots.jsx | 25 ++-- semcore/d3-chart/src/Pattern.tsx | 108 +++++++++++++++++ .../src/component/Chart/AbstractChart.tsx | 2 +- .../src/component/Chart/AbstractChart.type.ts | 12 +- .../src/component/Chart/AreaChart.tsx | 15 ++- .../src/component/Chart/AreaChart.type.ts | 5 +- semcore/d3-chart/src/component/Chart/index.ts | 6 +- semcore/d3-chart/src/style/area.shadow.css | 13 +- semcore/d3-chart/src/style/dot.shadow.css | 10 +- semcore/d3-chart/src/style/pattern.shadow.css | 10 ++ stories/components/d3-chart/__mocks__/area.ts | 14 ++- 12 files changed, 279 insertions(+), 53 deletions(-) create mode 100644 semcore/d3-chart/src/style/pattern.shadow.css diff --git a/semcore/d3-chart/src/Area.jsx b/semcore/d3-chart/src/Area.jsx index f2573d9516..d1dba3dcd2 100644 --- a/semcore/d3-chart/src/Area.jsx +++ b/semcore/d3-chart/src/Area.jsx @@ -6,10 +6,16 @@ import { area, curveLinear, line } from 'd3-shape'; import React from 'react'; import AnimatedClipPath from './AnimatedClipPath'; +import { IS_FORECAST, IS_POTENTIAL } from './component/Chart/AbstractChart.type'; import { SvgElement } from './component/SvgElement'; import createElement from './createElement'; import Dots from './Dots'; -import { PatternFill } from './Pattern'; +import { + PatternFill, + ForecastGradient, + PotentialGradient, + StrokeMask, +} from './Pattern'; import style from './style/area.shadow.css'; import { definedData, @@ -102,6 +108,104 @@ class AreaRoot extends Component { onClick(index, e); } + renderForecast() { + const SArea = this.Element; + const SAreaLine = SvgElement; + const { + styles, + hide, + d3, + d3Line, + color, + uid, + size, + duration, + y, + transparent, + resolveColor, + } = this.asProps; + const data = this.asProps.data.filter((item) => item[y] !== interpolateValue && item[IS_FORECAST]); + + return sstyled(styles)( + <> + + + {duration && } + + + , + ); + } + + renderPotential() { + const SArea = this.Element; + const SAreaLine = SvgElement; + const { + styles, + hide, + d3, + d3Line, + uid, + size, + duration, + y, + transparent, + } = this.asProps; + const data = this.asProps.data.filter((item) => item[y] !== interpolateValue && item[IS_POTENTIAL]); + + return sstyled(styles)( + <> + + + + {duration && } + + + + , + ); + } + render() { const SArea = this.Element; const SAreaLine = SvgElement; @@ -121,9 +225,10 @@ class AreaRoot extends Component { forcedAdvancedMode, resolveColor, patterns, + withGradient, } = this.asProps; const advancedMode = forcedAdvancedMode || !!findComponent(Children, [Area.Line.displayName]); - const data = this.asProps.data.filter((item) => item[y] !== interpolateValue); + const data = this.asProps.data.filter((item) => item[y] !== interpolateValue && !item[IS_FORECAST] && !item[IS_POTENTIAL]); this.asProps.dataHintsHandler.specifyDataRowFields(x, y); this.asProps.dataHintsHandler.establishDataType('time-series'); @@ -152,6 +257,7 @@ class AreaRoot extends Component { use:duration={`${duration}ms`} transparent={transparent} onClickCapture={this.handlerOnClick.bind(this)} + withGradient={patterns ? undefined : withGradient} /> {duration && } {patterns && ( @@ -162,6 +268,8 @@ class AreaRoot extends Component { patterns={patterns} /> )} + {this.renderForecast()} + {this.renderPotential()} , ); } diff --git a/semcore/d3-chart/src/Dots.jsx b/semcore/d3-chart/src/Dots.jsx index 0affc5b715..59816ea9b5 100644 --- a/semcore/d3-chart/src/Dots.jsx +++ b/semcore/d3-chart/src/Dots.jsx @@ -3,9 +3,9 @@ import trottle from '@semcore/core/lib/utils/rafTrottle'; import { bisector } from 'd3-array'; import React from 'react'; -import { HIGHLIGHT, GOOD, BAD, INSIGHTFUL } from './component/Chart/AbstractChart.type'; +import { HIGHLIGHT_DOT, GOOD, BAD, INSIGHTFUL } from './component/Chart/AbstractChart.type'; import createElement from './createElement'; -import { PatternSymbol, getPatternSymbolSize } from './Pattern'; +import { PatternSymbol, getPatternSymbolSize, PotentialGradient } from './Pattern'; import style from './style/dot.shadow.css'; import { eventToPoint, invert, interpolateValue, getChartDefaultColorName } from './utils'; @@ -77,10 +77,10 @@ function Dots(props) { const resolveHighlightColor = React.useCallback((highlight) => { switch (highlight) { case GOOD: { - return resolveColor('--intergalalctic-chart-data-success'); + return resolveColor('--intergalactic-chart-data-success'); } case BAD: { - return resolveColor('--intergalalctic-chart-data-critical'); + return resolveColor('--intergalactic-chart-data-critical'); } case INSIGHTFUL: { return `url(#dotGradient_${uid})`; @@ -128,7 +128,7 @@ function Dots(props) { acc.push( sstyled(styles)( - {d[HIGHLIGHT] && ( + {d[HIGHLIGHT_DOT] && ( <> )} @@ -194,16 +194,9 @@ function Dots(props) { return acc; }, []); const SDots = 'g'; - const SStopFrom = 'stop'; - const SStopTo = 'stop'; return sstyled(styles)( - - - - - - + { patterns && ( { + return ( + + + + + + + ); +}; +export const LightStrokePattern = (props: { id: string }) => { + return ( + + + + + + ); +}; +// export const FillPattern = (props: { id: string; color: string }) => { +// return ( +// +// +// +// +// +// ); +// }; +// export const GradientPattern = (props: { id: string; color: string; stopOpacity?: number }) => { +// return ( +// +// +// +// +// +// +// +// +// +// +// ); +// }; +export const StrokeMask = (props: { id: string }) => { + return ( + + + + + + + + + ); +}; +export const ForecastGradient = (props: { id: string }) => { + return ( + + + + + + + + ); +}; +export const PotentialGradient = (props: { id: string; type?: 'line' }) => { + const SLinearGradient = 'linearGradient'; + const SStopFrom = 'stop'; + const SStopTo = 'stop'; + + return sstyled(style)( + + + + + + , + ); +}; + export const PatternFill = ({ id, color, diff --git a/semcore/d3-chart/src/component/Chart/AbstractChart.tsx b/semcore/d3-chart/src/component/Chart/AbstractChart.tsx index ca59462eaa..6944f8e483 100644 --- a/semcore/d3-chart/src/component/Chart/AbstractChart.tsx +++ b/semcore/d3-chart/src/component/Chart/AbstractChart.tsx @@ -49,7 +49,7 @@ export abstract class AbstractChart< /** * Padding from the end's of chart to the container (except axis sides) */ - protected plotPadding = 6; + protected plotPadding = 10; protected dataHints = makeDataHintsContainer(); diff --git a/semcore/d3-chart/src/component/Chart/AbstractChart.type.ts b/semcore/d3-chart/src/component/Chart/AbstractChart.type.ts index 7804069e3f..e43ed1aea6 100644 --- a/semcore/d3-chart/src/component/Chart/AbstractChart.type.ts +++ b/semcore/d3-chart/src/component/Chart/AbstractChart.type.ts @@ -6,7 +6,7 @@ import type React from 'react'; import type { PatternsConfig } from '../../Pattern'; // @ts-ignore import type { PlotSummarizerConfig } from '../../Plot'; -import type { PlotEventEmitter } from '../../utils'; +import type { interpolateValue, PlotEventEmitter } from '../../utils'; import type { BaseChartLegendProps } from '../ChartLegend/BaseLegend.type'; import type { TrendProps } from '../ChartLegend/LegendFlex/LegendFlex.type'; import type { LegendItemKey } from '../ChartLegend/LegendItem/LegendItem.type'; @@ -14,7 +14,9 @@ import type { LegendItemKey } from '../ChartLegend/LegendItem/LegendItem.type'; export const GOOD = Symbol('GOOD'); export const BAD = Symbol('BAD'); export const INSIGHTFUL = Symbol('INSIGHTFUL'); -export const HIGHLIGHT = Symbol('HIGHLIGHT'); +export const HIGHLIGHT_DOT = Symbol('HIGHLIGHT_DOT'); +export const IS_FORECAST = Symbol('FORECAST_DATA'); +export const IS_POTENTIAL = Symbol('POTENTIAL_DATA'); export type BaseLegendProps = BaseChartLegendProps & { /** @@ -48,8 +50,10 @@ export type BaseLegendProps = BaseChartLegendProps & { } ); -export type ObjectData = Record & { - [HIGHLIGHT]?: typeof GOOD | typeof BAD | typeof INSIGHTFUL; +export type ObjectData = Record & { + [HIGHLIGHT_DOT]?: typeof GOOD | typeof BAD | typeof INSIGHTFUL; + [IS_POTENTIAL]?: boolean; + [IS_FORECAST]?: boolean; }; export type ListData = ObjectData[]; diff --git a/semcore/d3-chart/src/component/Chart/AreaChart.tsx b/semcore/d3-chart/src/component/Chart/AreaChart.tsx index 840216ee15..847f7c603e 100644 --- a/semcore/d3-chart/src/component/Chart/AreaChart.tsx +++ b/semcore/d3-chart/src/component/Chart/AreaChart.tsx @@ -8,8 +8,13 @@ import { Area, minMax, HoverLine, StackedArea } from '../..'; import type { ChartState } from './AbstractChart'; import { AbstractChart } from './AbstractChart'; import type { ObjectData } from './AbstractChart.type'; -import { HIGHLIGHT } from './AbstractChart.type'; -import type { AreaChartData, AreaChartProps, AreaChartType, AreaChartDefaultProps } from './AreaChart.type'; +import { HIGHLIGHT_DOT } from './AbstractChart.type'; +import type { + AreaChartData, + AreaChartProps, + AreaChartType, + AreaChartDefaultProps, +} from './AreaChart.type'; import { localizedMessages } from '../../translations/__intergalactic-dynamic-locales'; class AreaChartComponent extends AbstractChart< @@ -85,6 +90,7 @@ class AreaChartComponent extends AbstractChart< transparent={highlightedItem !== -1 && highlightedItem !== index} curve={curve} onClick={onClickArea} + withGradient > @@ -95,6 +101,8 @@ class AreaChartComponent extends AbstractChart< ); } + const withGradient = dataDefinitions.filter((item) => item.checked).length === 1; + return dataDefinitions.map((item, index) => { return ( item.checked && ( @@ -106,6 +114,7 @@ class AreaChartComponent extends AbstractChart< transparent={highlightedItem !== -1 && highlightedItem !== index} curve={curve} onClick={onClickArea} + withGradient={withGradient} > @@ -139,7 +148,7 @@ class AreaChartComponent extends AbstractChart< } protected displayDots = (i: number, isActive: boolean, noAround: boolean, data: ObjectData): boolean => { - return isActive || noAround || Boolean(data[HIGHLIGHT]); + return isActive || noAround || Boolean(data[HIGHLIGHT_DOT]); }; } diff --git a/semcore/d3-chart/src/component/Chart/AreaChart.type.ts b/semcore/d3-chart/src/component/Chart/AreaChart.type.ts index edec722ed7..d2f13a29d6 100644 --- a/semcore/d3-chart/src/component/Chart/AreaChart.type.ts +++ b/semcore/d3-chart/src/component/Chart/AreaChart.type.ts @@ -3,10 +3,9 @@ import type { Intergalactic } from '@semcore/core'; import type { ScaleLinear, ScaleTime } from 'd3-scale'; import type { CurveFactory } from 'd3-shape'; -import type { AriaNameProps, BaseChartProps } from './AbstractChart.type'; -import type { interpolateValue } from '../../utils'; +import type { AriaNameProps, BaseChartProps, ObjectData } from './AbstractChart.type'; -export type AreaChartData = Array>; +export type AreaChartData = Array; export type AreaChartProps = BaseChartProps & { /** Field name that groups the data points */ diff --git a/semcore/d3-chart/src/component/Chart/index.ts b/semcore/d3-chart/src/component/Chart/index.ts index f005f2a441..306f18c353 100644 --- a/semcore/d3-chart/src/component/Chart/index.ts +++ b/semcore/d3-chart/src/component/Chart/index.ts @@ -1,4 +1,4 @@ -import { GOOD, BAD, INSIGHTFUL, HIGHLIGHT } from './AbstractChart.type'; +import { GOOD, BAD, INSIGHTFUL, HIGHLIGHT_DOT, IS_POTENTIAL, IS_FORECAST } from './AbstractChart.type'; import { AreaChart } from './AreaChart'; import { BarChart } from './BarChart'; import { BubbleChart } from './BubbleChart'; @@ -29,5 +29,7 @@ export { GOOD, BAD, INSIGHTFUL, - HIGHLIGHT, + HIGHLIGHT_DOT, + IS_FORECAST, + IS_POTENTIAL, }; diff --git a/semcore/d3-chart/src/style/area.shadow.css b/semcore/d3-chart/src/style/area.shadow.css index 0f2e27cc7f..009aad758e 100644 --- a/semcore/d3-chart/src/style/area.shadow.css +++ b/semcore/d3-chart/src/style/area.shadow.css @@ -3,12 +3,9 @@ SArea { fill: var(--intergalactic-chart-palette-order-1, oklch(0.23 0.01 140)); fill-opacity: 0.1; - -webkit-mask-image: linear-gradient( - to bottom, - #000 0%, - rgba(0, 0, 0, 0.48) 60%, - transparent 100% - ); +} + +SArea[withGradient] { mask-image: linear-gradient(to bottom, #000 0%, rgba(0, 0, 0, 0.48) 60%, transparent 100%); } @@ -18,7 +15,7 @@ SArea[color] { SArea[pattern] { fill: var(--pattern); - fill-opacity: 0.7; + fill-opacity: 0.5; } SArea[transparent] { @@ -54,4 +51,4 @@ SNull { SNull[hide] { display: none; -} \ No newline at end of file +} diff --git a/semcore/d3-chart/src/style/dot.shadow.css b/semcore/d3-chart/src/style/dot.shadow.css index 5d6d8b302e..c1cea55b48 100644 --- a/semcore/d3-chart/src/style/dot.shadow.css +++ b/semcore/d3-chart/src/style/dot.shadow.css @@ -37,17 +37,9 @@ SDotBorder { stroke-width: 1px; stroke: var(--intergalactic-chart-grid-line, oklch(0.95 0.002 180)); opacity: 0.5; - stroke-dasharray: 3; + stroke-dasharray: 2; } SDot[transparent] { opacity: 0.3; } - -SStopFrom { - stop-color: var(--violet-300, undefined); -} - -SStopTo { - stop-color: var(--green-100, undefined); -} diff --git a/semcore/d3-chart/src/style/pattern.shadow.css b/semcore/d3-chart/src/style/pattern.shadow.css new file mode 100644 index 0000000000..41859e8b33 --- /dev/null +++ b/semcore/d3-chart/src/style/pattern.shadow.css @@ -0,0 +1,10 @@ + +SLinearGradient { + SStopFrom { + stop-color: var(--violet-300, oklch(0.74 0.17 303)); + } + + SStopTo { + stop-color: var(--green-100, oklch(0.9 0.11 175)); + } +} diff --git a/stories/components/d3-chart/__mocks__/area.ts b/stories/components/d3-chart/__mocks__/area.ts index e7ee0e6daf..806e345b95 100644 --- a/stories/components/d3-chart/__mocks__/area.ts +++ b/stories/components/d3-chart/__mocks__/area.ts @@ -1,17 +1,21 @@ -import { interpolateValue, GOOD, HIGHLIGHT, BAD, INSIGHTFUL } from '@semcore/ui/d3-chart'; +import { interpolateValue, GOOD, HIGHLIGHT_DOT, BAD, INSIGHTFUL, IS_FORECAST, IS_POTENTIAL } from '@semcore/ui/d3-chart'; export default { Default: [ { time: new Date(2025, 0, 1), line: 3 }, { time: new Date(2025, 0, 5), line: 7 }, { time: new Date(2025, 0, 10), line: 1 }, - { time: new Date(2025, 0, 15), line: 9, [HIGHLIGHT]: GOOD }, + { time: new Date(2025, 0, 15), line: 9, [HIGHLIGHT_DOT]: GOOD }, { time: new Date(2025, 0, 20), line: 4 }, - { time: new Date(2025, 0, 25), line: 6, [HIGHLIGHT]: INSIGHTFUL }, + { time: new Date(2025, 0, 25), line: 6, [HIGHLIGHT_DOT]: INSIGHTFUL }, { time: new Date(2025, 0, 30), line: 2 }, { time: new Date(2025, 1, 4), line: 8 }, - { time: new Date(2025, 1, 9), line: 0, [HIGHLIGHT]: BAD }, - { time: new Date(2025, 1, 14), line: 5 }, + { time: new Date(2025, 1, 4), line: 8, [IS_FORECAST]: true }, + { time: new Date(2025, 1, 9), line: 0, [IS_FORECAST]: true, [HIGHLIGHT_DOT]: BAD }, + { time: new Date(2025, 1, 14), line: 5, [IS_FORECAST]: true }, + { time: new Date(2025, 1, 14), line: 5, [IS_POTENTIAL]: true }, + { time: new Date(2025, 1, 18), line: 7, [IS_POTENTIAL]: true }, + { time: new Date(2025, 1, 28), line: 8, [IS_POTENTIAL]: true, [HIGHLIGHT_DOT]: INSIGHTFUL }, ], Interpolation: [ { time: new Date(2025, 0, 1), line1: 5, line2: 3 }, From 394e787eb6792a45ac50dadfbf98534ad9cec832 Mon Sep 17 00:00:00 2001 From: "ilia.brauer" Date: Thu, 10 Sep 2026 11:33:20 +0200 Subject: [PATCH 3/5] [d3-chart] area redesign --- semcore/d3-chart/src/Area.jsx | 8 ++-- semcore/d3-chart/src/Pattern.tsx | 23 ----------- .../src/component/Chart/AbstractChart.type.ts | 11 +++--- semcore/d3-chart/src/component/Chart/index.ts | 7 ++-- stories/components/d3-chart/__mocks__/area.ts | 38 +++++++++++++------ .../docs/examples/area-chart/basic-usage.tsx | 35 +++++++++++++---- 6 files changed, 68 insertions(+), 54 deletions(-) diff --git a/semcore/d3-chart/src/Area.jsx b/semcore/d3-chart/src/Area.jsx index d1dba3dcd2..f464cff0e4 100644 --- a/semcore/d3-chart/src/Area.jsx +++ b/semcore/d3-chart/src/Area.jsx @@ -6,7 +6,7 @@ import { area, curveLinear, line } from 'd3-shape'; import React from 'react'; import AnimatedClipPath from './AnimatedClipPath'; -import { IS_FORECAST, IS_POTENTIAL } from './component/Chart/AbstractChart.type'; +import { DATA_TYPE, FORECAST, POTENTIAL } from './component/Chart/AbstractChart.type'; import { SvgElement } from './component/SvgElement'; import createElement from './createElement'; import Dots from './Dots'; @@ -124,7 +124,7 @@ class AreaRoot extends Component { transparent, resolveColor, } = this.asProps; - const data = this.asProps.data.filter((item) => item[y] !== interpolateValue && item[IS_FORECAST]); + const data = this.asProps.data.filter((item) => item[y] !== interpolateValue && item[DATA_TYPE] === FORECAST); return sstyled(styles)( <> @@ -171,7 +171,7 @@ class AreaRoot extends Component { y, transparent, } = this.asProps; - const data = this.asProps.data.filter((item) => item[y] !== interpolateValue && item[IS_POTENTIAL]); + const data = this.asProps.data.filter((item) => item[y] !== interpolateValue && item[DATA_TYPE] === POTENTIAL); return sstyled(styles)( <> @@ -228,7 +228,7 @@ class AreaRoot extends Component { withGradient, } = this.asProps; const advancedMode = forcedAdvancedMode || !!findComponent(Children, [Area.Line.displayName]); - const data = this.asProps.data.filter((item) => item[y] !== interpolateValue && !item[IS_FORECAST] && !item[IS_POTENTIAL]); + const data = this.asProps.data.filter((item) => item[y] !== interpolateValue && item[DATA_TYPE] !== FORECAST && item[DATA_TYPE] !== POTENTIAL); this.asProps.dataHintsHandler.specifyDataRowFields(x, y); this.asProps.dataHintsHandler.establishDataType('time-series'); diff --git a/semcore/d3-chart/src/Pattern.tsx b/semcore/d3-chart/src/Pattern.tsx index 2537a16606..e2cea61bda 100644 --- a/semcore/d3-chart/src/Pattern.tsx +++ b/semcore/d3-chart/src/Pattern.tsx @@ -665,29 +665,6 @@ export const LightStrokePattern = (props: { id: string }) => { ); }; -// export const FillPattern = (props: { id: string; color: string }) => { -// return ( -// -// -// -// -// -// ); -// }; -// export const GradientPattern = (props: { id: string; color: string; stopOpacity?: number }) => { -// return ( -// -// -// -// -// -// -// -// -// -// -// ); -// }; export const StrokeMask = (props: { id: string }) => { return ( diff --git a/semcore/d3-chart/src/component/Chart/AbstractChart.type.ts b/semcore/d3-chart/src/component/Chart/AbstractChart.type.ts index e43ed1aea6..4dd4acd65d 100644 --- a/semcore/d3-chart/src/component/Chart/AbstractChart.type.ts +++ b/semcore/d3-chart/src/component/Chart/AbstractChart.type.ts @@ -15,8 +15,10 @@ export const GOOD = Symbol('GOOD'); export const BAD = Symbol('BAD'); export const INSIGHTFUL = Symbol('INSIGHTFUL'); export const HIGHLIGHT_DOT = Symbol('HIGHLIGHT_DOT'); -export const IS_FORECAST = Symbol('FORECAST_DATA'); -export const IS_POTENTIAL = Symbol('POTENTIAL_DATA'); + +export const DATA_TYPE = Symbol('DATA_TYPE'); +export const FORECAST = Symbol('FORECAST_DATA'); +export const POTENTIAL = Symbol('POTENTIAL_DATA'); export type BaseLegendProps = BaseChartLegendProps & { /** @@ -50,10 +52,9 @@ export type BaseLegendProps = BaseChartLegendProps & { } ); -export type ObjectData = Record & { +export type ObjectData = Record & { [HIGHLIGHT_DOT]?: typeof GOOD | typeof BAD | typeof INSIGHTFUL; - [IS_POTENTIAL]?: boolean; - [IS_FORECAST]?: boolean; + [DATA_TYPE]?: typeof POTENTIAL | typeof FORECAST; }; export type ListData = ObjectData[]; diff --git a/semcore/d3-chart/src/component/Chart/index.ts b/semcore/d3-chart/src/component/Chart/index.ts index 306f18c353..0afd4d430c 100644 --- a/semcore/d3-chart/src/component/Chart/index.ts +++ b/semcore/d3-chart/src/component/Chart/index.ts @@ -1,4 +1,4 @@ -import { GOOD, BAD, INSIGHTFUL, HIGHLIGHT_DOT, IS_POTENTIAL, IS_FORECAST } from './AbstractChart.type'; +import { GOOD, BAD, INSIGHTFUL, HIGHLIGHT_DOT, DATA_TYPE, POTENTIAL, FORECAST } from './AbstractChart.type'; import { AreaChart } from './AreaChart'; import { BarChart } from './BarChart'; import { BubbleChart } from './BubbleChart'; @@ -30,6 +30,7 @@ export { BAD, INSIGHTFUL, HIGHLIGHT_DOT, - IS_FORECAST, - IS_POTENTIAL, + DATA_TYPE, + FORECAST, + POTENTIAL, }; diff --git a/stories/components/d3-chart/__mocks__/area.ts b/stories/components/d3-chart/__mocks__/area.ts index 806e345b95..f7e1910570 100644 --- a/stories/components/d3-chart/__mocks__/area.ts +++ b/stories/components/d3-chart/__mocks__/area.ts @@ -1,21 +1,15 @@ -import { interpolateValue, GOOD, HIGHLIGHT_DOT, BAD, INSIGHTFUL, IS_FORECAST, IS_POTENTIAL } from '@semcore/ui/d3-chart'; +import { interpolateValue, GOOD, HIGHLIGHT_DOT, BAD, INSIGHTFUL, DATA_TYPE, FORECAST, POTENTIAL } from '@semcore/ui/d3-chart'; export default { Default: [ { time: new Date(2025, 0, 1), line: 3 }, { time: new Date(2025, 0, 5), line: 7 }, - { time: new Date(2025, 0, 10), line: 1 }, - { time: new Date(2025, 0, 15), line: 9, [HIGHLIGHT_DOT]: GOOD }, + { time: new Date(2025, 0, 10), line: 5 }, + { time: new Date(2025, 0, 15), line: 9, [HIGHLIGHT_DOT]: GOOD } as const, { time: new Date(2025, 0, 20), line: 4 }, - { time: new Date(2025, 0, 25), line: 6, [HIGHLIGHT_DOT]: INSIGHTFUL }, - { time: new Date(2025, 0, 30), line: 2 }, + { time: new Date(2025, 0, 25), line: 6 }, + { time: new Date(2025, 0, 30), line: 2, [HIGHLIGHT_DOT]: BAD } as const, { time: new Date(2025, 1, 4), line: 8 }, - { time: new Date(2025, 1, 4), line: 8, [IS_FORECAST]: true }, - { time: new Date(2025, 1, 9), line: 0, [IS_FORECAST]: true, [HIGHLIGHT_DOT]: BAD }, - { time: new Date(2025, 1, 14), line: 5, [IS_FORECAST]: true }, - { time: new Date(2025, 1, 14), line: 5, [IS_POTENTIAL]: true }, - { time: new Date(2025, 1, 18), line: 7, [IS_POTENTIAL]: true }, - { time: new Date(2025, 1, 28), line: 8, [IS_POTENTIAL]: true, [HIGHLIGHT_DOT]: INSIGHTFUL }, ], Interpolation: [ { time: new Date(2025, 0, 1), line1: 5, line2: 3 }, @@ -49,4 +43,26 @@ export default { { x: 4, y: 1 }, { x: 5, y: null }, ], + Forecast: [ + { time: new Date(2025, 0, 1), line: 3 }, + { time: new Date(2025, 0, 5), line: 7 }, + { time: new Date(2025, 0, 10), line: 1 }, + { time: new Date(2025, 0, 15), line: 9 }, + { time: new Date(2025, 0, 20), line: 4 }, + { time: new Date(2025, 0, 25), line: 6 }, + { time: new Date(2025, 0, 25), line: 6, [DATA_TYPE]: FORECAST } as const, + { time: new Date(2025, 0, 30), line: 2, [DATA_TYPE]: FORECAST } as const, + { time: new Date(2025, 1, 4), line: 8, [DATA_TYPE]: FORECAST } as const, + ], + Potential: [ + { time: new Date(2025, 0, 1), line: 3 }, + { time: new Date(2025, 0, 5), line: 7 }, + { time: new Date(2025, 0, 10), line: 1 }, + { time: new Date(2025, 0, 15), line: 9 }, + { time: new Date(2025, 0, 20), line: 4 }, + { time: new Date(2025, 1, 14), line: 5 }, + { time: new Date(2025, 1, 14), line: 5, [DATA_TYPE]: POTENTIAL } as const, + { time: new Date(2025, 1, 18), line: 7, [DATA_TYPE]: POTENTIAL } as const, + { time: new Date(2025, 1, 28), line: 8, [DATA_TYPE]: POTENTIAL, [HIGHLIGHT_DOT]: INSIGHTFUL } as const, + ], }; diff --git a/stories/components/d3-chart/docs/examples/area-chart/basic-usage.tsx b/stories/components/d3-chart/docs/examples/area-chart/basic-usage.tsx index 8ca932ff25..c7d5ae76bd 100644 --- a/stories/components/d3-chart/docs/examples/area-chart/basic-usage.tsx +++ b/stories/components/d3-chart/docs/examples/area-chart/basic-usage.tsx @@ -1,3 +1,4 @@ +import { Flex } from '@semcore/ui/base-components'; import { Chart } from '@semcore/ui/d3-chart'; import React from 'react'; @@ -14,14 +15,32 @@ function formatDate(value: any) { const Demo = () => { return ( - + + + + + ); }; From 7216563fa506385e8805268a10884079bb3b7e39 Mon Sep 17 00:00:00 2001 From: "ilia.brauer" Date: Thu, 10 Sep 2026 14:15:47 +0200 Subject: [PATCH 4/5] [d3-chart] area charts are stacked by default, props were deprecated --- semcore/d3-chart/src/component/Chart/AreaChart.tsx | 1 + semcore/d3-chart/src/component/Chart/AreaChart.type.ts | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/semcore/d3-chart/src/component/Chart/AreaChart.tsx b/semcore/d3-chart/src/component/Chart/AreaChart.tsx index 847f7c603e..0744bc77ab 100644 --- a/semcore/d3-chart/src/component/Chart/AreaChart.tsx +++ b/semcore/d3-chart/src/component/Chart/AreaChart.tsx @@ -34,6 +34,7 @@ class AreaChartComponent extends AbstractChart< showXAxis: true, showYAxis: true, showTooltip: true, + stacked: true, } as const; get xScale() { diff --git a/semcore/d3-chart/src/component/Chart/AreaChart.type.ts b/semcore/d3-chart/src/component/Chart/AreaChart.type.ts index d2f13a29d6..69f645f5f0 100644 --- a/semcore/d3-chart/src/component/Chart/AreaChart.type.ts +++ b/semcore/d3-chart/src/component/Chart/AreaChart.type.ts @@ -18,7 +18,11 @@ export type AreaChartProps = BaseChartProps & { showDots?: boolean; /** D3 curve factory for line interpolation (e.g., curveLinear, curveCardinal) */ curve?: CurveFactory; - /** Enables stacked area chart mode */ + /** + * Enables stacked area chart mode + * @deprecated True by default. AreaChart should be only stacked. + * @default true + */ stacked?: boolean; /** Callback triggered when a user clicks on a chart at a position corresponding to a data item */ onClickArea?: (index: number, event: React.SyntheticEvent) => void; From 308503d1bbcf46bf2a82cd2649f206635c5dfa48 Mon Sep 17 00:00:00 2001 From: "ilia.brauer" Date: Thu, 10 Sep 2026 14:32:31 +0200 Subject: [PATCH 5/5] [d3-chart] fixed animation for lines in area charts --- semcore/d3-chart/src/Area.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/semcore/d3-chart/src/Area.jsx b/semcore/d3-chart/src/Area.jsx index f464cff0e4..c6c7d62756 100644 --- a/semcore/d3-chart/src/Area.jsx +++ b/semcore/d3-chart/src/Area.jsx @@ -150,7 +150,6 @@ class AreaRoot extends Component { transparent={transparent} onClickCapture={this.handlerOnClick.bind(this)} /> - {duration && } , @@ -198,7 +197,6 @@ class AreaRoot extends Component { onClickCapture={this.handlerOnClick.bind(this)} /> - {duration && } @@ -260,6 +258,7 @@ class AreaRoot extends Component { withGradient={patterns ? undefined : withGradient} /> {duration && } + {duration && } {patterns && (