Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 109 additions & 2 deletions semcore/d3-chart/src/Area.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)(
<>
<SAreaLine
tag='path'
aria-hidden
clipPath={`url(#${uid}-animation)`}
d={d3Line(data)}
color={resolveColor(color)}
use:duration={`${duration}ms`}
transparent={transparent}
strokeDasharray='4 4'
/>
<SArea
aria-hidden
clipPath={`url(#${uid})`}
render='path'
d={d3(data)}
hide={hide}
pattern={`url(#${uid}-forecast-gradient)`}
mask={`url(#${uid}-forecast-mask)`}
use:duration={`${duration}ms`}
transparent={transparent}
onClickCapture={this.handlerOnClick.bind(this)}
/>
<ForecastGradient id={`${uid}-forecast-gradient`} />
<StrokeMask id={`${uid}-forecast-mask`} />
</>,
);
}

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)(
<>
<SAreaLine
tag='path'
aria-hidden
clipPath={`url(#${uid}-animation)`}
d={d3Line(data)}
color={`url(#${uid}-potential-gradient-line)`}
use:duration={`${duration}ms`}
transparent={transparent}
strokeDasharray='4 4'
/>
<SArea
aria-hidden
clipPath={`url(#${uid})`}
render='path'
d={d3(data)}
hide={hide}
pattern={`url(#${uid}-potential-gradient)`}
mask={`url(#${uid}-potential-mask)`}
use:duration={`${duration}ms`}
transparent={transparent}
onClickCapture={this.handlerOnClick.bind(this)}
/>

<PotentialGradient id={`${uid}-potential-gradient`} />
<PotentialGradient id={`${uid}-potential-gradient-line`} type='line' />
<StrokeMask id={`${uid}-potential-mask`} />
</>,
);
}

render() {
const SArea = this.Element;
const SAreaLine = SvgElement;
Expand All @@ -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');
Expand Down Expand Up @@ -152,8 +255,10 @@ class AreaRoot extends Component {
use:duration={`${duration}ms`}
transparent={transparent}
onClickCapture={this.handlerOnClick.bind(this)}
withGradient={patterns ? undefined : withGradient}
/>
{duration && <AnimatedClipPath duration={duration} id={uid} width={0} height={size[1]} />}
{duration && <AnimatedClipPath duration={duration} id={`${uid}-animation`} width={0} height={size[1]} />}
{patterns && (
<PatternFill
id={`${uid}-pattern`}
Expand All @@ -162,6 +267,8 @@ class AreaRoot extends Component {
patterns={patterns}
/>
)}
{this.renderForecast()}
{this.renderPotential()}
</>,
);
}
Expand Down
88 changes: 68 additions & 20 deletions semcore/d3-chart/src/Dots.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -97,31 +118,57 @@ 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;

if (!patterns) {
acc.push(
sstyled(styles)(
<SDot
render='circle'
color={resolveColor(color)}
patternKey={patternKey}
patterns={patterns}
key={`${i}`}
value={d}
visible={visible}
active={active}
hide={hide}
transparent={transparent}
cx={d3.x()(d)}
cy={d3.y()(d)}
r={radius}
__excludeProps={['display', 'data', 'scale']}
/>,
<React.Fragment key={i}>
{d[HIGHLIGHT_DOT] && (
<>
<SDotBorder
visible={visible}
active={active}
hide={hide}
transparent={transparent}
cx={d3.x()(d)}
cy={d3.y()(d)}
r={11.5}
/>
<SDotHighlight
color={resolveHighlightColor(d[HIGHLIGHT_DOT])}
value={d}
visible={visible}
active={active}
hide={hide}
transparent={transparent}
cx={d3.x()(d)}
cy={d3.y()(d)}
r={8.5}
/>
</>
)}
<SDot
render='circle'
color={resolveColor(color)}
patternKey={patternKey}
patterns={patterns}
key={`${i}`}
value={d}
visible={visible}
active={active}
hide={hide}
transparent={transparent}
cx={d3.x()(d)}
cy={d3.y()(d)}
r={radius}
__excludeProps={['display', 'data', 'scale']}
/>
</React.Fragment>,
),
);
} else {
Expand Down Expand Up @@ -149,6 +196,7 @@ function Dots(props) {
const SDots = 'g';
return sstyled(styles)(
<SDots duration={`${duration}ms`} onClickCapture={handlerOnClick}>
<PotentialGradient id={`dotGradient_${uid}`} />
{ patterns && (
<PatternSymbol
color={resolveColor(color)}
Expand All @@ -157,7 +205,7 @@ function Dots(props) {
patterns={patterns}
x={-1 * width}
/>
) }
)}
{dots}
</SDots>,
);
Expand Down
85 changes: 85 additions & 0 deletions semcore/d3-chart/src/Pattern.tsx
Original file line number Diff line number Diff line change
@@ -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
*/
Expand Down Expand Up @@ -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 (
<defs>
<pattern
id={props.id}
width='10'
height='8'
patternUnits='userSpaceOnUse'
patternTransform='rotate(-117)'
>
<rect
width='10'
height='8'
fill='transparent'
/>
<line
x1='0'
y1='0'
x2='0'
y2='8'
stroke='#fff'
strokeWidth='8'
/>
</pattern>
</defs>
);
};
export const LightStrokePattern = (props: { id: string }) => {
return (
<defs>
<pattern id={props.id} width='6' height='6' patternUnits='userSpaceOnUse' patternTransform='rotate(-117)'>
<line x1='0' y1='0' x2='0' y2='6' stroke='white' strokeWidth='3' />
</pattern>
</defs>
);
};
export const StrokeMask = (props: { id: string }) => {
return (
<defs>
<LightStrokePattern id={`${props.id}-stroke`} />

<mask id={props.id}>
<rect width='100%' height='100%' fill='black' />
<rect width='100%' height='100%' fill={`url(#${props.id}-stroke)`} />
</mask>
</defs>
);
};
export const ForecastGradient = (props: { id: string }) => {
return (
<defs>
<linearGradient id={props.id} x1='0%' y1='0%' x2='0%' y2='100%'>
<stop offset='0%' stopColor='#000' stopOpacity='1' />
<stop offset='40%' stopColor='#000' stopOpacity='0.48' />
<stop offset='100%' stopColor='#000' stopOpacity='0' />
</linearGradient>
</defs>
);
};
export const PotentialGradient = (props: { id: string; type?: 'line' }) => {
const SLinearGradient = 'linearGradient';
const SStopFrom = 'stop';
const SStopTo = 'stop';

return sstyled(style)(
<defs>
<SLinearGradient
id={props.id}
x1='0%'
y1='0%'
x2={props.type === 'line' ? '100%' : '0'}
y2={props.type === 'line' ? '0' : '100%'}
gradientTransform='rotate(-45 0.5 0.5)'
>
<SStopFrom offset='0%' />
<SStopTo offset='100%' />
</SLinearGradient>
</defs>,
);
};

export const PatternFill = ({
id,
color,
Expand Down
Loading
Loading