diff --git a/semcore/d3-chart/src/Area.jsx b/semcore/d3-chart/src/Area.jsx
index f2573d9516..c6c7d62756 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 { DATA_TYPE, FORECAST, 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,102 @@ 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[DATA_TYPE] === FORECAST);
+
+ return sstyled(styles)(
+ <>
+
+
+
+
+ >,
+ );
+ }
+
+ 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[DATA_TYPE] === POTENTIAL);
+
+ return sstyled(styles)(
+ <>
+
+
+
+
+
+
+ >,
+ );
+ }
+
render() {
const SArea = this.Element;
const SAreaLine = SvgElement;
@@ -121,9 +223,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[DATA_TYPE] !== FORECAST && item[DATA_TYPE] !== POTENTIAL);
this.asProps.dataHintsHandler.specifyDataRowFields(x, y);
this.asProps.dataHintsHandler.establishDataType('time-series');
@@ -152,8 +255,10 @@ class AreaRoot extends Component {
use:duration={`${duration}ms`}
transparent={transparent}
onClickCapture={this.handlerOnClick.bind(this)}
+ withGradient={patterns ? undefined : withGradient}
/>
{duration && }
+ {duration && }
{patterns && (
)}
+ {this.renderForecast()}
+ {this.renderPotential()}
>,
);
}
diff --git a/semcore/d3-chart/src/Dots.jsx b/semcore/d3-chart/src/Dots.jsx
index dde55a07a8..59816ea9b5 100644
--- a/semcore/d3-chart/src/Dots.jsx
+++ b/semcore/d3-chart/src/Dots.jsx
@@ -3,14 +3,18 @@ import trottle from '@semcore/core/lib/utils/rafTrottle';
import { bisector } from 'd3-array';
import React from 'react';
+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';
const BASE_RADIUS = 3.5;
const ACTIVE_RADIUS = 4.5;
+
function Dots(props) {
+ const SDotHighlight = 'circle';
+ const SDotBorder = 'circle';
const {
Element: SDot,
styles,
@@ -29,6 +33,7 @@ function Dots(props) {
resolveColor,
patterns,
onClick,
+ uid,
} = props;
const bisect = bisector((d) => d[x]).center;
const [activeIndex, setActiveIndex] = React.useState(null);
@@ -69,6 +74,22 @@ function Dots(props) {
onClick(index, e);
}, [scale, data, onClick]);
+ const resolveHighlightColor = React.useCallback((highlight) => {
+ switch (highlight) {
+ case GOOD: {
+ return resolveColor('--intergalactic-chart-data-success');
+ }
+ case BAD: {
+ return resolveColor('--intergalactic-chart-data-critical');
+ }
+ case INSIGHTFUL: {
+ return `url(#dotGradient_${uid})`;
+ }
+ default:
+ return '';
+ }
+ }, []);
+
React.useEffect(() => {
const unsubscribeMouseMoveRoot = eventEmitter.subscribe('onMouseMoveChart', (e) => {
e.persist();
@@ -97,8 +118,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 = active ? ACTIVE_RADIUS : radiusBase;
if (!d3.defined()(d)) return acc;
if (!visible) return acc;
@@ -106,22 +127,48 @@ function Dots(props) {
if (!patterns) {
acc.push(
sstyled(styles)(
- ,
+
+ {d[HIGHLIGHT_DOT] && (
+ <>
+
+
+ >
+ )}
+
+ ,
),
);
} else {
@@ -149,6 +196,7 @@ function Dots(props) {
const SDots = 'g';
return sstyled(styles)(
+
{ patterns && (
- ) }
+ )}
{dots}
,
);
diff --git a/semcore/d3-chart/src/Pattern.tsx b/semcore/d3-chart/src/Pattern.tsx
index 04396e90dd..e2cea61bda 100644
--- a/semcore/d3-chart/src/Pattern.tsx
+++ b/semcore/d3-chart/src/Pattern.tsx
@@ -1,6 +1,9 @@
+import { sstyled } from '@semcore/core';
import propsForElement from '@semcore/core/lib/utils/propsForElement';
+import { line } from 'd3-shape';
import React from 'react';
+import style from './style/pattern.shadow.css';
/**
* Object that is fully describes the pattern
*/
@@ -625,6 +628,88 @@ const resolvePattern = (patternKey: string, patternsConfig: PatternsConfig = tru
patternIndex = patternIndex % patterns.length;
return patterns[patternIndex];
};
+
+export const DefaultStrokePattern = (props: { id: string }) => {
+ return (
+
+
+
+
+
+
+ );
+};
+export const LightStrokePattern = (props: { id: string }) => {
+ 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 0409ed99f3..61ddbe5df8 100644
--- a/semcore/d3-chart/src/component/Chart/AbstractChart.tsx
+++ b/semcore/d3-chart/src/component/Chart/AbstractChart.tsx
@@ -52,7 +52,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 63c83aad6e..8b9243ef82 100644
--- a/semcore/d3-chart/src/component/Chart/AbstractChart.type.ts
+++ b/semcore/d3-chart/src/component/Chart/AbstractChart.type.ts
@@ -6,11 +6,20 @@ 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';
+export const GOOD = Symbol('GOOD');
+export const BAD = Symbol('BAD');
+export const INSIGHTFUL = Symbol('INSIGHTFUL');
+export const HIGHLIGHT_DOT = Symbol('HIGHLIGHT_DOT');
+
+export const DATA_TYPE = Symbol('DATA_TYPE');
+export const FORECAST = Symbol('FORECAST_DATA');
+export const POTENTIAL = Symbol('POTENTIAL_DATA');
+
export type BaseLegendProps = BaseChartLegendProps & {
/**
* Disable hover (for transition items legend of each not hovered)
@@ -44,7 +53,10 @@ export type BaseLegendProps = BaseChartLegendProps & {
);
export type ObjectDataKey = string;
-export type ObjectData = Record;
+export type ObjectData = Record & {
+ [HIGHLIGHT_DOT]?: typeof GOOD | typeof BAD | typeof INSIGHTFUL;
+ [DATA_TYPE]?: typeof POTENTIAL | typeof FORECAST;
+};
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 80624f394c..846118d88a 100644
--- a/semcore/d3-chart/src/component/Chart/AreaChart.tsx
+++ b/semcore/d3-chart/src/component/Chart/AreaChart.tsx
@@ -7,7 +7,14 @@ import React from 'react';
import { Area, minMax, HoverLine, StackedArea } from '../..';
import type { ChartState } from './AbstractChart';
import { AbstractChart } from './AbstractChart';
-import type { AreaChartData, AreaChartProps, AreaChartType, AreaChartDefaultProps } from './AreaChart.type';
+import type { ObjectData } from './AbstractChart.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<
@@ -27,6 +34,7 @@ class AreaChartComponent extends AbstractChart<
showXAxis: true,
showYAxis: true,
showTooltip: true,
+ stacked: true,
locale: 'en',
} as const;
@@ -84,8 +92,9 @@ class AreaChartComponent extends AbstractChart<
transparent={highlightedItem !== -1 && highlightedItem !== index}
curve={curve}
onClick={onClickArea}
+ withGradient
>
-
+
)
);
@@ -94,6 +103,8 @@ class AreaChartComponent extends AbstractChart<
);
}
+ const withGradient = dataDefinitions.filter((item) => item.checked).length === 1;
+
return dataDefinitions.map((item, index) => {
return (
item.checked && (
@@ -105,8 +116,9 @@ class AreaChartComponent extends AbstractChart<
transparent={highlightedItem !== -1 && highlightedItem !== index}
curve={curve}
onClick={onClickArea}
+ withGradient={withGradient}
>
-
+
)
);
@@ -136,6 +148,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_DOT]);
+ };
}
/**
diff --git a/semcore/d3-chart/src/component/Chart/AreaChart.type.ts b/semcore/d3-chart/src/component/Chart/AreaChart.type.ts
index 0697a682e2..9ec1d4fe74 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 */
@@ -19,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;
diff --git a/semcore/d3-chart/src/component/Chart/index.ts b/semcore/d3-chart/src/component/Chart/index.ts
index cd3ee5bbc3..0afd4d430c 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_DOT, DATA_TYPE, POTENTIAL, FORECAST } from './AbstractChart.type';
import { AreaChart } from './AreaChart';
import { BarChart } from './BarChart';
import { BubbleChart } from './BubbleChart';
@@ -23,3 +24,13 @@ export default {
Cigarette: CigaretteChart,
CompactHorizontalBar: CompactHorizontalBarChart,
};
+
+export {
+ GOOD,
+ BAD,
+ INSIGHTFUL,
+ HIGHLIGHT_DOT,
+ DATA_TYPE,
+ FORECAST,
+ POTENTIAL,
+};
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/area.shadow.css b/semcore/d3-chart/src/style/area.shadow.css
index 27072527fa..009aad758e 100644
--- a/semcore/d3-chart/src/style/area.shadow.css
+++ b/semcore/d3-chart/src/style/area.shadow.css
@@ -3,6 +3,9 @@
SArea {
fill: var(--intergalactic-chart-palette-order-1, oklch(0.23 0.01 140));
fill-opacity: 0.1;
+}
+
+SArea[withGradient] {
mask-image: linear-gradient(to bottom, #000 0%, rgba(0, 0, 0, 0.48) 60%, transparent 100%);
}
@@ -12,7 +15,7 @@ SArea[color] {
SArea[pattern] {
fill: var(--pattern);
- fill-opacity: 0.7;
+ fill-opacity: 0.5;
}
SArea[transparent] {
@@ -48,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 18534242fa..687d42c263 100644
--- a/semcore/d3-chart/src/style/dot.shadow.css
+++ b/semcore/d3-chart/src/style/dot.shadow.css
@@ -28,6 +28,19 @@ 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: 2;
+}
+
SDot[transparent] {
opacity: 0.3;
-}
\ No newline at end of file
+}
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 43376283a3..f7e1910570 100644
--- a/stories/components/d3-chart/__mocks__/area.ts
+++ b/stories/components/d3-chart/__mocks__/area.ts
@@ -1,17 +1,15 @@
-import { interpolateValue } 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 },
+ { 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 },
- { time: new Date(2025, 0, 30), line: 2 },
+ { 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, 9), line: 0 },
- { time: new Date(2025, 1, 14), line: 5 },
],
Interpolation: [
{ time: new Date(2025, 0, 1), line1: 5, line2: 3 },
@@ -45,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 (
-
+
+
+
+
+
);
};