@cloud-ru/uikit-product-charts 0.13.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +843 -0
- package/LICENSE +201 -0
- package/README.md +8 -0
- package/package.json +67 -0
- package/src/components/BagelChart/BagelChart.tsx +47 -0
- package/src/components/BagelChart/index.ts +1 -0
- package/src/components/BagelChart/styles.module.scss +29 -0
- package/src/components/BagelChart/utils.ts +14 -0
- package/src/components/HeatMapChart/HeatMapChart.tsx +154 -0
- package/src/components/HeatMapChart/constants.ts +4 -0
- package/src/components/HeatMapChart/helpers/constants.ts +6 -0
- package/src/components/HeatMapChart/helpers/getComputedColor.ts +5 -0
- package/src/components/HeatMapChart/helpers/getContrastColor.ts +16 -0
- package/src/components/HeatMapChart/helpers/getStyles.ts +34 -0
- package/src/components/HeatMapChart/helpers/getTickValues.ts +63 -0
- package/src/components/HeatMapChart/helpers/index.ts +4 -0
- package/src/components/HeatMapChart/index.ts +2 -0
- package/src/components/HeatMapChart/styles.module.scss +96 -0
- package/src/components/HeatMapChart/types.ts +40 -0
- package/src/components/InteractiveChart/InteractiveChart.tsx +75 -0
- package/src/components/InteractiveChart/configurations/boxPlot.ts +75 -0
- package/src/components/InteractiveChart/configurations/defaultPlot.ts +63 -0
- package/src/components/InteractiveChart/configurations/index.ts +2 -0
- package/src/components/InteractiveChart/constants.ts +19 -0
- package/src/components/InteractiveChart/helpers/pathRenderer.ts +48 -0
- package/src/components/InteractiveChart/hooks/useComputedColors.ts +32 -0
- package/src/components/InteractiveChart/hooks/useLayer.ts +33 -0
- package/src/components/InteractiveChart/index.ts +2 -0
- package/src/components/InteractiveChart/plugins/boxPlotPlugin.ts +132 -0
- package/src/components/InteractiveChart/plugins/columnHighlightPlugin.ts +78 -0
- package/src/components/InteractiveChart/plugins/legendAsTooltipPlugin.ts +69 -0
- package/src/components/InteractiveChart/plugins/wheelZoomPlugin.ts +115 -0
- package/src/components/InteractiveChart/styles.module.scss +39 -0
- package/src/components/InteractiveChart/types.ts +7 -0
- package/src/components/PieChart/Legend/Legend.tsx +71 -0
- package/src/components/PieChart/Legend/index.ts +1 -0
- package/src/components/PieChart/Legend/styles.module.scss +45 -0
- package/src/components/PieChart/Pie.tsx +71 -0
- package/src/components/PieChart/PieChart.tsx +154 -0
- package/src/components/PieChart/index.ts +2 -0
- package/src/components/PieChart/styles.module.scss +56 -0
- package/src/components/PieChart/types.ts +42 -0
- package/src/components/index.ts +4 -0
- package/src/constants/colors.ts +70 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
@use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables.scss';
|
|
2
|
+
|
|
3
|
+
$title-height: 74;
|
|
4
|
+
$legend-height: 94;
|
|
5
|
+
$x-axis-label-height: 32;
|
|
6
|
+
$ticks-size: 34;
|
|
7
|
+
|
|
8
|
+
.wrapper {
|
|
9
|
+
padding: 24px;
|
|
10
|
+
background-color: styles-theme-variables.$sys-neutral-background1-level;
|
|
11
|
+
border-radius: 8px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.gridWrapper {
|
|
15
|
+
position: relative;
|
|
16
|
+
display: block;
|
|
17
|
+
|
|
18
|
+
&[data-grid] {
|
|
19
|
+
display: grid;
|
|
20
|
+
grid-auto-rows: minmax(min-content, max-content);
|
|
21
|
+
grid-template-columns: auto 1fr;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.title {
|
|
26
|
+
@include styles-theme-variables.composite-var(styles-theme-variables.$sans-title-m);
|
|
27
|
+
margin-bottom: 24px;
|
|
28
|
+
font-size: 20px;
|
|
29
|
+
font-weight: bold;
|
|
30
|
+
color: styles-theme-variables.$sys-neutral-text-main;
|
|
31
|
+
height: #{$title-height - 24}px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.legend {
|
|
35
|
+
margin-top: 24px;
|
|
36
|
+
height: #{$legend-height - 24}px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.gradient {
|
|
40
|
+
height: 20px;
|
|
41
|
+
margin: 24px 0 8px;
|
|
42
|
+
background: var(--gradient);
|
|
43
|
+
/* stylelint-disable-next-line declaration-no-important */
|
|
44
|
+
background-color: styles-theme-variables.$sys-red-accent-default !important;
|
|
45
|
+
border-radius: 4px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.legendTicksWrapper {
|
|
49
|
+
display: flex;
|
|
50
|
+
justify-content: space-between;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.tick {
|
|
54
|
+
@include styles-theme-variables.composite-var(styles-theme-variables.$sans-body-m);
|
|
55
|
+
|
|
56
|
+
color: styles-theme-variables.$sys-neutral-text-support;
|
|
57
|
+
font-size: 12px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.cell {
|
|
61
|
+
@include styles-theme-variables.composite-var(styles-theme-variables.$sans-body-s);
|
|
62
|
+
|
|
63
|
+
color: var(--color);
|
|
64
|
+
text-overflow: ellipsis;
|
|
65
|
+
overflow: hidden;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.xAxisLabel {
|
|
69
|
+
display: flex;
|
|
70
|
+
justify-content: center;
|
|
71
|
+
margin-top: 8px;
|
|
72
|
+
color: styles-theme-variables.$sys-neutral-text-support;
|
|
73
|
+
font-size: 12px;
|
|
74
|
+
font-weight: 600;
|
|
75
|
+
height: #{$x-axis-label-height - 8}px;
|
|
76
|
+
margin-left: 6px;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.yAxisLabel {
|
|
80
|
+
margin-right: 16px;
|
|
81
|
+
transform: rotate(180deg);
|
|
82
|
+
transform-origin: right, top;
|
|
83
|
+
writing-mode: vertical-rl;
|
|
84
|
+
text-align: center;
|
|
85
|
+
font-size: 12px;
|
|
86
|
+
font-weight: 600;
|
|
87
|
+
color: styles-theme-variables.$sys-neutral-text-support;
|
|
88
|
+
|
|
89
|
+
&[data-x-axis-position='top'] {
|
|
90
|
+
margin-top: #{$ticks-size}px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
&[data-x-axis-position='bottom'] {
|
|
94
|
+
margin-bottom: #{$ticks-size}px;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { XAxisPosition } from './constants';
|
|
4
|
+
|
|
5
|
+
export type HeatMapChartAxisOptions = {
|
|
6
|
+
label?: string;
|
|
7
|
+
ticks?: string[];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type HeatMapChartLegendOptions = {
|
|
11
|
+
show?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type HeatMapChartAxesOptions = {
|
|
15
|
+
xAxis?: HeatMapChartAxisOptions & { position?: XAxisPosition };
|
|
16
|
+
yAxis?: HeatMapChartAxisOptions;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type HeatMapChartStyles = {
|
|
20
|
+
xLabelsStyle: (index: number) => Record<string, string | number>;
|
|
21
|
+
yLabelsStyle: (index: number) => Record<string, string | number>;
|
|
22
|
+
cellStyle: (x: number, y: number, ratio: number) => Record<string, string | number>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type HeatMapChartOptions = {
|
|
26
|
+
title?: string;
|
|
27
|
+
height?: number;
|
|
28
|
+
formatter?: (value: number) => string;
|
|
29
|
+
axes?: HeatMapChartAxesOptions;
|
|
30
|
+
domain: [number, number];
|
|
31
|
+
cellRender?: (x: number, y: number, value: number) => ReactNode;
|
|
32
|
+
legend?: HeatMapChartLegendOptions;
|
|
33
|
+
styles?: Partial<HeatMapChartStyles>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type HeatMapChartProps = {
|
|
37
|
+
data: number[][];
|
|
38
|
+
options: HeatMapChartOptions;
|
|
39
|
+
className?: string;
|
|
40
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import 'uplot/dist/uPlot.min.css';
|
|
2
|
+
|
|
3
|
+
import './styles.module.scss';
|
|
4
|
+
|
|
5
|
+
import cn from 'classnames';
|
|
6
|
+
import merge from 'lodash.merge';
|
|
7
|
+
import { CSSProperties, useMemo } from 'react';
|
|
8
|
+
import uPlot from 'uplot';
|
|
9
|
+
import UPlotReact from 'uplot-react';
|
|
10
|
+
|
|
11
|
+
import { extractSupportProps, WithSupportProps } from '@cloud-ru/uikit-product-utils';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
CHART_OTHER_COLORS,
|
|
15
|
+
CHART_SERIES_COLORS,
|
|
16
|
+
COLOR_CONTAINER_CLASSNAME,
|
|
17
|
+
SeriesColorMap,
|
|
18
|
+
} from '../../constants/colors';
|
|
19
|
+
import { getBoxPlotOptions, getDefaultPlotOptions } from './configurations';
|
|
20
|
+
import { PLOT_TYPES } from './constants';
|
|
21
|
+
import { useComputedColors } from './hooks/useComputedColors';
|
|
22
|
+
import { PlotType } from './types';
|
|
23
|
+
|
|
24
|
+
export type InteractiveChartProps = {
|
|
25
|
+
data: uPlot.AlignedData;
|
|
26
|
+
options?: Partial<uPlot.Options>;
|
|
27
|
+
type?: PlotType;
|
|
28
|
+
className?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function chooseBaseOptions({
|
|
32
|
+
type,
|
|
33
|
+
computedColors,
|
|
34
|
+
}: {
|
|
35
|
+
type: PlotType;
|
|
36
|
+
computedColors: SeriesColorMap;
|
|
37
|
+
}): uPlot.Options {
|
|
38
|
+
switch (type) {
|
|
39
|
+
case PLOT_TYPES.BoxPlot:
|
|
40
|
+
return getBoxPlotOptions({ computedColors });
|
|
41
|
+
case PLOT_TYPES.Default:
|
|
42
|
+
default:
|
|
43
|
+
return getDefaultPlotOptions({ computedColors });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function InteractiveChart({
|
|
48
|
+
data,
|
|
49
|
+
options,
|
|
50
|
+
type = PLOT_TYPES.Default,
|
|
51
|
+
className,
|
|
52
|
+
...rest
|
|
53
|
+
}: WithSupportProps<InteractiveChartProps>) {
|
|
54
|
+
const style = useMemo(
|
|
55
|
+
() =>
|
|
56
|
+
Object.entries({ ...CHART_SERIES_COLORS, ...CHART_OTHER_COLORS }).reduce((styles, [key, value]) => {
|
|
57
|
+
styles[`--${key}`] = value;
|
|
58
|
+
return styles;
|
|
59
|
+
}, {} as CSSProperties),
|
|
60
|
+
[],
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const computedColors = useComputedColors();
|
|
64
|
+
|
|
65
|
+
const resultOptions = useMemo(
|
|
66
|
+
() => merge({}, chooseBaseOptions({ type, computedColors }), options),
|
|
67
|
+
[computedColors, options, type],
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div {...extractSupportProps(rest)} className={cn(COLOR_CONTAINER_CLASSNAME, className)} style={style}>
|
|
72
|
+
<UPlotReact options={resultOptions} data={data} />
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import uPlot from 'uplot';
|
|
2
|
+
|
|
3
|
+
import { ColorMap } from '../../../constants/colors';
|
|
4
|
+
import { boxPlotPlugin } from '../plugins/boxPlotPlugin';
|
|
5
|
+
import { columnHighlightPlugin } from '../plugins/columnHighlightPlugin';
|
|
6
|
+
import { legendAsTooltipPlugin } from '../plugins/legendAsTooltipPlugin';
|
|
7
|
+
|
|
8
|
+
export const getBoxPlotOptions = ({ computedColors }: { computedColors: ColorMap }): uPlot.Options => ({
|
|
9
|
+
id: 'boxPlot',
|
|
10
|
+
title: 'Distribution of object predictions by bin',
|
|
11
|
+
width: 800,
|
|
12
|
+
height: 600,
|
|
13
|
+
cursor: {
|
|
14
|
+
drag: {
|
|
15
|
+
x: false,
|
|
16
|
+
y: false,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
plugins: [
|
|
20
|
+
boxPlotPlugin({ computedColors }),
|
|
21
|
+
columnHighlightPlugin({ computedColors }),
|
|
22
|
+
legendAsTooltipPlugin({ computedColors }),
|
|
23
|
+
],
|
|
24
|
+
series: [
|
|
25
|
+
{
|
|
26
|
+
label: 'bin',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
label: 'X1',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
label: 'Q1',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
label: 'Median',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
label: 'Q3',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
label: 'X2',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
scales: {
|
|
45
|
+
x: {
|
|
46
|
+
distr: 2,
|
|
47
|
+
time: false,
|
|
48
|
+
range: (self, fromMin, fromMax) => [fromMin - 1, fromMax + 1],
|
|
49
|
+
},
|
|
50
|
+
y: {
|
|
51
|
+
time: false,
|
|
52
|
+
auto: true,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
axes: [
|
|
56
|
+
{
|
|
57
|
+
label: 'Bin Number',
|
|
58
|
+
labelSize: 30,
|
|
59
|
+
stroke: '#808080',
|
|
60
|
+
grid: {
|
|
61
|
+
stroke: 'rgba(128, 128, 128, 0.15)',
|
|
62
|
+
width: 1 / devicePixelRatio,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
label: 'Prediction',
|
|
67
|
+
labelSize: 30,
|
|
68
|
+
stroke: '#808080',
|
|
69
|
+
grid: {
|
|
70
|
+
stroke: 'rgba(128, 128, 128, 0.15)',
|
|
71
|
+
width: 1 / devicePixelRatio,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import Color from 'color';
|
|
2
|
+
import uPlot from 'uplot';
|
|
3
|
+
|
|
4
|
+
import { ColorMap, OTHER_COLORS } from '../../../constants/colors';
|
|
5
|
+
import { wheelZoomPlugin } from '../plugins/wheelZoomPlugin';
|
|
6
|
+
|
|
7
|
+
export const getDefaultPlotOptions = ({ computedColors }: { computedColors: ColorMap }): uPlot.Options => {
|
|
8
|
+
const axisColor = new Color(computedColors[OTHER_COLORS.AxisColor]).alpha(0.8).rgb().string();
|
|
9
|
+
const labelColor = computedColors[OTHER_COLORS.LabelColor];
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
id: 'defaultPlot',
|
|
13
|
+
title: 'Title',
|
|
14
|
+
width: 800,
|
|
15
|
+
height: 600,
|
|
16
|
+
cursor: {
|
|
17
|
+
drag: {
|
|
18
|
+
x: true,
|
|
19
|
+
y: true,
|
|
20
|
+
dist: 10,
|
|
21
|
+
uni: 10,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
plugins: [wheelZoomPlugin({ factor: 0.75 })],
|
|
25
|
+
series: [
|
|
26
|
+
{
|
|
27
|
+
label: 'x',
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
scales: {
|
|
31
|
+
x: {
|
|
32
|
+
time: false,
|
|
33
|
+
auto: true,
|
|
34
|
+
},
|
|
35
|
+
y: {
|
|
36
|
+
time: false,
|
|
37
|
+
auto: true,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
axes: [
|
|
41
|
+
{
|
|
42
|
+
show: true,
|
|
43
|
+
label: 'x',
|
|
44
|
+
labelSize: 30,
|
|
45
|
+
stroke: labelColor,
|
|
46
|
+
grid: {
|
|
47
|
+
stroke: axisColor,
|
|
48
|
+
width: 1 / devicePixelRatio,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
show: true,
|
|
53
|
+
label: 'y',
|
|
54
|
+
labelSize: 30,
|
|
55
|
+
stroke: labelColor,
|
|
56
|
+
grid: {
|
|
57
|
+
stroke: axisColor,
|
|
58
|
+
width: 1 / devicePixelRatio,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const PLOT_TYPES = {
|
|
2
|
+
BoxPlot: 'boxPlot',
|
|
3
|
+
Default: 'default',
|
|
4
|
+
} as const;
|
|
5
|
+
|
|
6
|
+
export const LINE_INTERPOLATIONS = {
|
|
7
|
+
Linear: 'linear',
|
|
8
|
+
StepAfter: 'stepAfter',
|
|
9
|
+
StepBefore: 'stepBefore',
|
|
10
|
+
Spline: 'spline',
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
export const DRAW_STYLES = {
|
|
14
|
+
Line: 'line',
|
|
15
|
+
Bars: 'bars',
|
|
16
|
+
Points: 'points',
|
|
17
|
+
BarsLeft: 'barsLeft',
|
|
18
|
+
BarsRight: 'barsRight',
|
|
19
|
+
} as const;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
/* eslint-disable */
|
|
4
|
+
|
|
5
|
+
import uPlot from 'uplot';
|
|
6
|
+
|
|
7
|
+
import { LINE_INTERPOLATIONS, DRAW_STYLES } from '../constants';
|
|
8
|
+
import { DrawStyle, LineInterpolation } from '../types';
|
|
9
|
+
|
|
10
|
+
const { linear, stepped, bars, spline } = uPlot.paths;
|
|
11
|
+
|
|
12
|
+
const _bars60_100 = bars({ size: [0.6, 100] });
|
|
13
|
+
const _bars100Left = bars({ size: [1], align: 1 });
|
|
14
|
+
const _bars100Right = bars({ size: [1], align: -1 });
|
|
15
|
+
const _stepBefore = stepped({ align: -1 });
|
|
16
|
+
const _stepAfter = stepped({ align: 1 });
|
|
17
|
+
const _linear = linear();
|
|
18
|
+
const _spline = spline();
|
|
19
|
+
|
|
20
|
+
export function getPathRenderer(drawStyle: DrawStyle, lineInterpolation?: LineInterpolation) {
|
|
21
|
+
return function pathRenderer(self: uPlot, seriesIdx: number, idx0: number, idx1: number): uPlot.Series.Paths | null {
|
|
22
|
+
const style = drawStyle;
|
|
23
|
+
const interp = lineInterpolation;
|
|
24
|
+
|
|
25
|
+
const renderer =
|
|
26
|
+
style === DRAW_STYLES.Line
|
|
27
|
+
? interp === LINE_INTERPOLATIONS.Linear
|
|
28
|
+
? _linear
|
|
29
|
+
: interp === LINE_INTERPOLATIONS.StepAfter
|
|
30
|
+
? _stepAfter
|
|
31
|
+
: interp === LINE_INTERPOLATIONS.StepBefore
|
|
32
|
+
? _stepBefore
|
|
33
|
+
: interp === LINE_INTERPOLATIONS.Spline
|
|
34
|
+
? _spline
|
|
35
|
+
: null
|
|
36
|
+
: style === DRAW_STYLES.Bars
|
|
37
|
+
? _bars60_100
|
|
38
|
+
: style === DRAW_STYLES.BarsLeft
|
|
39
|
+
? _bars100Left
|
|
40
|
+
: style === DRAW_STYLES.BarsRight
|
|
41
|
+
? _bars100Right
|
|
42
|
+
: style === DRAW_STYLES.Points
|
|
43
|
+
? () => null
|
|
44
|
+
: () => null;
|
|
45
|
+
|
|
46
|
+
return renderer(self, seriesIdx, idx0, idx1);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useTheme } from '@cloud-ru/uikit-product-utils';
|
|
4
|
+
import { useLayoutEffect } from '@snack-uikit/utils';
|
|
5
|
+
|
|
6
|
+
import { COLOR_CONTAINER_CLASSNAME, OTHER_COLORS, SERIES_COLORS, SeriesColorMap } from '../../../constants/colors';
|
|
7
|
+
|
|
8
|
+
const EMPTY_COLORS = {};
|
|
9
|
+
|
|
10
|
+
export function useComputedColors() {
|
|
11
|
+
const { theme } = useTheme();
|
|
12
|
+
const [computedColors, setComputedColors] = useState<SeriesColorMap>(EMPTY_COLORS);
|
|
13
|
+
|
|
14
|
+
useLayoutEffect(() => {
|
|
15
|
+
const colorContainer = document.querySelector('.' + COLOR_CONTAINER_CLASSNAME);
|
|
16
|
+
|
|
17
|
+
if (!colorContainer) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const styles = getComputedStyle(colorContainer);
|
|
22
|
+
|
|
23
|
+
setComputedColors(
|
|
24
|
+
Object.values({ ...SERIES_COLORS, ...OTHER_COLORS }).reduce((colors, color) => {
|
|
25
|
+
colors[color] = styles.getPropertyValue(`--${color}`);
|
|
26
|
+
return colors;
|
|
27
|
+
}, {} as SeriesColorMap),
|
|
28
|
+
);
|
|
29
|
+
}, [theme]);
|
|
30
|
+
|
|
31
|
+
return computedColors;
|
|
32
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import Color from 'color';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { Series } from 'uplot';
|
|
4
|
+
|
|
5
|
+
import { SeriesColor } from '../../../constants/colors';
|
|
6
|
+
import { getPathRenderer } from '../helpers/pathRenderer';
|
|
7
|
+
import { DrawStyle, LineInterpolation } from '../types';
|
|
8
|
+
import { useComputedColors } from './useComputedColors';
|
|
9
|
+
|
|
10
|
+
type Layer = Partial<Series>;
|
|
11
|
+
type UseLayerProps = {
|
|
12
|
+
label: string;
|
|
13
|
+
color: SeriesColor;
|
|
14
|
+
drawStyle: DrawStyle;
|
|
15
|
+
lineInterpolation?: LineInterpolation;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function useLayer({ label, color, drawStyle, lineInterpolation }: UseLayerProps): Layer {
|
|
19
|
+
const computedColors = useComputedColors();
|
|
20
|
+
const stroke = computedColors[color];
|
|
21
|
+
const fill = new Color(stroke).alpha(0.1).rgb().string();
|
|
22
|
+
|
|
23
|
+
return useMemo(
|
|
24
|
+
() => ({
|
|
25
|
+
label,
|
|
26
|
+
stroke,
|
|
27
|
+
fill,
|
|
28
|
+
width: 2,
|
|
29
|
+
paths: getPathRenderer(drawStyle, lineInterpolation),
|
|
30
|
+
}),
|
|
31
|
+
[drawStyle, label, stroke, fill, lineInterpolation],
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
/* eslint-disable */
|
|
4
|
+
import Color from 'color';
|
|
5
|
+
import uPlot from 'uplot';
|
|
6
|
+
|
|
7
|
+
import { SERIES_COLORS, ColorMap, OTHER_COLORS } from '../../../constants/colors';
|
|
8
|
+
|
|
9
|
+
export function boxPlotPlugin({
|
|
10
|
+
gap = 5,
|
|
11
|
+
bodyMaxWidth = 60,
|
|
12
|
+
shadowWidth = 3,
|
|
13
|
+
computedColors,
|
|
14
|
+
}: {
|
|
15
|
+
gap?: number;
|
|
16
|
+
bodyMaxWidth?: number;
|
|
17
|
+
shadowWidth?: number;
|
|
18
|
+
computedColors: ColorMap;
|
|
19
|
+
}) {
|
|
20
|
+
const shadowColor = computedColors[OTHER_COLORS.ShadowColor];
|
|
21
|
+
const lineColor = new Color(computedColors[OTHER_COLORS.LineColor]).alpha(0.5).rgb().string();
|
|
22
|
+
|
|
23
|
+
function roundRect(ctx, x, y, width, height, radius) {
|
|
24
|
+
ctx.beginPath();
|
|
25
|
+
ctx.moveTo(x + radius, y);
|
|
26
|
+
ctx.lineTo(x + width - radius, y);
|
|
27
|
+
ctx.quadraticCurveTo(x + width, y, x + width, y - radius);
|
|
28
|
+
ctx.lineTo(x + width, y + height + radius);
|
|
29
|
+
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
|
30
|
+
ctx.lineTo(x + radius, y + height);
|
|
31
|
+
ctx.quadraticCurveTo(x, y + height, x, y + height + radius);
|
|
32
|
+
ctx.lineTo(x, y - radius);
|
|
33
|
+
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
34
|
+
ctx.closePath();
|
|
35
|
+
ctx.fill();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function drawBoxes(u) {
|
|
39
|
+
u.ctx.save();
|
|
40
|
+
|
|
41
|
+
const offset = (shadowWidth % 2) / 2;
|
|
42
|
+
|
|
43
|
+
u.ctx.translate(offset, offset);
|
|
44
|
+
|
|
45
|
+
const [iMin, iMax] = u.series[0].idxs;
|
|
46
|
+
|
|
47
|
+
for (let i = iMin; i <= iMax; i++) {
|
|
48
|
+
const xVal = u.scales.x.distr === 2 ? i : u.data[0][i];
|
|
49
|
+
const open = u.data[1][i];
|
|
50
|
+
const low = u.data[2][i];
|
|
51
|
+
const median = u.data[3][i];
|
|
52
|
+
const high = u.data[4][i];
|
|
53
|
+
const close = u.data[5][i];
|
|
54
|
+
|
|
55
|
+
const timeAsX = u.valToPos(xVal, 'x', true);
|
|
56
|
+
const openAsY = u.valToPos(open, 'y', true);
|
|
57
|
+
const lowAsY = u.valToPos(low, 'y', true);
|
|
58
|
+
const medianAsY = u.valToPos(median, 'y', true);
|
|
59
|
+
const highAsY = u.valToPos(high, 'y', true);
|
|
60
|
+
const closeAsY = u.valToPos(close, 'y', true);
|
|
61
|
+
|
|
62
|
+
// moustache
|
|
63
|
+
const shadowHeight = closeAsY - openAsY;
|
|
64
|
+
const shadowX = timeAsX - shadowWidth / 2;
|
|
65
|
+
const shadowY = openAsY;
|
|
66
|
+
const columnWidth = u.bbox.width / (iMax - iMin + 2);
|
|
67
|
+
const bodyWidth = Math.min(bodyMaxWidth, columnWidth - gap);
|
|
68
|
+
|
|
69
|
+
u.ctx.fillStyle = shadowColor;
|
|
70
|
+
u.ctx.fillRect(Math.round(shadowX), Math.round(shadowY), Math.round(shadowWidth), Math.round(shadowHeight));
|
|
71
|
+
u.ctx.fillRect(
|
|
72
|
+
Math.round(timeAsX - bodyWidth / 4),
|
|
73
|
+
Math.round(shadowY - shadowWidth / 2),
|
|
74
|
+
Math.round(bodyWidth / 2),
|
|
75
|
+
Math.round(shadowWidth),
|
|
76
|
+
);
|
|
77
|
+
u.ctx.fillRect(
|
|
78
|
+
Math.round(timeAsX - bodyWidth / 4),
|
|
79
|
+
Math.round(shadowY + shadowHeight - shadowWidth / 2),
|
|
80
|
+
Math.round(bodyWidth / 2),
|
|
81
|
+
Math.round(shadowWidth),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// body rect
|
|
85
|
+
const bodyHeight = highAsY - lowAsY;
|
|
86
|
+
const bodyX = timeAsX - bodyWidth / 2;
|
|
87
|
+
const bodyY = lowAsY;
|
|
88
|
+
|
|
89
|
+
if (Math.abs(bodyHeight) > 8) {
|
|
90
|
+
u.ctx.fillStyle = computedColors?.[Object.values(SERIES_COLORS)[i % Object.keys(SERIES_COLORS).length]];
|
|
91
|
+
|
|
92
|
+
roundRect(
|
|
93
|
+
u.ctx,
|
|
94
|
+
Math.round(bodyX),
|
|
95
|
+
Math.round(bodyY),
|
|
96
|
+
Math.round(bodyWidth),
|
|
97
|
+
Math.round(bodyHeight),
|
|
98
|
+
8,
|
|
99
|
+
true,
|
|
100
|
+
true,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
u.ctx.fillStyle = lineColor;
|
|
104
|
+
u.ctx.fillRect(Math.round(bodyX), medianAsY - shadowWidth / 2, Math.round(bodyWidth), Math.round(shadowWidth));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
u.ctx.translate(-offset, -offset);
|
|
109
|
+
|
|
110
|
+
u.ctx.restore();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
opts: (u, opts) => {
|
|
115
|
+
uPlot.assign(opts, {
|
|
116
|
+
cursor: {
|
|
117
|
+
points: {
|
|
118
|
+
show: false,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
opts.series.forEach(series => {
|
|
124
|
+
series.paths = () => null;
|
|
125
|
+
series.points = { show: false };
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
hooks: {
|
|
129
|
+
draw: drawBoxes,
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
/* eslint-disable */
|
|
4
|
+
import Color from 'color';
|
|
5
|
+
import uPlot from 'uplot';
|
|
6
|
+
import { ColorMap, OTHER_COLORS } from '../../../constants/colors';
|
|
7
|
+
|
|
8
|
+
export function columnHighlightPlugin({
|
|
9
|
+
className = '',
|
|
10
|
+
computedColors,
|
|
11
|
+
}: {
|
|
12
|
+
className?: string;
|
|
13
|
+
computedColors: ColorMap;
|
|
14
|
+
}) {
|
|
15
|
+
const backgroundColor = new Color(computedColors[OTHER_COLORS.ColumnHighlightColor]).alpha(0.5).rgb().string();
|
|
16
|
+
|
|
17
|
+
let underEl, overEl, currIdx: number;
|
|
18
|
+
|
|
19
|
+
function init(u: uPlot) {
|
|
20
|
+
underEl = u.under;
|
|
21
|
+
overEl = u.over;
|
|
22
|
+
|
|
23
|
+
const highlightEl = document.createElement('div');
|
|
24
|
+
|
|
25
|
+
className && highlightEl.classList.add(className);
|
|
26
|
+
|
|
27
|
+
uPlot.assign(highlightEl.style, {
|
|
28
|
+
pointerEvents: 'none',
|
|
29
|
+
display: 'none',
|
|
30
|
+
position: 'absolute',
|
|
31
|
+
left: 0,
|
|
32
|
+
top: 0,
|
|
33
|
+
height: '100%',
|
|
34
|
+
backgroundColor,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
underEl.appendChild(highlightEl);
|
|
38
|
+
|
|
39
|
+
u._highlightEl = highlightEl;
|
|
40
|
+
|
|
41
|
+
// show/hide highlight on enter/exit
|
|
42
|
+
overEl.addEventListener('mouseenter', () => {
|
|
43
|
+
highlightEl.style.display = null;
|
|
44
|
+
});
|
|
45
|
+
overEl.addEventListener('mouseleave', () => {
|
|
46
|
+
highlightEl.style.display = 'none';
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function update(u) {
|
|
51
|
+
if (currIdx !== u.cursor.idx) {
|
|
52
|
+
currIdx = u.cursor.idx;
|
|
53
|
+
const highlightEl = u._highlightEl;
|
|
54
|
+
|
|
55
|
+
const dx = u.scales.x.max - u.scales.x.min;
|
|
56
|
+
const width = u.bbox.width / dx / devicePixelRatio;
|
|
57
|
+
const left = u.valToPos(currIdx, 'x') - width / 2;
|
|
58
|
+
|
|
59
|
+
highlightEl.style.transform = 'translateX(' + Math.round(left) + 'px)';
|
|
60
|
+
highlightEl.style.width = Math.round(width) + 'px';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
opts: (u, opts) => {
|
|
66
|
+
uPlot.assign(opts, {
|
|
67
|
+
cursor: {
|
|
68
|
+
x: false,
|
|
69
|
+
y: false,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
hooks: {
|
|
74
|
+
init: init,
|
|
75
|
+
setCursor: update,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|