@qtsurfer/sveltecharts 0.2.12 → 0.4.0

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.
@@ -1,88 +1,158 @@
1
- <script context="module">import { init, use } from "echarts/core";
2
- import { LineChart } from "echarts/charts";
3
- import {
4
- DataZoomComponent,
5
- LegendComponent,
6
- TitleComponent,
7
- TooltipComponent,
8
- GridComponent,
9
- DatasetComponent
10
- } from "echarts/components";
11
- import { LabelLayout } from "echarts/features";
12
- import { CanvasRenderer } from "echarts/renderers";
13
- use([
14
- LineChart,
15
- DataZoomComponent,
16
- LegendComponent,
17
- TitleComponent,
18
- TooltipComponent,
19
- GridComponent,
20
- DatasetComponent,
21
- LabelLayout,
22
- CanvasRenderer
23
- ]);
24
- const DEFAULT_CONFIG = {
25
- theme: void 0,
26
- renderer: "canvas"
27
- };
1
+ <script lang="ts" module>
2
+ import { init, use } from 'echarts/core';
3
+ import { LineChart, BarChart } from 'echarts/charts';
4
+ import {
5
+ DataZoomComponent,
6
+ LegendComponent,
7
+ TitleComponent,
8
+ TooltipComponent,
9
+ GridComponent,
10
+ DatasetComponent,
11
+ MarkLineComponent,
12
+ MarkPointComponent,
13
+ MarkAreaComponent
14
+ } from 'echarts/components';
15
+ import { LabelLayout } from 'echarts/features';
16
+ import { CanvasRenderer } from 'echarts/renderers';
17
+ import type { ECharts, EChartsOption } from './types';
18
+ import 'echarts/theme/dark.js';
19
+
20
+ // Register the required components
21
+ use([
22
+ LineChart,
23
+ BarChart,
24
+ DataZoomComponent,
25
+ LegendComponent,
26
+ TitleComponent,
27
+ TooltipComponent,
28
+ GridComponent,
29
+ DatasetComponent,
30
+ LabelLayout,
31
+ CanvasRenderer,
32
+ MarkLineComponent,
33
+ MarkPointComponent,
34
+ MarkAreaComponent
35
+ ]);
36
+
37
+ export type EChartsTheme = string | object;
38
+ export type EChartsRenderer = 'canvas' | 'svg';
39
+
40
+ export type EChartsConfig = {
41
+ theme?: EChartsTheme;
42
+ renderer?: EChartsRenderer;
43
+ option: EChartsOption;
44
+ };
45
+
46
+ export type DataRange = { start: number; end: number };
47
+ export type DataZoomEventSingle = { batch?: never } & DataRange;
48
+ export type DataZoomEventBatch = { batch: DataRange[]; start?: never; end?: never };
49
+
50
+ export type DataZoomEvent = DataZoomEventBatch | DataZoomEventSingle;
51
+
52
+ const DEFAULT_CONFIG: EChartsConfig = {
53
+ theme: undefined,
54
+ renderer: 'canvas',
55
+ option: {}
56
+ };
28
57
  </script>
29
58
 
30
- <script>import { createEventDispatcher } from "svelte";
31
- export let option;
32
- export let { theme, renderer } = DEFAULT_CONFIG;
33
- let instance;
34
- const dispatch = createEventDispatcher();
35
- const handleDataZoom = (event) => {
36
- let start, end;
37
- if (event.batch) {
38
- const [info] = event.batch;
39
- start = info.start;
40
- end = info.end;
41
- } else {
42
- start = event.start;
43
- end = event.end;
44
- }
45
- dispatch("datazoom", { event, start, end });
46
- };
47
- export function chartAction(element, echartsConfig) {
48
- const { theme: theme2, renderer: renderer2, option: option2 } = {
49
- ...DEFAULT_CONFIG,
50
- ...echartsConfig
51
- };
52
- instance = init(element, theme2, { renderer: renderer2 });
53
- const handleResize = () => {
54
- instance.resize();
55
- };
56
- window.addEventListener("resize", handleResize);
57
- instance.setOption(option2);
58
- instance.on("datazoom", handleDataZoom);
59
- return {
60
- destroy() {
61
- instance.off("datazoom", handleDataZoom);
62
- window.removeEventListener("resize", handleResize);
63
- instance.dispose();
64
- },
65
- update(config) {
66
- instance.setOption({
67
- ...echartsConfig.option,
68
- ...config.option
69
- });
70
- }
71
- };
72
- }
73
- export function showLoading(text) {
74
- instance.showLoading({ text: text || "" });
75
- }
76
- export function hideLoading() {
77
- instance.hideLoading();
78
- }
59
+ <script lang="ts">
60
+ let {
61
+ onLoad,
62
+ config,
63
+ onDataZoom,
64
+ loading = $bindable(false),
65
+ onClear = $bindable(),
66
+ isDark = false
67
+ }: {
68
+ onLoad: (instance: ECharts) => Promise<void>;
69
+ config?: Partial<EChartsConfig>;
70
+ onDataZoom?: (event: DataZoomEventSingle) => void;
71
+ loading?: boolean;
72
+ onClear?: () => void;
73
+ isDark?: boolean;
74
+ } = $props();
75
+
76
+ let instance: ECharts;
77
+
78
+ const { theme, renderer, option } = {
79
+ ...DEFAULT_CONFIG,
80
+ ...config
81
+ };
82
+
83
+ const handleDataZoom = (zoomEvent: unknown) => {
84
+ if (!onDataZoom) {
85
+ return;
86
+ }
87
+
88
+ const event = zoomEvent as DataZoomEvent;
89
+ let start: number, end: number;
90
+
91
+ if (event.batch) {
92
+ const [info] = event.batch;
93
+ start = info.start;
94
+ end = info.end;
95
+ } else {
96
+ start = event.start;
97
+ end = event.end;
98
+ }
99
+
100
+ onDataZoom({ start, end });
101
+ };
102
+
103
+ function chartAction(element: HTMLElement) {
104
+ instance = init(element, isDark ? 'dark' : undefined, { renderer });
105
+
106
+ const handleResize = () => {
107
+ instance.resize();
108
+ };
109
+ window.addEventListener('resize', handleResize);
110
+ instance.on('datazoom', handleDataZoom);
111
+
112
+ if (Object.keys(option).length) {
113
+ instance.setOption(option);
114
+ }
115
+
116
+ onClear = () => instance.clear();
117
+
118
+ onLoad(instance);
119
+
120
+ return {
121
+ destroy() {
122
+ instance.off('datazoom', handleDataZoom);
123
+ window.removeEventListener('resize', handleResize);
124
+ instance.dispose();
125
+ }
126
+ /**
127
+ * @todo
128
+ * Limiting option assignment. Review implementation
129
+ */
130
+ // update(config: EChartsConfig) {
131
+ // instance.setOption({
132
+ // ...echartsConfig.option,
133
+ // ...config.option
134
+ // });
135
+ // }
136
+ };
137
+ }
138
+
139
+ $effect(() => {
140
+ if (instance) {
141
+ instance.setTheme({ backgroundColor: isDark ? '#100c2a' : '#fff' });
142
+ }
143
+ });
79
144
  </script>
80
145
 
81
- <div id="chart" class="echarts" use:chartAction={{ renderer, theme, option }}></div>
146
+ <div style="position: relative; width: 100%; height: 100%;">
147
+ <div id="chart" class="echarts" use:chartAction></div>
148
+ </div>
82
149
 
83
150
  <style>
84
151
  .echarts {
85
152
  width: 100%;
86
- height: 75vh;
153
+ height: 100%;
154
+ min-height: 300px;
155
+ position: relative;
156
+ z-index: 1;
87
157
  }
88
158
  </style>
@@ -1,5 +1,5 @@
1
- import { SvelteComponent } from "svelte";
2
- import type { EChartsOption } from './types';
1
+ import type { ECharts, EChartsOption } from './types';
2
+ import 'echarts/theme/dark.js';
3
3
  export type EChartsTheme = string | object;
4
4
  export type EChartsRenderer = 'canvas' | 'svg';
5
5
  export type EChartsConfig = {
@@ -11,38 +11,24 @@ export type DataRange = {
11
11
  start: number;
12
12
  end: number;
13
13
  };
14
- export type DataZoomEvent = CustomEvent<DataRange>;
15
- declare const __propDef: {
16
- props: {
17
- option: EChartsOption;
18
- theme?: EChartsTheme | undefined;
19
- renderer?: EChartsRenderer | undefined;
20
- chartAction?: (element: HTMLElement, echartsConfig: EChartsConfig) => {
21
- destroy(): void;
22
- update(config: EChartsConfig): void;
23
- };
24
- showLoading?: (text?: string) => void;
25
- hideLoading?: () => void;
26
- };
27
- events: {
28
- datazoom: CustomEvent<any>;
29
- } & {
30
- [evt: string]: CustomEvent<any>;
31
- };
32
- slots: {};
33
- exports?: {} | undefined;
34
- bindings?: string | undefined;
14
+ export type DataZoomEventSingle = {
15
+ batch?: never;
16
+ } & DataRange;
17
+ export type DataZoomEventBatch = {
18
+ batch: DataRange[];
19
+ start?: never;
20
+ end?: never;
35
21
  };
36
- export type SveChartsProps = typeof __propDef.props;
37
- export type SveChartsEvents = typeof __propDef.events;
38
- export type SveChartsSlots = typeof __propDef.slots;
39
- export default class SveCharts extends SvelteComponent<SveChartsProps, SveChartsEvents, SveChartsSlots> {
40
- get chartAction(): (element: HTMLElement, echartsConfig: EChartsConfig) => {
41
- destroy(): void;
42
- update(config: EChartsConfig): void;
43
- };
44
- get showLoading(): (text?: string) => void;
45
- get hideLoading(): () => void;
46
- }
47
- export {};
22
+ export type DataZoomEvent = DataZoomEventBatch | DataZoomEventSingle;
23
+ type $$ComponentProps = {
24
+ onLoad: (instance: ECharts) => Promise<void>;
25
+ config?: Partial<EChartsConfig>;
26
+ onDataZoom?: (event: DataZoomEventSingle) => void;
27
+ loading?: boolean;
28
+ onClear?: () => void;
29
+ isDark?: boolean;
30
+ };
31
+ declare const SVECharts: import("svelte").Component<$$ComponentProps, {}, "loading" | "onClear">;
32
+ type SVECharts = ReturnType<typeof SVECharts>;
33
+ export default SVECharts;
48
34
  //# sourceMappingURL=SVECharts.svelte.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SVECharts.svelte.d.ts","sourceRoot":"","sources":["../src/lib/SVECharts.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,QAAQ,CAEvC;AAaA,OAAO,KAAK,EAAW,aAAa,EAAE,MAAM,SAAS,CAAC;AActD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,CAAC;AAC3C,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE/C,MAAM,MAAM,aAAa,GAAG;IAC3B,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,MAAM,EAAE,aAAa,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACZ,CAAC;AACF,MAAM,MAAM,aAAa,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;AA4EpD,QAAA,MAAM,SAAS;;gBADmJ,aAAa;;;gCA5C/I,WAAW,iBAAiB,aAAa;;2BAoBvD,aAAa;;8BAQD,MAAM;;;;;;;;;;;CAiBmB,CAAC;AACxD,MAAM,MAAM,cAAc,GAAG,OAAO,SAAS,CAAC,KAAK,CAAC;AACpD,MAAM,MAAM,eAAe,GAAG,OAAO,SAAS,CAAC,MAAM,CAAC;AACtD,MAAM,MAAM,cAAc,GAAG,OAAO,SAAS,CAAC,KAAK,CAAC;AAEpD,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,eAAe,CAAC,cAAc,EAAE,eAAe,EAAE,cAAc,CAAC;IACnG,IAAI,WAAW,cAnDa,WAAW,iBAAiB,aAAa;;uBAoBvD,aAAa;MA+BuD;IAClF,IAAI,WAAW,YAxBW,MAAM,UAwBkD;IAClF,IAAI,WAAW,eAAmE;CACrF"}
1
+ {"version":3,"file":"SVECharts.svelte.d.ts","sourceRoot":"","sources":["../src/lib/SVECharts.svelte.ts"],"names":[],"mappings":"AAkBC,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,uBAAuB,CAAC;AAmB/B,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,CAAC;AAC3C,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE/C,MAAM,MAAM,aAAa,GAAG;IAC3B,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,MAAM,EAAE,aAAa,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AACvD,MAAM,MAAM,mBAAmB,GAAG;IAAE,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG,SAAS,CAAC;AAChE,MAAM,MAAM,kBAAkB,GAAG;IAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IAAC,GAAG,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC;AAEpF,MAAM,MAAM,aAAa,GAAG,kBAAkB,GAAG,mBAAmB,CAAC;AAQrE,KAAK,gBAAgB,GAAI;IACxB,MAAM,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IAChC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAClD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AA2FH,QAAA,MAAM,SAAS,yEAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -0,0 +1,131 @@
1
+ import { type LineSeriesOption, type DataZoomComponentOption } from 'echarts';
2
+ import { type ECharts } from './';
3
+ import type { GridOption } from 'echarts/types/dist/shared';
4
+ import type { ZRColor } from 'echarts/types/src/util/types.js';
5
+ type IconType = 'circle' | 'rect' | 'roundRect' | 'triangle' | 'diamond' | 'pin' | 'arrowUp' | 'arrowDown' | 'none';
6
+ type LabelPosition = 'top' | 'left' | 'right' | 'bottom' | 'inside' | 'insideLeft' | 'insideRight' | 'insideTop' | 'insideBottom' | 'insideTopLeft' | 'insideBottomLeft' | 'insideTopRight' | 'insideBottomRight';
7
+ type MarkerPointOption = {
8
+ icon: IconType;
9
+ color: ZRColor;
10
+ position: LabelPosition;
11
+ symbolSize: number;
12
+ };
13
+ type DatasetFormatSimpleObject = Record<string, number[]>;
14
+ type DatasetFormatObject = Record<string, any>[];
15
+ type DatasetFormatArray = number[][];
16
+ /**
17
+ *
18
+ * ConfigBuilder:
19
+ * externalManagerLegend: Hides EChart legends to allow external management
20
+ *
21
+ */
22
+ type ConfigBuilder = {
23
+ externalManagerLegend?: boolean;
24
+ };
25
+ export type MarkerEvent = {
26
+ name?: string;
27
+ xAxis: number[];
28
+ icon?: IconType;
29
+ color?: ZRColor;
30
+ position?: 'aboveBar' | 'belowBar';
31
+ };
32
+ export type MarkArea = {
33
+ name?: string;
34
+ xAxis: [number, number];
35
+ color?: ZRColor;
36
+ };
37
+ export declare class TimeSeriesChartBuilder {
38
+ ECharts: ECharts;
39
+ private builderConfig;
40
+ private option;
41
+ private yDimensions;
42
+ private yDimensionNames?;
43
+ private _tsColumn;
44
+ constructor(instance: ECharts, builderConfig?: ConfigBuilder);
45
+ /**
46
+ * Accepts data as rows: [timestamp, v1, v2, ...]
47
+ * Automatically generates line series for each value column (>=1).
48
+ */
49
+ setDataset(data: DatasetFormatArray | DatasetFormatObject | DatasetFormatSimpleObject, yDimensionsNames?: string[]): this;
50
+ toggleLegend(column: string): this;
51
+ goToZoom(start: number, end: number): this;
52
+ /**
53
+ * data: [1658870400, 823, 95.8, ...]
54
+ * dimensionsNames: ['_ts', 'price', 'otherColumn', ...]
55
+ */
56
+ private setDatasetByArray;
57
+ /**
58
+ * Data is an array of objects.
59
+ * [
60
+ * {_ts: 1658870400, price: 823, otherColumn: 95.8},
61
+ * {...}
62
+ * ]
63
+ */
64
+ private setDataByObject;
65
+ private setDataByObjectSimple;
66
+ addDimension(data: DatasetFormatSimpleObject, dimName: string): this;
67
+ private getColumnsSelected;
68
+ addSeries(dim: string, dimName: string, isSelected: boolean): void;
69
+ /**
70
+ * Tooltip bound to axis with a crosshair pointer.
71
+ */
72
+ setAxisTooltip(): this;
73
+ /**
74
+ * Legend with a custom icon (e.g., 'circle', 'rect').
75
+ */
76
+ setLegendIcon(icon: IconType): this;
77
+ /**
78
+ * Adds both inside and slider dataZoom.
79
+ */
80
+ setDataZoom(zoomOptions: DataZoomComponentOption): this;
81
+ setGrid(gridOption: GridOption): this;
82
+ /**
83
+ * Sets chart title and optional subtitle, centered.
84
+ */
85
+ setTitle(text: string, subtext?: string): this;
86
+ /**
87
+ * Applies a partial style to all existing series (e.g., { smooth: true, symbol: 'none' }).
88
+ */
89
+ setSeriesStyle(style: Partial<LineSeriesOption>): this;
90
+ /**
91
+ * Adds a marker event to the chart.
92
+ */
93
+ addMarkerEvents(data: MarkerEvent[], widthLine?: number): this;
94
+ /**
95
+ * Adds a marker area event to the chart.
96
+ */
97
+ addMarkArea(data: MarkArea[]): this;
98
+ private getIcon;
99
+ addMarkerPoint(id: number, data: {
100
+ dimName: string;
101
+ timestamp: number;
102
+ name?: string;
103
+ }, options?: Partial<MarkerPointOption>): this;
104
+ private isNumberArray;
105
+ /**
106
+ * Creates the series data
107
+ */
108
+ private createSeriesData;
109
+ /**
110
+ * Search for the dimension key and timestamp
111
+ */
112
+ private searchValueByDimensionKeyAndTimestamp;
113
+ /**
114
+ * Return the percentage fields in the dataset
115
+ */
116
+ private detectPercentageFields;
117
+ build(): this;
118
+ getDimensionKeys(): {
119
+ y: string[];
120
+ x: string;
121
+ };
122
+ getLegendStatus(): Record<string, boolean>;
123
+ getTotalRows(): number;
124
+ private isSimpleObject;
125
+ private isRecordArray;
126
+ private isNumberMatrix;
127
+ getRangeValues(): any[];
128
+ toggleMarkers(id: number, dimName: string, shape: string): this | undefined;
129
+ }
130
+ export {};
131
+ //# sourceMappingURL=TimeSeriesChartBuilder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimeSeriesChartBuilder.d.ts","sourceRoot":"","sources":["../src/lib/TimeSeriesChartBuilder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,699 @@
1
+ import {} from 'echarts';
2
+ import {} from './';
3
+ export class TimeSeriesChartBuilder {
4
+ ECharts;
5
+ builderConfig = {
6
+ externalManagerLegend: false
7
+ };
8
+ option = {};
9
+ yDimensions;
10
+ yDimensionNames;
11
+ _tsColumn = '_ts';
12
+ constructor(instance, builderConfig) {
13
+ this.ECharts = instance;
14
+ this.builderConfig = { ...this.builderConfig, ...builderConfig };
15
+ this.option.animation = false;
16
+ this.option.legend = this.builderConfig.externalManagerLegend
17
+ ? {
18
+ show: false,
19
+ selected: {}
20
+ }
21
+ : {
22
+ top: '5%',
23
+ selected: {}
24
+ };
25
+ this.option.grid = {
26
+ top: '10%',
27
+ left: '3%',
28
+ right: '4%',
29
+ bottom: '15%',
30
+ containLabel: true
31
+ };
32
+ this.option.dataZoom = [
33
+ {
34
+ type: 'inside',
35
+ filterMode: 'filter',
36
+ zoomOnMouseWheel: true,
37
+ moveOnMouseMove: true,
38
+ realtime: true,
39
+ start: 45,
40
+ end: 55
41
+ },
42
+ {
43
+ top: '86%',
44
+ left: '8%',
45
+ right: '8%',
46
+ bottom: '5%',
47
+ type: 'slider',
48
+ show: true,
49
+ filterMode: 'filter',
50
+ realtime: false
51
+ }
52
+ ];
53
+ this.option.tooltip = {
54
+ trigger: 'axis',
55
+ axisPointer: { type: 'cross' }
56
+ };
57
+ this.option.xAxis = {
58
+ type: 'time',
59
+ axisLine: { show: true }
60
+ };
61
+ this.option.yAxis = [
62
+ {
63
+ type: 'value',
64
+ scale: true,
65
+ splitLine: { show: false },
66
+ axisLine: { show: true, lineStyle: { type: 'dashed' } }
67
+ },
68
+ {
69
+ type: 'value',
70
+ scale: true,
71
+ splitLine: { show: false },
72
+ axisLine: { show: true, lineStyle: { type: 'dashed' } },
73
+ axisLabel: {
74
+ formatter: (value) => `${value.toFixed(2)}%`
75
+ },
76
+ name: '%'
77
+ }
78
+ ];
79
+ this.option.dataset = {
80
+ dimensions: [],
81
+ source: []
82
+ };
83
+ this.option.series = [];
84
+ }
85
+ /**
86
+ * Accepts data as rows: [timestamp, v1, v2, ...]
87
+ * Automatically generates line series for each value column (>=1).
88
+ */
89
+ setDataset(data, yDimensionsNames) {
90
+ if (!Array.isArray(data)) {
91
+ this.setDataByObjectSimple(data, yDimensionsNames);
92
+ }
93
+ else {
94
+ if (data.length < 2) {
95
+ throw new Error('Minimum data length is 2.');
96
+ }
97
+ if (this.isNumberArray(data)) {
98
+ if (!yDimensionsNames?.length) {
99
+ throw new Error('Requires yDimensionsNames. e.g. ["v1", "v2", "v3"]');
100
+ }
101
+ this.setDatasetByArray(data, yDimensionsNames);
102
+ }
103
+ else if (this.isRecordArray(data)) {
104
+ this.setDataByObject(data, yDimensionsNames);
105
+ }
106
+ else {
107
+ throw new Error('Data must be an array');
108
+ }
109
+ }
110
+ return this.build();
111
+ }
112
+ toggleLegend(column) {
113
+ if (!column || !this.ECharts)
114
+ return this;
115
+ const selected = this.getColumnsSelected();
116
+ selected[column] = !selected[column];
117
+ this.ECharts.dispatchAction({
118
+ type: 'legendToggleSelect',
119
+ name: column
120
+ });
121
+ return this;
122
+ }
123
+ goToZoom(start, end) {
124
+ this.ECharts.dispatchAction({
125
+ type: 'dataZoom',
126
+ dataZoomIndex: 0,
127
+ start,
128
+ end
129
+ });
130
+ return this;
131
+ }
132
+ /**
133
+ * data: [1658870400, 823, 95.8, ...]
134
+ * dimensionsNames: ['_ts', 'price', 'otherColumn', ...]
135
+ */
136
+ setDatasetByArray(data, dimensionsNames, xAxisName) {
137
+ // Build series based on number of columns (minus the time column).
138
+ const columns = Array.isArray(data) && data.length > 0 ? data[0].length : 0;
139
+ const totalCol = Math.max(0, columns);
140
+ if (totalCol !== dimensionsNames?.length) {
141
+ throw new Error(`Dimensions length ${dimensionsNames?.length} does not match total columns ${totalCol}.`);
142
+ }
143
+ /**
144
+ * First column is the time dimension.
145
+ * ------
146
+ * _ts |
147
+ * ------
148
+ */
149
+ const timeDimensionKey = dimensionsNames.shift();
150
+ if (timeDimensionKey === undefined) {
151
+ throw new Error('No time dimension found.');
152
+ }
153
+ this._tsColumn = timeDimensionKey;
154
+ /**
155
+ * TimeDimensionName is the name of the time dimension.
156
+ */
157
+ const timeDimensionName = xAxisName || this._tsColumn;
158
+ /**
159
+ * YDimensions are the column names.
160
+ * --------------------------------------------------------
161
+ * Column 1 | Column 2 | Column 3 | Column 4 | Column 5
162
+ * --------------------------------------------------------
163
+ */
164
+ this.yDimensions = dimensionsNames;
165
+ this.yDimensionNames = dimensionsNames;
166
+ /**
167
+ * Dataset is an array of rows.
168
+ * --------------------------------------------------------------------------------
169
+ * Dimensions | TIME | Column 1 | Column 2 | Column 3 | Column 4 | Column 5 |
170
+ * --------------------------------------------------------------------------------
171
+ * Source | 1658870400 | 32.4 | 32.7 | 32.8 | 32.9 | 32.5 |
172
+ * --------------------------------------------------------------------------------
173
+ */
174
+ if (this.option.dataset && !Array.isArray(this.option.dataset)) {
175
+ this.option.dataset.dimensions = [timeDimensionKey, ...this.yDimensions];
176
+ this.option.dataset.source = data;
177
+ }
178
+ this.createSeriesData(this._tsColumn, timeDimensionName);
179
+ }
180
+ /**
181
+ * Data is an array of objects.
182
+ * [
183
+ * {_ts: 1658870400, price: 823, otherColumn: 95.8},
184
+ * {...}
185
+ * ]
186
+ */
187
+ setDataByObject(data, dimensionsNames) {
188
+ if (data.length < 2) {
189
+ throw new Error('Minimum data length is 2.');
190
+ }
191
+ // All dimensions are obtained based on the keys of the first element in the array.
192
+ // The first dimension, corresponding to time, is separated.
193
+ const dimensionKeys = Object.keys(data[0]);
194
+ const timeDimensionKey = dimensionKeys.shift();
195
+ if (timeDimensionKey === undefined) {
196
+ throw new Error('No time dimension found.');
197
+ }
198
+ this._tsColumn = timeDimensionKey;
199
+ // If custom dimension names are specified, those values will be used.
200
+ // By default, the dimensions will keep the same names as the original keys.
201
+ const timeDimensionName = dimensionsNames ? dimensionsNames.shift() : this._tsColumn;
202
+ /**
203
+ * `yDimensions` represents all data keys except the time dimension.
204
+ * -------------------
205
+ * price | otherColumn
206
+ * -------------------
207
+ */
208
+ this.yDimensions = dimensionKeys;
209
+ // If custom dimension names are specified, those values will be used.
210
+ // By default, the dimensions will keep the same names as the original keys.
211
+ this.yDimensionNames = dimensionsNames || dimensionKeys;
212
+ if (this.option.dataset && !Array.isArray(this.option.dataset)) {
213
+ this.option.dataset.dimensions = [this._tsColumn, ...this.yDimensions];
214
+ this.option.dataset.source = data;
215
+ }
216
+ this.createSeriesData(this._tsColumn, timeDimensionName);
217
+ }
218
+ setDataByObjectSimple(data, dimensionsNames) {
219
+ // All dimensions are obtained based on the keys of the first element in the array.
220
+ // The first dimension, corresponding to time, is separated.
221
+ const dimensionKeys = Object.keys(data);
222
+ const timeDimensionKey = dimensionKeys.shift();
223
+ if (timeDimensionKey === undefined) {
224
+ throw new Error('No time dimension found.');
225
+ }
226
+ this._tsColumn = timeDimensionKey;
227
+ // If custom dimension names are specified, those values will be used.
228
+ // By default, the dimensions will keep the same names as the original keys.
229
+ const timeDimensionName = dimensionsNames ? dimensionsNames.shift() : this._tsColumn;
230
+ /**
231
+ * `yDimensions` represents all data keys except the time dimension.
232
+ * -------------------
233
+ * price | otherColumn
234
+ * -------------------
235
+ */
236
+ this.yDimensions = dimensionKeys;
237
+ // If custom dimension names are specified, those values will be used.
238
+ // By default, the dimensions will keep the same names as the original keys.
239
+ this.yDimensionNames = dimensionsNames || dimensionKeys;
240
+ if (this.option.dataset && !Array.isArray(this.option.dataset)) {
241
+ this.option.dataset.dimensions = [this._tsColumn, ...this.yDimensions];
242
+ this.option.dataset.source = data;
243
+ }
244
+ this.createSeriesData(this._tsColumn, timeDimensionName);
245
+ }
246
+ addDimension(data, dimName) {
247
+ this.yDimensions.push(dimName);
248
+ this.yDimensionNames?.push(dimName);
249
+ if (this.option.dataset && !Array.isArray(this.option.dataset) && this.option.dataset.source) {
250
+ this.option.dataset.dimensions?.push(dimName);
251
+ Object.assign(this.option.dataset.source, data);
252
+ }
253
+ Object.keys(data).forEach((key) => this.addSeries(key, dimName, true));
254
+ this.build();
255
+ return this;
256
+ }
257
+ getColumnsSelected() {
258
+ const selected = this.option.legend.selected;
259
+ return selected;
260
+ }
261
+ addSeries(dim, dimName, isSelected) {
262
+ const percentageFields = this.detectPercentageFields();
263
+ const isPercentage = percentageFields.includes(dim);
264
+ const selected = this.getColumnsSelected();
265
+ Object.assign(selected, { [dimName]: isSelected });
266
+ const series = this.option.series;
267
+ series.push({
268
+ type: 'line',
269
+ animation: false,
270
+ id: dim,
271
+ name: dimName,
272
+ encode: { x: this._tsColumn, y: dim },
273
+ emphasis: {
274
+ focus: 'none',
275
+ disabled: true
276
+ },
277
+ connectNulls: false,
278
+ smooth: false,
279
+ sampling: 'lttb',
280
+ showSymbol: false,
281
+ progressive: 4000,
282
+ progressiveThreshold: 3000,
283
+ progressiveChunkMode: 'mod',
284
+ silent: true,
285
+ clip: true,
286
+ lineStyle: { width: 1 },
287
+ yAxisIndex: isPercentage ? 1 : 0,
288
+ label: {
289
+ show: true,
290
+ backgroundColor: '#000000ff',
291
+ color: '#fff',
292
+ fontSize: 10,
293
+ fontWeight: 'bold',
294
+ borderRadius: 3,
295
+ padding: [5, 5, 5, 5],
296
+ position: 'inside',
297
+ formatter(params) {
298
+ if (!params.seriesId || !params.data)
299
+ return '';
300
+ const value = params.data;
301
+ if (value[params.seriesId]) {
302
+ return `${value[params.seriesId].toFixed(2)}${isPercentage ? '%' : ''}`;
303
+ }
304
+ const idx = params.componentIndex + 1;
305
+ if (value[idx]) {
306
+ return `${value[idx].toFixed(2)}${isPercentage ? '%' : ''}`;
307
+ }
308
+ return '-';
309
+ }
310
+ }
311
+ });
312
+ }
313
+ /**
314
+ * Tooltip bound to axis with a crosshair pointer.
315
+ */
316
+ setAxisTooltip() {
317
+ this.option.tooltip = {
318
+ ...this.option.tooltip
319
+ };
320
+ return this;
321
+ }
322
+ /**
323
+ * Legend with a custom icon (e.g., 'circle', 'rect').
324
+ */
325
+ setLegendIcon(icon) {
326
+ this.option.legend = {
327
+ ...this.option.legend,
328
+ icon
329
+ };
330
+ return this;
331
+ }
332
+ /**
333
+ * Adds both inside and slider dataZoom.
334
+ */
335
+ setDataZoom(zoomOptions) {
336
+ this.option.dataZoom = zoomOptions;
337
+ return this;
338
+ }
339
+ setGrid(gridOption) {
340
+ this.option.grid = {
341
+ ...this.option.grid,
342
+ ...gridOption
343
+ };
344
+ return this;
345
+ }
346
+ /**
347
+ * Sets chart title and optional subtitle, centered.
348
+ */
349
+ setTitle(text, subtext) {
350
+ this.option.title = {
351
+ ...this.option.title,
352
+ text,
353
+ subtext
354
+ };
355
+ return this;
356
+ }
357
+ /**
358
+ * Applies a partial style to all existing series (e.g., { smooth: true, symbol: 'none' }).
359
+ */
360
+ setSeriesStyle(style) {
361
+ if (Array.isArray(this.option.series)) {
362
+ this.option.series = this.option.series.map((s) => ({
363
+ ...s,
364
+ ...style
365
+ }));
366
+ }
367
+ return this;
368
+ }
369
+ /**
370
+ * Adds a marker event to the chart.
371
+ */
372
+ addMarkerEvents(data, widthLine = 1) {
373
+ if (!Array.isArray(this.option.series)) {
374
+ throw new Error('Series must be an array');
375
+ }
376
+ for (const event of data) {
377
+ const position = event.position || 'aboveBar';
378
+ this.option.series.push({
379
+ type: 'line',
380
+ data: [],
381
+ markLine: {
382
+ symbol: this.getIcon(event.icon || 'none'),
383
+ symbolSize: [15, 15],
384
+ symbolOffset: [
385
+ [0, 15],
386
+ [0, 15]
387
+ ],
388
+ label: {
389
+ position: position === 'aboveBar' ? 'insideEnd' : 'insideStart',
390
+ offset: position === 'aboveBar' ? [-35, 0] : [35, 0],
391
+ distance: 0,
392
+ color: 'white',
393
+ formatter: event.name || '',
394
+ fontSize: 12,
395
+ fontFamily: 'Arial',
396
+ fontStyle: 'normal',
397
+ padding: 8,
398
+ backgroundColor: event.name ? (event.color?.toString() ?? 'white') : undefined,
399
+ borderRadius: 4
400
+ },
401
+ emphasis: {
402
+ disabled: true
403
+ },
404
+ lineStyle: {
405
+ color: event.color,
406
+ width: widthLine,
407
+ type: 'dashed'
408
+ },
409
+ data: event.xAxis.map((x) => ({ xAxis: x }))
410
+ }
411
+ });
412
+ }
413
+ return this.build();
414
+ }
415
+ /**
416
+ * Adds a marker area event to the chart.
417
+ */
418
+ addMarkArea(data) {
419
+ if (!Array.isArray(this.option.series)) {
420
+ throw new Error('Series must be an array');
421
+ }
422
+ for (const event of data) {
423
+ this.option.series.push({
424
+ type: 'line',
425
+ data: [],
426
+ markArea: {
427
+ itemStyle: {
428
+ color: event.color || 'rgba(0, 17, 255, 0.1)'
429
+ },
430
+ label: {
431
+ position: 'top',
432
+ formatter: event.name || '',
433
+ fontWeight: 'bold',
434
+ fontSize: 11
435
+ },
436
+ data: [
437
+ [
438
+ {
439
+ xAxis: event.xAxis[0]
440
+ },
441
+ {
442
+ xAxis: event.xAxis[1]
443
+ }
444
+ ]
445
+ ]
446
+ }
447
+ });
448
+ }
449
+ this.addMarkerEvents(data.map((e) => ({ ...e, name: undefined })), 1);
450
+ return this.build();
451
+ }
452
+ getIcon(icon) {
453
+ const arrowUpPath = 'path://M7.414 27.414l16.586-16.586v7.172c0 1.105 0.895 2 2 2s2-0.895 2-2v-12c0-0.809-0.487-1.538-1.235-1.848-0.248-0.103-0.508-0.151-0.765-0.151v-0.001h-12c-1.105 0-2 0.895-2 2s0.895 2 2 2h7.172l-16.586 16.586c-0.391 0.39-0.586 0.902-0.586 1.414s0.195 1.024 0.586 1.414c0.781 0.781 2.047 0.781 2.828 0z';
454
+ const arrowDownPath = 'path://M4.586 7.414l16.586 16.586h-7.171c-1.105 0-2 0.895-2 2s0.895 2 2 2h12c0.809 0 1.538-0.487 1.848-1.235 0.103-0.248 0.151-0.508 0.151-0.765h0.001v-12c0-1.105-0.895-2-2-2s-2 0.895-2 2v7.172l-16.586-16.586c-0.391-0.391-0.902-0.586-1.414-0.586s-1.024 0.195-1.414 0.586c-0.781 0.781-0.781 2.047 0 2.828z';
455
+ const circlePath = 'path://M16 0c-8.837 0-16 7.163-16 16s7.163 16 16 16 16-7.163 16-16-7.163-16-16-16zM16 28c-6.627 0-12-5.373-12-12s5.373-12 12-12c6.627 0 12 5.373 12 12s-5.373 12-12 12z';
456
+ if (icon === 'arrowDown') {
457
+ return arrowDownPath;
458
+ }
459
+ if (icon === 'arrowUp') {
460
+ return arrowUpPath;
461
+ }
462
+ if (icon === 'circle') {
463
+ return circlePath;
464
+ }
465
+ return icon;
466
+ }
467
+ addMarkerPoint(id, data, options) {
468
+ try {
469
+ const opt = {
470
+ icon: 'none',
471
+ position: 'inside',
472
+ symbolSize: 18,
473
+ color: 'black',
474
+ ...options
475
+ };
476
+ if (!Array.isArray(this.option.series)) {
477
+ throw new Error('Series must be an array');
478
+ }
479
+ if (Array.isArray(this.option.dataset)) {
480
+ throw new Error('Series must be an array');
481
+ }
482
+ // Search for the dimension
483
+ const seriesDimension = this.option.series
484
+ .filter((s) => s.encode && s.encode.y)
485
+ .find((s) => {
486
+ return s.encode.y === data.dimName;
487
+ });
488
+ if (!seriesDimension)
489
+ throw new Error(`Dimension ${data.dimName} not found`);
490
+ let value = this.searchValueByDimensionKeyAndTimestamp(data.dimName, data.timestamp);
491
+ /**
492
+ * Creates a data point for the marker
493
+ */
494
+ const dataPoint = () => {
495
+ return {
496
+ name: `markerpoint-${id}`,
497
+ coord: [data.timestamp, value],
498
+ symbol: this.getIcon(opt.icon),
499
+ symbolSize: opt.symbolSize,
500
+ symbolOffset: [0, -1 * (opt.symbolSize * 3)],
501
+ itemStyle: {
502
+ color: opt.color,
503
+ borderColor: opt.color,
504
+ borderWidth: 2
505
+ },
506
+ label: {
507
+ show: true,
508
+ offset: [0, 30],
509
+ formatter: data.name && Number(data.name)
510
+ ? Number(data.name).toFixed(2)
511
+ : (data.name ?? value.toFixed(2)),
512
+ fontSize: 12,
513
+ fontWeight: 'bold',
514
+ color: 'white',
515
+ backgroundColor: opt.color,
516
+ padding: 4,
517
+ borderRadius: 4
518
+ },
519
+ z: 11
520
+ };
521
+ };
522
+ // Create markPoint if it doesn't exist
523
+ if (!seriesDimension.markPoint) {
524
+ seriesDimension.markPoint = {
525
+ data: [dataPoint()]
526
+ };
527
+ }
528
+ else {
529
+ seriesDimension.markPoint.data.push(dataPoint());
530
+ }
531
+ }
532
+ catch (error) {
533
+ console.error(error.message);
534
+ }
535
+ return this;
536
+ }
537
+ isNumberArray(arr) {
538
+ return Array.isArray(arr[0]);
539
+ }
540
+ /**
541
+ * Creates the series data
542
+ */
543
+ createSeriesData(timeDimensionKey, timeDimensionName) {
544
+ if (!this.yDimensions?.length || !this.yDimensionNames?.length) {
545
+ throw new Error('No dimensions found.');
546
+ }
547
+ if (this.yDimensions.length !== this.yDimensionNames.length) {
548
+ throw new Error(`Dimensions length ${this.yDimensionNames.length} does not match total columns ${this.yDimensions.length}.`);
549
+ }
550
+ this.option.xAxis = { type: 'time', name: timeDimensionName };
551
+ this.yDimensions.map((dim, inx) => this.addSeries(dim, this.yDimensionNames[inx], (this.yDimensions.length > 1 && dim === 'price') || this.yDimensions.length === 1));
552
+ }
553
+ /**
554
+ * Search for the dimension key and timestamp
555
+ */
556
+ searchValueByDimensionKeyAndTimestamp(yDimKey, timestamp) {
557
+ const dataset = this.option.dataset;
558
+ if (!dataset.dimensions.find((d) => d === yDimKey)) {
559
+ throw new Error('No source data or dimensions found. Before loading data');
560
+ }
561
+ if (Array.isArray(dataset.source)) {
562
+ if (this.isNumberArray(dataset.source)) {
563
+ const dataFind = dataset.source.find((row) => {
564
+ return row[0] === timestamp;
565
+ });
566
+ if (!dataFind) {
567
+ throw new Error(`No data found in timestamp ${timestamp}`);
568
+ }
569
+ const yDimensionKey = dataset.dimensions.findIndex((d) => d === yDimKey);
570
+ return dataFind[yDimensionKey];
571
+ }
572
+ else if (this.isRecordArray(dataset.source)) {
573
+ const dataFind = dataset.source.find((row) => {
574
+ return row[this._tsColumn] === timestamp;
575
+ });
576
+ if (!dataFind) {
577
+ throw new Error(`No data found in timestamp ${timestamp}`);
578
+ }
579
+ return dataFind[yDimKey];
580
+ }
581
+ }
582
+ else {
583
+ const dataFind = dataset.source[this._tsColumn].indexOf(timestamp);
584
+ if (dataFind === -1) {
585
+ throw new Error(`No data found in timestamp ${timestamp}`);
586
+ }
587
+ return dataset.source[yDimKey][dataFind];
588
+ }
589
+ }
590
+ /**
591
+ * Return the percentage fields in the dataset
592
+ */
593
+ detectPercentageFields() {
594
+ if (!this.yDimensions?.length) {
595
+ throw new Error('No dimensions found.');
596
+ }
597
+ const percentFields = this.yDimensions.filter((key) => !key.startsWith('_') && key.endsWith('%'));
598
+ return percentFields;
599
+ }
600
+ build() {
601
+ const option = this.ECharts.getOption();
602
+ if (option &&
603
+ option.dataZoom &&
604
+ Array.isArray(option.dataZoom) &&
605
+ Array.isArray(this.option.dataZoom)) {
606
+ this.option.dataZoom[0].start = option.dataZoom[0].start;
607
+ this.option.dataZoom[0].end = option.dataZoom[0].end;
608
+ }
609
+ this.ECharts.setOption(this.option, {
610
+ lazyUpdate: true,
611
+ notMerge: false,
612
+ replaceMerge: ['dataset']
613
+ });
614
+ return this;
615
+ }
616
+ getDimensionKeys() {
617
+ return {
618
+ y: this.yDimensionNames,
619
+ x: this._tsColumn
620
+ };
621
+ }
622
+ getLegendStatus() {
623
+ return this.getColumnsSelected();
624
+ }
625
+ getTotalRows() {
626
+ const dataset = this.option.dataset;
627
+ if (Array.isArray(dataset.source)) {
628
+ return dataset.source.length;
629
+ }
630
+ else {
631
+ return dataset.source[this._tsColumn].length;
632
+ }
633
+ }
634
+ isSimpleObject(s) {
635
+ return !Array.isArray(s) && typeof s === 'object' && s !== null;
636
+ }
637
+ isRecordArray(source) {
638
+ return Array.isArray(source) && (source.length === 0 || !Array.isArray(source[0]));
639
+ }
640
+ isNumberMatrix(source) {
641
+ return Array.isArray(source) && (source.length === 0 || Array.isArray(source[0]));
642
+ }
643
+ getRangeValues() {
644
+ const dataset = this.option.dataset;
645
+ const source = dataset.source;
646
+ // ---- Record<string, any>[] ----
647
+ if (this.isRecordArray(source)) {
648
+ if (!source.length)
649
+ return [0, 0];
650
+ const objSource = source;
651
+ const firstRow = objSource[0];
652
+ const lastRow = objSource[objSource.length - 1];
653
+ const first = firstRow[this._tsColumn];
654
+ const last = lastRow[this._tsColumn];
655
+ return [first, last];
656
+ }
657
+ // ---- number[][] ----
658
+ if (this.isNumberMatrix(source)) {
659
+ if (!source.length)
660
+ return [0, 0];
661
+ const matrixSource = source;
662
+ const tsIndex = dataset.dimensions.indexOf(this._tsColumn);
663
+ const idx = tsIndex === -1 ? 0 : tsIndex;
664
+ const firstRow = matrixSource[0];
665
+ const lastRow = matrixSource[matrixSource.length - 1];
666
+ const first = firstRow[idx];
667
+ const last = lastRow[idx];
668
+ return [first, last];
669
+ }
670
+ // ---- Record<string, number[]> ----
671
+ if (this.isSimpleObject(source)) {
672
+ const col = source[this._tsColumn];
673
+ if (!col?.length)
674
+ return [0, 0];
675
+ return [col[0], col[col.length - 1]];
676
+ }
677
+ return [0, 0];
678
+ }
679
+ toggleMarkers(id, dimName, shape) {
680
+ if (!Array.isArray(this.option.series)) {
681
+ throw new Error('Series must be an array');
682
+ }
683
+ if (Array.isArray(this.option.dataset)) {
684
+ throw new Error('Series must be an array');
685
+ }
686
+ // Search for the dimension
687
+ const seriesDimension = this.option.series.find((s) => {
688
+ return s.encode && s.encode.y && s.encode.y === dimName;
689
+ });
690
+ const markerPoints = seriesDimension?.markPoint.data;
691
+ const point = markerPoints.find((mp) => mp.name === `markerpoint-${id}`);
692
+ if (!point) {
693
+ return;
694
+ }
695
+ point.symbol = point.symbol === 'none' ? this.getIcon(shape) : 'none';
696
+ this.build();
697
+ return this;
698
+ }
699
+ }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { default as SVECharts } from './SVECharts.svelte';
2
+ export { TimeSeriesChartBuilder } from './TimeSeriesChartBuilder';
2
3
  export * from './types';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,cAAc,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { default as SVECharts } from './SVECharts.svelte';
2
+ export { TimeSeriesChartBuilder } from './TimeSeriesChartBuilder';
2
3
  export * from './types';
@@ -0,0 +1,5 @@
1
+ export declare function createDataSet<T>(hours: number, type: 'object' | 'array'): {
2
+ data: T[];
3
+ yDimensionsNames: string[];
4
+ };
5
+ //# sourceMappingURL=mockDataSet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mockDataSet.d.ts","sourceRoot":"","sources":["../src/lib/mockDataSet.ts"],"names":[],"mappings":"AAAA,wBAAgB,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO;UA8BxD,CAAC,EAAE;;EAGlB"}
@@ -0,0 +1,32 @@
1
+ export function createDataSet(hours, type) {
2
+ // Create random number between min and max
3
+ const random = (min = 45, max = 50) => Math.random() * max + min;
4
+ const rows = [];
5
+ const YDimensionsName = ['_ts', 'price', 'Column 2', 'Column 3', 'Column 4', 'Column 5 %'];
6
+ // Create a date range
7
+ let startDate = new Date(2025, 9, 28, 14, 0, 0).getTime();
8
+ // Create random data per hour
9
+ for (let i = 0; i < hours; i++) {
10
+ // Add 1 hour
11
+ const ts = new Date(startDate + i * 60000).getTime();
12
+ if (type === 'object') {
13
+ rows.push({
14
+ _ts: ts,
15
+ price: random(),
16
+ col2: random(),
17
+ col3: random(),
18
+ col4: random(),
19
+ 'col5%': random(0, 5)
20
+ });
21
+ continue;
22
+ }
23
+ else {
24
+ rows.push([ts, random(), random(), random(), random(), random()]);
25
+ continue;
26
+ }
27
+ }
28
+ return {
29
+ data: rows,
30
+ yDimensionsNames: YDimensionsName
31
+ };
32
+ }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
+ "private": false,
2
3
  "name": "@qtsurfer/sveltecharts",
3
- "version": "0.2.12",
4
- "author": {
5
- "name": "QTSurfer",
6
- "url": "https://github.com/QTSurfer"
7
- },
4
+ "version": "0.4.0",
5
+ "license": "Apache-2.0",
6
+ "author": "QTSurfer (https://github.com/QTSurfer)",
7
+ "homepage": "https://github.com/QTSurfer/svelte-timeseries/tree/main/packages/sveltecharts",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/QTSurfer/svelte-timeseries.git",
@@ -13,8 +13,7 @@
13
13
  "bugs": {
14
14
  "url": "https://github.com/QTSurfer/svelte-timeseries/issues"
15
15
  },
16
- "license": "Apache-2.0",
17
- "homepage": "https://github.com/QTSurfer/svelte-timeseries",
16
+ "type": "module",
18
17
  "exports": {
19
18
  ".": {
20
19
  "types": "./dist/index.d.ts",
@@ -22,58 +21,59 @@
22
21
  }
23
22
  },
24
23
  "files": [
25
- "package.json",
26
- "README.md",
27
- "LICENSE",
28
24
  "dist",
29
- "!dist/**/*.test.*",
30
- "!dist/**/*.spec.*"
25
+ "!dist/**/*.spec.*",
26
+ "!dist/**/*.test.*"
31
27
  ],
28
+ "types": "./dist/index.d.ts",
32
29
  "peerDependencies": {
33
- "echarts": "^5.6.0",
34
- "svelte": "^4.0.0"
30
+ "echarts": "^6.0.0",
31
+ "svelte": "^5.43.14"
35
32
  },
36
33
  "devDependencies": {
37
- "@eslint/eslintrc": "^3.2.0",
38
- "@eslint/js": "^9.20.0",
39
- "@sveltejs/adapter-auto": "^3.3.1",
40
- "@sveltejs/kit": "^2.17.1",
41
- "@sveltejs/package": "^2.3.10",
42
- "@sveltejs/vite-plugin-svelte": "^3.1.2",
34
+ "@eslint/eslintrc": "^3.3.1",
35
+ "@eslint/js": "^9.39.1",
36
+ "@sveltejs/adapter-auto": "^7.0.0",
37
+ "@sveltejs/kit": "^2.48.6",
38
+ "@sveltejs/package": "^2.5.6",
39
+ "@sveltejs/vite-plugin-svelte": "^6.2.1",
43
40
  "@types/eslint": "^9.6.1",
44
- "@types/node": "^22.13.1",
45
- "@typescript-eslint/eslint-plugin": "^8.23.0",
46
- "@typescript-eslint/parser": "^8.23.0",
47
- "eslint": "^9.20.0",
48
- "eslint-config-prettier": "^10.0.0",
49
- "eslint-plugin-svelte": "^2.46.1",
50
- "globals": "^15.14.0",
51
- "prettier": "^3.5.0",
52
- "prettier-plugin-svelte": "^3.3.3",
53
- "publint": "^0.3.4",
54
- "svelte": "^4.2.19",
55
- "svelte-check": "^3.8.6",
56
- "typescript": "^5.7.3",
57
- "vite": "^5.4.14",
58
- "vitest": "^1.6.1"
41
+ "@types/node": "^24.10.1",
42
+ "@typescript-eslint/eslint-plugin": "^8.47.0",
43
+ "@typescript-eslint/parser": "^8.47.0",
44
+ "echarts": "^6.0.0",
45
+ "eslint": "^9.39.1",
46
+ "eslint-config-prettier": "^10.1.8",
47
+ "eslint-plugin-svelte": "^3.13.0",
48
+ "globals": "^16.5.0",
49
+ "npm-run-all2": "^8.0.4",
50
+ "prettier": "^3.6.2",
51
+ "prettier-plugin-svelte": "^3.4.0",
52
+ "publint": "^0.3.15",
53
+ "svelte": "^5.43.14",
54
+ "svelte-check": "^4.3.4",
55
+ "typescript": "^5.9.3",
56
+ "vite": "^7.2.4",
57
+ "vitest": "^4.0.12"
59
58
  },
60
- "dependencies": {
61
- "echarts": "^5.6.0"
59
+ "publishConfig": {
60
+ "access": "public"
62
61
  },
63
62
  "svelte": "./dist/index.js",
64
- "types": "./dist/index.d.ts",
65
- "type": "module",
66
63
  "scripts": {
67
- "dev": "vite dev",
68
- "build": "vite build && npm run package",
69
- "preview": "vite preview",
70
- "package": "svelte-kit sync && svelte-package && publint",
64
+ "build": "run-s vite:build package",
71
65
  "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
72
66
  "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
73
- "test": "vitest",
67
+ "dev": "vite dev",
68
+ "format": "run-p format:all format:package",
69
+ "format:all": "prettier -l --write .",
70
+ "format:package": "npx prettier-package-json --write ./package.json",
71
+ "knip": "pnpm dlx knip",
74
72
  "lint": "prettier --check . && eslint .",
75
- "format": "prettier --write .",
73
+ "package": "svelte-kit sync && svelte-package && publint",
74
+ "preview": "vite preview",
76
75
  "publishd": "pnpm publish --dry-run --no-git-checks",
77
- "knip": "pnpm dlx knip"
76
+ "test": "vitest",
77
+ "vite:build": "vite build"
78
78
  }
79
79
  }