lupine.components 1.1.55 → 1.1.56
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/LICENSE +21 -21
- package/package.json +1 -1
- package/src/component-pool/aspect-ratio/aspect-ratio-demo.tsx +103 -103
- package/src/component-pool/avatar/avatar-demo.tsx +127 -127
- package/src/component-pool/badge/badge-demo.tsx +152 -152
- package/src/component-pool/breadcrumbs/breadcrumbs-demo.tsx +105 -105
- package/src/component-pool/card/card-demo.tsx +179 -179
- package/src/component-pool/carousel/carousel-demo.tsx +191 -191
- package/src/component-pool/cascader/cascader-demo.tsx +127 -127
- package/src/component-pool/charts/area-chart-demo.tsx +106 -106
- package/src/component-pool/charts/area-chart.tsx +254 -254
- package/src/component-pool/charts/bar-chart-demo.tsx +107 -107
- package/src/component-pool/charts/bar-chart.tsx +223 -223
- package/src/component-pool/charts/chart-utils.ts +182 -182
- package/src/component-pool/charts/column-chart-demo.tsx +111 -111
- package/src/component-pool/charts/column-chart.tsx +230 -230
- package/src/component-pool/charts/donut-chart-demo.tsx +112 -112
- package/src/component-pool/charts/donut-chart.tsx +6 -6
- package/src/component-pool/charts/gauge-chart-demo.tsx +101 -101
- package/src/component-pool/charts/gauge-chart.tsx +117 -117
- package/src/component-pool/charts/index.ts +19 -19
- package/src/component-pool/charts/line-chart-demo.tsx +109 -109
- package/src/component-pool/charts/line-chart.tsx +233 -233
- package/src/component-pool/charts/pie-chart-demo.tsx +133 -133
- package/src/component-pool/charts/pie-chart.tsx +142 -142
- package/src/component-pool/charts/radar-chart-demo.tsx +88 -88
- package/src/component-pool/charts/radar-chart.tsx +231 -231
- package/src/component-pool/charts/scatter-chart-demo.tsx +88 -88
- package/src/component-pool/charts/scatter-chart.tsx +237 -237
- package/src/component-pool/copy-button/copy-button-demo.tsx +71 -71
- package/src/component-pool/date-picker/date-picker-demo.tsx +84 -84
- package/src/component-pool/date-picker/date-picker.tsx +437 -437
- package/src/component-pool/date-picker/index.ts +2 -2
- package/src/component-pool/floating-icon-menu/floating-icon-menu-demo.tsx +94 -94
- package/src/component-pool/floating-icon-menu/floating-icon-menu.tsx +241 -241
- package/src/component-pool/floating-icon-menu/index.ts +2 -2
- package/src/component-pool/h-editor/h-editor-demo.tsx +112 -112
- package/src/component-pool/h-editor/h-editor.ts +862 -862
- package/src/component-pool/h-editor/index.ts +2 -2
- package/src/component-pool/i-editor/i-editor-demo.tsx +72 -72
- package/src/component-pool/i-editor/i-editor-frames.ts +42 -42
- package/src/component-pool/i-editor/i-editor-styles.ts +22 -22
- package/src/component-pool/i-editor/i-editor.ts +3869 -3869
- package/src/component-pool/i-editor/index.ts +2 -2
- package/src/component-pool/map-wrapper/map-wrapper-demo.tsx +55 -55
- package/src/component-pool/p-editor/index.ts +4 -4
- package/src/component-pool/p-editor/p-editor-demo.tsx +51 -51
- package/src/component-pool/p-editor/p-editor-styles.ts +44 -44
- package/src/component-pool/p-editor/p-editor-types.ts +4 -4
- package/src/component-pool/p-editor/p-editor-utils.ts +38 -38
- package/src/component-pool/p-editor/p-editor.ts +2477 -2477
- package/src/component-pool/pdf-viewer/index.ts +2 -2
- package/src/component-pool/pdf-viewer/pdf-viewer-demo.tsx +116 -116
- package/src/component-pool/pdf-viewer/pdf-viewer.tsx +56 -56
- package/src/component-pool/picker-helper.tsx +222 -222
- package/src/component-pool/pull-to-refresh/pull-to-refresh-demo.tsx +99 -99
- package/src/component-pool/qrcode/index.ts +3 -3
- package/src/component-pool/qrcode/qrcode-demo.tsx +102 -102
- package/src/component-pool/qrcode/qrcode.tsx +169 -169
- package/src/component-pool/radial-progress/index.ts +2 -2
- package/src/component-pool/radial-progress/radial-progress-demo.tsx +84 -84
- package/src/component-pool/radial-progress/radial-progress.tsx +118 -118
- package/src/component-pool/range/gauge-demo.tsx +113 -113
- package/src/component-pool/range/gauge.tsx +392 -392
- package/src/component-pool/range/range-demo.tsx +97 -97
- package/src/component-pool/search-input/index.ts +2 -2
- package/src/component-pool/search-input/search-input-demo.tsx +50 -50
- package/src/component-pool/search-input/search-input.tsx +125 -125
- package/src/component-pool/skeleton/skeleton-card.tsx +114 -114
- package/src/component-pool/skeleton/skeleton-demo.tsx +55 -55
- package/src/component-pool/svg-icons.ts +95 -95
- package/src/component-pool/svg-props.tsx +144 -144
- package/src/component-pool/tag-input/index.ts +2 -2
- package/src/component-pool/tag-input/tag-input-demo.tsx +47 -47
- package/src/component-pool/tag-input/tag-input.tsx +166 -166
- package/src/component-pool/time-picker/index.ts +2 -2
- package/src/component-pool/time-picker/time-picker-demo.tsx +80 -80
- package/src/component-pool/time-picker/time-picker.tsx +311 -311
- package/src/component-pool/timeline/timeline-demo.tsx +75 -75
- package/src/component-pool/tooltip/tooltip-demo.tsx +144 -144
- package/src/component-pool/tour/tour-demo.tsx +92 -92
- package/src/component-pool/youtube-player/index.ts +2 -2
- package/src/component-pool/youtube-player/youtube-player-demo.tsx +57 -57
- package/src/component-pool/youtube-player/youtube-player.tsx +116 -116
- package/src/components/action-sheet-color.tsx +269 -269
- package/src/components/action-sheet-date.tsx +299 -299
- package/src/components/action-sheet-demo.tsx +216 -216
- package/src/components/action-sheet-theme.tsx +150 -150
- package/src/components/action-sheet-time.tsx +157 -157
- package/src/components/input-number.tsx +206 -206
- package/src/components/input-with-title-demo.tsx +42 -42
- package/src/components/loading-spin.tsx +74 -74
- package/src/components/message-box-demo.tsx +107 -107
- package/src/components/mobile-components/mobile-side-menu-demo.tsx +47 -47
- package/src/components/modal-demo.tsx +56 -56
- package/src/components/notice-message-demo.tsx +57 -57
- package/src/components/paging-link-demo.tsx +430 -430
- package/src/components/popup-menu-demo.tsx +172 -172
- package/src/components/progress-demo.tsx +53 -53
- package/src/components/radio-label-demo.tsx +41 -41
- package/src/components/redirect-demo.tsx +36 -36
- package/src/components/resizable-splitter-demo.tsx +104 -104
- package/src/components/select-angle-demo.tsx +32 -32
- package/src/components/select-with-title-demo.tsx +53 -53
- package/src/components/slide-tab-component-demo.tsx +86 -86
- package/src/components/spinner-demo.tsx +52 -52
- package/src/components/stars-component-demo.tsx +36 -36
- package/src/components/switch-option-component.tsx +74 -74
- package/src/components/switch-option-demo.tsx +36 -36
- package/src/components/tabs-demo.tsx +42 -42
- package/src/components/text-glow-demo.tsx +46 -46
- package/src/components/text-scale-demo.tsx +41 -41
- package/src/components/text-wave-demo.tsx +48 -48
- package/src/components/toggle-button-demo.tsx +230 -230
- package/src/demo/demo-page.tsx +271 -271
- package/src/demo/demo-registry.ts +143 -143
- package/src/demo/demo-render-page.tsx +94 -94
- package/src/demo/demo-types.ts +22 -22
- package/src/demo/mock/demo-icons.ts +72 -72
- package/src/demo/mock/responsive-demo-mock-pages.tsx +197 -197
- package/src/demo/mock/side-menu-mock.tsx +113 -113
- package/src/demo/mock/user-settings-mock.tsx +310 -310
- package/src/frames/responsive-frame-demo.tsx +120 -120
- package/src/frames/slider-frame-demo.tsx +111 -111
- package/src/frames/slider-helper.tsx +194 -194
- package/src/lib/button-async-lock.tsx +43 -43
- package/src/lib/canvas/canvas-drawing.ts +503 -503
- package/src/lib/canvas/canvas-geometry.ts +255 -255
- package/src/lib/canvas/canvas-helper.tsx +176 -176
- package/src/lib/canvas/canvas-image.ts +110 -110
- package/src/lib/canvas/canvas-types.ts +93 -93
- package/src/lib/canvas/index.ts +5 -5
- package/src/lib/disable-console.ts +11 -11
- package/src/lib/disable-debug.ts +8 -8
- package/src/lib/support-safe-area.ts +25 -25
- package/tsconfig.json +113 -113
|
@@ -1,107 +1,107 @@
|
|
|
1
|
-
import { BarChart } from './bar-chart';
|
|
2
|
-
import { DemoStory } from '../../demo/demo-types';
|
|
3
|
-
import { ChartData } from './chart-utils';
|
|
4
|
-
|
|
5
|
-
const singleSeriesData: ChartData = {
|
|
6
|
-
labels: ['United States', 'China', 'Japan', 'Germany', 'United Kingdom'],
|
|
7
|
-
series: [
|
|
8
|
-
{
|
|
9
|
-
name: 'GDP (Trillions)',
|
|
10
|
-
data: [25.4, 17.9, 4.2, 4.0, 3.0],
|
|
11
|
-
},
|
|
12
|
-
],
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const multiSeriesData: ChartData = {
|
|
16
|
-
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
|
|
17
|
-
series: [
|
|
18
|
-
{ name: 'Product A', data: [40, 30, 50, 70] },
|
|
19
|
-
{ name: 'Product B', data: [20, 40, 30, 60] },
|
|
20
|
-
],
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export const barChartDemo: DemoStory<any> = {
|
|
24
|
-
id: 'barChartDemo',
|
|
25
|
-
text: 'Bar Chart',
|
|
26
|
-
args: {
|
|
27
|
-
title: 'Top Economies',
|
|
28
|
-
width: '100%',
|
|
29
|
-
showLegend: true,
|
|
30
|
-
},
|
|
31
|
-
argTypes: {
|
|
32
|
-
title: { control: 'text' },
|
|
33
|
-
width: { control: 'text' },
|
|
34
|
-
showLegend: { control: 'boolean' },
|
|
35
|
-
},
|
|
36
|
-
render: (args: any) => {
|
|
37
|
-
const css = {
|
|
38
|
-
display: 'flex',
|
|
39
|
-
flexDirection: 'column',
|
|
40
|
-
gap: '48px',
|
|
41
|
-
padding: '24px',
|
|
42
|
-
alignItems: 'center',
|
|
43
|
-
|
|
44
|
-
'.demo-section': {
|
|
45
|
-
display: 'flex',
|
|
46
|
-
flexDirection: 'column',
|
|
47
|
-
alignItems: 'center',
|
|
48
|
-
gap: '24px',
|
|
49
|
-
width: '100%',
|
|
50
|
-
maxWidth: '800px',
|
|
51
|
-
},
|
|
52
|
-
'.section-title': {
|
|
53
|
-
fontSize: '20px',
|
|
54
|
-
fontWeight: 'bold',
|
|
55
|
-
color: 'var(--primary-color)',
|
|
56
|
-
marginBottom: '16px',
|
|
57
|
-
borderBottom: '2px solid var(--secondary-border-color)',
|
|
58
|
-
width: '100%',
|
|
59
|
-
paddingBottom: '8px',
|
|
60
|
-
},
|
|
61
|
-
'.chart-box': {
|
|
62
|
-
padding: '20px',
|
|
63
|
-
border: '1px solid var(--secondary-border-color)',
|
|
64
|
-
borderRadius: '8px',
|
|
65
|
-
backgroundColor: 'var(--primary-bg-color)',
|
|
66
|
-
width: '100%',
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
return (
|
|
71
|
-
<div css={css}>
|
|
72
|
-
<section class='demo-section'>
|
|
73
|
-
<div class='section-title'>Interactive Control</div>
|
|
74
|
-
<div class='chart-box'>
|
|
75
|
-
<BarChart
|
|
76
|
-
data={singleSeriesData}
|
|
77
|
-
title={args.title}
|
|
78
|
-
width={args.width}
|
|
79
|
-
showLegend={args.showLegend}
|
|
80
|
-
xAxisFormatter={(val) => '$' + val + 'T'}
|
|
81
|
-
/>
|
|
82
|
-
</div>
|
|
83
|
-
</section>
|
|
84
|
-
|
|
85
|
-
<section class='demo-section'>
|
|
86
|
-
<div class='section-title'>Multi-Series (Grouped Horizontal)</div>
|
|
87
|
-
<div class='chart-box'>
|
|
88
|
-
<BarChart data={multiSeriesData} title='Quarterly Comparison' showLegend={true} />
|
|
89
|
-
</div>
|
|
90
|
-
</section>
|
|
91
|
-
</div>
|
|
92
|
-
);
|
|
93
|
-
},
|
|
94
|
-
code: `import { BarChart } from 'lupine.components/component-pool/charts';
|
|
95
|
-
|
|
96
|
-
const data = {
|
|
97
|
-
labels: ['US', 'CN', 'JP'],
|
|
98
|
-
series: [{ name: 'GDP', data: [25, 18, 4] }]
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
<BarChart
|
|
102
|
-
data={data}
|
|
103
|
-
title="Top Economies"
|
|
104
|
-
xAxisFormatter={val => '$' + val + 'T'}
|
|
105
|
-
/>
|
|
106
|
-
`,
|
|
107
|
-
};
|
|
1
|
+
import { BarChart } from './bar-chart';
|
|
2
|
+
import { DemoStory } from '../../demo/demo-types';
|
|
3
|
+
import { ChartData } from './chart-utils';
|
|
4
|
+
|
|
5
|
+
const singleSeriesData: ChartData = {
|
|
6
|
+
labels: ['United States', 'China', 'Japan', 'Germany', 'United Kingdom'],
|
|
7
|
+
series: [
|
|
8
|
+
{
|
|
9
|
+
name: 'GDP (Trillions)',
|
|
10
|
+
data: [25.4, 17.9, 4.2, 4.0, 3.0],
|
|
11
|
+
},
|
|
12
|
+
],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const multiSeriesData: ChartData = {
|
|
16
|
+
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
|
|
17
|
+
series: [
|
|
18
|
+
{ name: 'Product A', data: [40, 30, 50, 70] },
|
|
19
|
+
{ name: 'Product B', data: [20, 40, 30, 60] },
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const barChartDemo: DemoStory<any> = {
|
|
24
|
+
id: 'barChartDemo',
|
|
25
|
+
text: 'Bar Chart',
|
|
26
|
+
args: {
|
|
27
|
+
title: 'Top Economies',
|
|
28
|
+
width: '100%',
|
|
29
|
+
showLegend: true,
|
|
30
|
+
},
|
|
31
|
+
argTypes: {
|
|
32
|
+
title: { control: 'text' },
|
|
33
|
+
width: { control: 'text' },
|
|
34
|
+
showLegend: { control: 'boolean' },
|
|
35
|
+
},
|
|
36
|
+
render: (args: any) => {
|
|
37
|
+
const css = {
|
|
38
|
+
display: 'flex',
|
|
39
|
+
flexDirection: 'column',
|
|
40
|
+
gap: '48px',
|
|
41
|
+
padding: '24px',
|
|
42
|
+
alignItems: 'center',
|
|
43
|
+
|
|
44
|
+
'.demo-section': {
|
|
45
|
+
display: 'flex',
|
|
46
|
+
flexDirection: 'column',
|
|
47
|
+
alignItems: 'center',
|
|
48
|
+
gap: '24px',
|
|
49
|
+
width: '100%',
|
|
50
|
+
maxWidth: '800px',
|
|
51
|
+
},
|
|
52
|
+
'.section-title': {
|
|
53
|
+
fontSize: '20px',
|
|
54
|
+
fontWeight: 'bold',
|
|
55
|
+
color: 'var(--primary-color)',
|
|
56
|
+
marginBottom: '16px',
|
|
57
|
+
borderBottom: '2px solid var(--secondary-border-color)',
|
|
58
|
+
width: '100%',
|
|
59
|
+
paddingBottom: '8px',
|
|
60
|
+
},
|
|
61
|
+
'.chart-box': {
|
|
62
|
+
padding: '20px',
|
|
63
|
+
border: '1px solid var(--secondary-border-color)',
|
|
64
|
+
borderRadius: '8px',
|
|
65
|
+
backgroundColor: 'var(--primary-bg-color)',
|
|
66
|
+
width: '100%',
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div css={css}>
|
|
72
|
+
<section class='demo-section'>
|
|
73
|
+
<div class='section-title'>Interactive Control</div>
|
|
74
|
+
<div class='chart-box'>
|
|
75
|
+
<BarChart
|
|
76
|
+
data={singleSeriesData}
|
|
77
|
+
title={args.title}
|
|
78
|
+
width={args.width}
|
|
79
|
+
showLegend={args.showLegend}
|
|
80
|
+
xAxisFormatter={(val) => '$' + val + 'T'}
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
</section>
|
|
84
|
+
|
|
85
|
+
<section class='demo-section'>
|
|
86
|
+
<div class='section-title'>Multi-Series (Grouped Horizontal)</div>
|
|
87
|
+
<div class='chart-box'>
|
|
88
|
+
<BarChart data={multiSeriesData} title='Quarterly Comparison' showLegend={true} />
|
|
89
|
+
</div>
|
|
90
|
+
</section>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
},
|
|
94
|
+
code: `import { BarChart } from 'lupine.components/component-pool/charts';
|
|
95
|
+
|
|
96
|
+
const data = {
|
|
97
|
+
labels: ['US', 'CN', 'JP'],
|
|
98
|
+
series: [{ name: 'GDP', data: [25, 18, 4] }]
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
<BarChart
|
|
102
|
+
data={data}
|
|
103
|
+
title="Top Economies"
|
|
104
|
+
xAxisFormatter={val => '$' + val + 'T'}
|
|
105
|
+
/>
|
|
106
|
+
`,
|
|
107
|
+
};
|
|
@@ -1,223 +1,223 @@
|
|
|
1
|
-
import { bindGlobalStyle, getGlobalStylesId, HtmlVar } from 'lupine.components';
|
|
2
|
-
import { chartCommonCss, getChartColor, BasicChartProps } from './chart-utils';
|
|
3
|
-
import { Tooltip } from '../tooltip';
|
|
4
|
-
|
|
5
|
-
export type BarChartProps = BasicChartProps & {
|
|
6
|
-
xAxisFormatter?: (value: number) => string;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export const BarChart = (props: BarChartProps) => {
|
|
10
|
-
const globalCssId = getGlobalStylesId(chartCommonCss);
|
|
11
|
-
bindGlobalStyle(globalCssId, chartCommonCss);
|
|
12
|
-
|
|
13
|
-
const showLegend = props.showLegend !== false;
|
|
14
|
-
const series = props.data.series;
|
|
15
|
-
const labels = props.data.labels;
|
|
16
|
-
|
|
17
|
-
if (!series || series.length === 0 || labels.length === 0) {
|
|
18
|
-
return <div class='&-container'>No data</div>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Find max value for X axis scale
|
|
22
|
-
let maxVal = 0;
|
|
23
|
-
series.forEach((s) => {
|
|
24
|
-
s.data.forEach((val) => {
|
|
25
|
-
if (val > maxVal) maxVal = val;
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const tickCount = 5;
|
|
30
|
-
const order = Math.floor(Math.log10(maxVal || 1));
|
|
31
|
-
const magnitude = Math.pow(10, order);
|
|
32
|
-
const niceStep = Math.ceil(maxVal / magnitude / tickCount) * magnitude;
|
|
33
|
-
const niceMax = niceStep * tickCount;
|
|
34
|
-
|
|
35
|
-
const xTicks = Array.from({ length: tickCount + 1 }, (_, i) => parseFloat((i * niceStep).toPrecision(12)));
|
|
36
|
-
|
|
37
|
-
const renderChart = (viewBoxWidth: number, viewBoxHeight: number) => {
|
|
38
|
-
// Layout parameters for Bar Chart (flipped)
|
|
39
|
-
const padding = { top: 20, right: 60, bottom: 40, left: 150 }; // larger left padding for labels
|
|
40
|
-
|
|
41
|
-
const chartWidth = viewBoxWidth - padding.left - padding.right;
|
|
42
|
-
const chartHeight = viewBoxHeight - padding.top - padding.bottom;
|
|
43
|
-
|
|
44
|
-
const groupHeight = chartHeight / Math.max(1, labels.length);
|
|
45
|
-
// Reduce barPadding a bit, ensure the whole group fits inside the allocated groupHeight height.
|
|
46
|
-
const barPadding = 0.2 * groupHeight;
|
|
47
|
-
const barHeight = (groupHeight - barPadding) / Math.max(1, series.length);
|
|
48
|
-
|
|
49
|
-
const formatX = props.xAxisFormatter || ((val) => val.toString());
|
|
50
|
-
|
|
51
|
-
// Render X Axis Ticks & Grid (Vertical lines now)
|
|
52
|
-
const renderXAxis = () => {
|
|
53
|
-
return xTicks.map((val) => {
|
|
54
|
-
const x = padding.left + (val / niceMax) * chartWidth;
|
|
55
|
-
return (
|
|
56
|
-
<g>
|
|
57
|
-
<line
|
|
58
|
-
x1={x}
|
|
59
|
-
y1={padding.top}
|
|
60
|
-
x2={x}
|
|
61
|
-
y2={viewBoxHeight - padding.bottom}
|
|
62
|
-
stroke='var(--secondary-border-color)'
|
|
63
|
-
stroke-width='1'
|
|
64
|
-
stroke-dasharray='4 4'
|
|
65
|
-
/>
|
|
66
|
-
<text
|
|
67
|
-
x={x}
|
|
68
|
-
y={viewBoxHeight - padding.bottom + 25}
|
|
69
|
-
fill='var(--primary-color)'
|
|
70
|
-
fontSize='12'
|
|
71
|
-
text-anchor='middle'
|
|
72
|
-
>
|
|
73
|
-
{formatX(val)}
|
|
74
|
-
</text>
|
|
75
|
-
</g>
|
|
76
|
-
);
|
|
77
|
-
});
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
// Render Y Axis Labels (Horizontal text now)
|
|
81
|
-
const renderYAxis = () => {
|
|
82
|
-
return labels.map((label, index) => {
|
|
83
|
-
const y = padding.top + index * groupHeight + groupHeight / 2;
|
|
84
|
-
return (
|
|
85
|
-
<text
|
|
86
|
-
x={padding.left - 15}
|
|
87
|
-
y={y + 4} // slight alignment offset
|
|
88
|
-
fill='var(--primary-color)'
|
|
89
|
-
fontSize='12'
|
|
90
|
-
text-anchor='end'
|
|
91
|
-
>
|
|
92
|
-
{label}
|
|
93
|
-
</text>
|
|
94
|
-
);
|
|
95
|
-
});
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
// Render Bars (Horizontal rectangles now)
|
|
99
|
-
const renderBars = () => {
|
|
100
|
-
const bars: any[] = [];
|
|
101
|
-
|
|
102
|
-
labels.forEach((label, lIndex) => {
|
|
103
|
-
const groupY = padding.top + lIndex * groupHeight + barPadding / 2;
|
|
104
|
-
|
|
105
|
-
series.forEach((s, sIndex) => {
|
|
106
|
-
const val = s.data[lIndex] || 0;
|
|
107
|
-
const color = s.color || getChartColor(sIndex);
|
|
108
|
-
|
|
109
|
-
const barWidth = (val / niceMax) * chartWidth;
|
|
110
|
-
const x = padding.left;
|
|
111
|
-
const y = groupY + sIndex * barHeight;
|
|
112
|
-
|
|
113
|
-
const handleMouseEnter = (e: any) => {
|
|
114
|
-
Tooltip.show(
|
|
115
|
-
e,
|
|
116
|
-
<div>
|
|
117
|
-
<div style={{ fontWeight: 'bold' }}>{label}</div>
|
|
118
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
|
119
|
-
<div style={{ width: '10px', height: '10px', backgroundColor: color, borderRadius: '2px' }} />
|
|
120
|
-
<span>
|
|
121
|
-
{s.name}: {formatX(val)}
|
|
122
|
-
</span>
|
|
123
|
-
</div>
|
|
124
|
-
</div>,
|
|
125
|
-
{ position: 'auto' }
|
|
126
|
-
);
|
|
127
|
-
e.target.style.opacity = 0.8;
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const handleMouseLeave = (e: any) => {
|
|
131
|
-
Tooltip.hide();
|
|
132
|
-
e.target.style.opacity = 1;
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
bars.push(
|
|
136
|
-
<rect
|
|
137
|
-
class='chart-element'
|
|
138
|
-
x={x}
|
|
139
|
-
y={y}
|
|
140
|
-
width={barWidth}
|
|
141
|
-
height={barHeight - 2} // -2 for slight individual bar padding
|
|
142
|
-
fill={color}
|
|
143
|
-
rx='2'
|
|
144
|
-
ry='2'
|
|
145
|
-
onMouseEnter={handleMouseEnter}
|
|
146
|
-
onMouseLeave={handleMouseLeave}
|
|
147
|
-
/>
|
|
148
|
-
);
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
return bars;
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
return (
|
|
156
|
-
<svg
|
|
157
|
-
class='chart-svg'
|
|
158
|
-
style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', overflow: 'visible' }}
|
|
159
|
-
viewBox={`0 0 ${viewBoxWidth} ${viewBoxHeight}`}
|
|
160
|
-
preserveAspectRatio='none'
|
|
161
|
-
>
|
|
162
|
-
{/* Base Axis line (Vertical now) */}
|
|
163
|
-
<line
|
|
164
|
-
x1={padding.left}
|
|
165
|
-
y1={padding.top}
|
|
166
|
-
x2={padding.left}
|
|
167
|
-
y2={viewBoxHeight - padding.bottom}
|
|
168
|
-
stroke='var(--secondary-border-color)'
|
|
169
|
-
stroke-width='2'
|
|
170
|
-
/>
|
|
171
|
-
|
|
172
|
-
<g class='x-axis'>{renderXAxis()}</g>
|
|
173
|
-
<g class='y-axis'>{renderYAxis()}</g>
|
|
174
|
-
<g class='bars'>{renderBars()}</g>
|
|
175
|
-
</svg>
|
|
176
|
-
);
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
const chartVar = new HtmlVar(renderChart(1000, Math.max(300, labels.length * 40 + 60)));
|
|
180
|
-
|
|
181
|
-
const ref = {
|
|
182
|
-
globalCssId,
|
|
183
|
-
onLoad: async (el: Element) => {
|
|
184
|
-
const ro = new ResizeObserver((entries) => {
|
|
185
|
-
const { width } = entries[0].contentRect;
|
|
186
|
-
if (width > 50) {
|
|
187
|
-
chartVar.value = renderChart(width, Math.max(300, labels.length * 40 + 60));
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
ro.observe(el);
|
|
191
|
-
(el as any)._ro = ro;
|
|
192
|
-
},
|
|
193
|
-
onUnload: async (el: Element) => {
|
|
194
|
-
if ((el as any)._ro) {
|
|
195
|
-
(el as any)._ro.disconnect();
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
const ratio = props.aspectRatio ?? 16 / 9;
|
|
201
|
-
const paddingTop = `${(1 / ratio) * 100}%`;
|
|
202
|
-
|
|
203
|
-
const styleStr = `width: ${props.width || '100%'};`;
|
|
204
|
-
|
|
205
|
-
return (
|
|
206
|
-
<div ref={ref} class='&-container' style={styleStr}>
|
|
207
|
-
{props.title && <div class='chart-title'>{props.title}</div>}
|
|
208
|
-
|
|
209
|
-
<div style={{ flex: 1, position: 'relative', minHeight: 0, paddingTop }}>{chartVar.node}</div>
|
|
210
|
-
|
|
211
|
-
{showLegend && (
|
|
212
|
-
<div class='chart-legend'>
|
|
213
|
-
{series.map((s, i) => (
|
|
214
|
-
<div class='legend-item'>
|
|
215
|
-
<div class='legend-color' style={{ backgroundColor: s.color || getChartColor(i) }} />
|
|
216
|
-
<div>{s.name}</div>
|
|
217
|
-
</div>
|
|
218
|
-
))}
|
|
219
|
-
</div>
|
|
220
|
-
)}
|
|
221
|
-
</div>
|
|
222
|
-
);
|
|
223
|
-
};
|
|
1
|
+
import { bindGlobalStyle, getGlobalStylesId, HtmlVar } from 'lupine.components';
|
|
2
|
+
import { chartCommonCss, getChartColor, BasicChartProps } from './chart-utils';
|
|
3
|
+
import { Tooltip } from '../tooltip';
|
|
4
|
+
|
|
5
|
+
export type BarChartProps = BasicChartProps & {
|
|
6
|
+
xAxisFormatter?: (value: number) => string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const BarChart = (props: BarChartProps) => {
|
|
10
|
+
const globalCssId = getGlobalStylesId(chartCommonCss);
|
|
11
|
+
bindGlobalStyle(globalCssId, chartCommonCss);
|
|
12
|
+
|
|
13
|
+
const showLegend = props.showLegend !== false;
|
|
14
|
+
const series = props.data.series;
|
|
15
|
+
const labels = props.data.labels;
|
|
16
|
+
|
|
17
|
+
if (!series || series.length === 0 || labels.length === 0) {
|
|
18
|
+
return <div class='&-container'>No data</div>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Find max value for X axis scale
|
|
22
|
+
let maxVal = 0;
|
|
23
|
+
series.forEach((s) => {
|
|
24
|
+
s.data.forEach((val) => {
|
|
25
|
+
if (val > maxVal) maxVal = val;
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const tickCount = 5;
|
|
30
|
+
const order = Math.floor(Math.log10(maxVal || 1));
|
|
31
|
+
const magnitude = Math.pow(10, order);
|
|
32
|
+
const niceStep = Math.ceil(maxVal / magnitude / tickCount) * magnitude;
|
|
33
|
+
const niceMax = niceStep * tickCount;
|
|
34
|
+
|
|
35
|
+
const xTicks = Array.from({ length: tickCount + 1 }, (_, i) => parseFloat((i * niceStep).toPrecision(12)));
|
|
36
|
+
|
|
37
|
+
const renderChart = (viewBoxWidth: number, viewBoxHeight: number) => {
|
|
38
|
+
// Layout parameters for Bar Chart (flipped)
|
|
39
|
+
const padding = { top: 20, right: 60, bottom: 40, left: 150 }; // larger left padding for labels
|
|
40
|
+
|
|
41
|
+
const chartWidth = viewBoxWidth - padding.left - padding.right;
|
|
42
|
+
const chartHeight = viewBoxHeight - padding.top - padding.bottom;
|
|
43
|
+
|
|
44
|
+
const groupHeight = chartHeight / Math.max(1, labels.length);
|
|
45
|
+
// Reduce barPadding a bit, ensure the whole group fits inside the allocated groupHeight height.
|
|
46
|
+
const barPadding = 0.2 * groupHeight;
|
|
47
|
+
const barHeight = (groupHeight - barPadding) / Math.max(1, series.length);
|
|
48
|
+
|
|
49
|
+
const formatX = props.xAxisFormatter || ((val) => val.toString());
|
|
50
|
+
|
|
51
|
+
// Render X Axis Ticks & Grid (Vertical lines now)
|
|
52
|
+
const renderXAxis = () => {
|
|
53
|
+
return xTicks.map((val) => {
|
|
54
|
+
const x = padding.left + (val / niceMax) * chartWidth;
|
|
55
|
+
return (
|
|
56
|
+
<g>
|
|
57
|
+
<line
|
|
58
|
+
x1={x}
|
|
59
|
+
y1={padding.top}
|
|
60
|
+
x2={x}
|
|
61
|
+
y2={viewBoxHeight - padding.bottom}
|
|
62
|
+
stroke='var(--secondary-border-color)'
|
|
63
|
+
stroke-width='1'
|
|
64
|
+
stroke-dasharray='4 4'
|
|
65
|
+
/>
|
|
66
|
+
<text
|
|
67
|
+
x={x}
|
|
68
|
+
y={viewBoxHeight - padding.bottom + 25}
|
|
69
|
+
fill='var(--primary-color)'
|
|
70
|
+
fontSize='12'
|
|
71
|
+
text-anchor='middle'
|
|
72
|
+
>
|
|
73
|
+
{formatX(val)}
|
|
74
|
+
</text>
|
|
75
|
+
</g>
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Render Y Axis Labels (Horizontal text now)
|
|
81
|
+
const renderYAxis = () => {
|
|
82
|
+
return labels.map((label, index) => {
|
|
83
|
+
const y = padding.top + index * groupHeight + groupHeight / 2;
|
|
84
|
+
return (
|
|
85
|
+
<text
|
|
86
|
+
x={padding.left - 15}
|
|
87
|
+
y={y + 4} // slight alignment offset
|
|
88
|
+
fill='var(--primary-color)'
|
|
89
|
+
fontSize='12'
|
|
90
|
+
text-anchor='end'
|
|
91
|
+
>
|
|
92
|
+
{label}
|
|
93
|
+
</text>
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Render Bars (Horizontal rectangles now)
|
|
99
|
+
const renderBars = () => {
|
|
100
|
+
const bars: any[] = [];
|
|
101
|
+
|
|
102
|
+
labels.forEach((label, lIndex) => {
|
|
103
|
+
const groupY = padding.top + lIndex * groupHeight + barPadding / 2;
|
|
104
|
+
|
|
105
|
+
series.forEach((s, sIndex) => {
|
|
106
|
+
const val = s.data[lIndex] || 0;
|
|
107
|
+
const color = s.color || getChartColor(sIndex);
|
|
108
|
+
|
|
109
|
+
const barWidth = (val / niceMax) * chartWidth;
|
|
110
|
+
const x = padding.left;
|
|
111
|
+
const y = groupY + sIndex * barHeight;
|
|
112
|
+
|
|
113
|
+
const handleMouseEnter = (e: any) => {
|
|
114
|
+
Tooltip.show(
|
|
115
|
+
e,
|
|
116
|
+
<div>
|
|
117
|
+
<div style={{ fontWeight: 'bold' }}>{label}</div>
|
|
118
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
|
119
|
+
<div style={{ width: '10px', height: '10px', backgroundColor: color, borderRadius: '2px' }} />
|
|
120
|
+
<span>
|
|
121
|
+
{s.name}: {formatX(val)}
|
|
122
|
+
</span>
|
|
123
|
+
</div>
|
|
124
|
+
</div>,
|
|
125
|
+
{ position: 'auto' }
|
|
126
|
+
);
|
|
127
|
+
e.target.style.opacity = 0.8;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const handleMouseLeave = (e: any) => {
|
|
131
|
+
Tooltip.hide();
|
|
132
|
+
e.target.style.opacity = 1;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
bars.push(
|
|
136
|
+
<rect
|
|
137
|
+
class='chart-element'
|
|
138
|
+
x={x}
|
|
139
|
+
y={y}
|
|
140
|
+
width={barWidth}
|
|
141
|
+
height={barHeight - 2} // -2 for slight individual bar padding
|
|
142
|
+
fill={color}
|
|
143
|
+
rx='2'
|
|
144
|
+
ry='2'
|
|
145
|
+
onMouseEnter={handleMouseEnter}
|
|
146
|
+
onMouseLeave={handleMouseLeave}
|
|
147
|
+
/>
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return bars;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<svg
|
|
157
|
+
class='chart-svg'
|
|
158
|
+
style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', overflow: 'visible' }}
|
|
159
|
+
viewBox={`0 0 ${viewBoxWidth} ${viewBoxHeight}`}
|
|
160
|
+
preserveAspectRatio='none'
|
|
161
|
+
>
|
|
162
|
+
{/* Base Axis line (Vertical now) */}
|
|
163
|
+
<line
|
|
164
|
+
x1={padding.left}
|
|
165
|
+
y1={padding.top}
|
|
166
|
+
x2={padding.left}
|
|
167
|
+
y2={viewBoxHeight - padding.bottom}
|
|
168
|
+
stroke='var(--secondary-border-color)'
|
|
169
|
+
stroke-width='2'
|
|
170
|
+
/>
|
|
171
|
+
|
|
172
|
+
<g class='x-axis'>{renderXAxis()}</g>
|
|
173
|
+
<g class='y-axis'>{renderYAxis()}</g>
|
|
174
|
+
<g class='bars'>{renderBars()}</g>
|
|
175
|
+
</svg>
|
|
176
|
+
);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const chartVar = new HtmlVar(renderChart(1000, Math.max(300, labels.length * 40 + 60)));
|
|
180
|
+
|
|
181
|
+
const ref = {
|
|
182
|
+
globalCssId,
|
|
183
|
+
onLoad: async (el: Element) => {
|
|
184
|
+
const ro = new ResizeObserver((entries) => {
|
|
185
|
+
const { width } = entries[0].contentRect;
|
|
186
|
+
if (width > 50) {
|
|
187
|
+
chartVar.value = renderChart(width, Math.max(300, labels.length * 40 + 60));
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
ro.observe(el);
|
|
191
|
+
(el as any)._ro = ro;
|
|
192
|
+
},
|
|
193
|
+
onUnload: async (el: Element) => {
|
|
194
|
+
if ((el as any)._ro) {
|
|
195
|
+
(el as any)._ro.disconnect();
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const ratio = props.aspectRatio ?? 16 / 9;
|
|
201
|
+
const paddingTop = `${(1 / ratio) * 100}%`;
|
|
202
|
+
|
|
203
|
+
const styleStr = `width: ${props.width || '100%'};`;
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<div ref={ref} class='&-container' style={styleStr}>
|
|
207
|
+
{props.title && <div class='chart-title'>{props.title}</div>}
|
|
208
|
+
|
|
209
|
+
<div style={{ flex: 1, position: 'relative', minHeight: 0, paddingTop }}>{chartVar.node}</div>
|
|
210
|
+
|
|
211
|
+
{showLegend && (
|
|
212
|
+
<div class='chart-legend'>
|
|
213
|
+
{series.map((s, i) => (
|
|
214
|
+
<div class='legend-item'>
|
|
215
|
+
<div class='legend-color' style={{ backgroundColor: s.color || getChartColor(i) }} />
|
|
216
|
+
<div>{s.name}</div>
|
|
217
|
+
</div>
|
|
218
|
+
))}
|
|
219
|
+
</div>
|
|
220
|
+
)}
|
|
221
|
+
</div>
|
|
222
|
+
);
|
|
223
|
+
};
|