@qtsurfer/sveltecharts 0.5.0 → 0.6.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.
@@ -0,0 +1,56 @@
1
+ import { type IChartApi } from 'lightweight-charts';
2
+ import type { ChartDataset, ChartDatasetFormatSimpleObject, ChartMarkerPointOptions, TimeSeriesChartAdapter } from './chartAdapter';
3
+ type ConfigBuilder = {
4
+ externalManagerLegend?: boolean;
5
+ };
6
+ export declare class LightweightTimeSeriesChartBuilder implements TimeSeriesChartAdapter {
7
+ LightweightChart: IChartApi;
8
+ private builderConfig;
9
+ private dataset;
10
+ private yDimensions;
11
+ private yDimensionNames;
12
+ private _tsColumn;
13
+ private selected;
14
+ private series;
15
+ private markersPlugins;
16
+ private markers;
17
+ private _dataDirty;
18
+ private _building;
19
+ private _timeRangeForced;
20
+ constructor(instance: IChartApi, builderConfig?: ConfigBuilder);
21
+ setLegendIcon(_icon: string): this;
22
+ setDataset(data: ChartDataset, yDimensionsNames?: string[]): this;
23
+ addDimension(data: ChartDatasetFormatSimpleObject, dimName: string): this;
24
+ addMarkerPoint(id: number, data: {
25
+ dimName: string;
26
+ timestamp: number;
27
+ name?: string;
28
+ }, options?: ChartMarkerPointOptions): this;
29
+ build(): this;
30
+ getLegendStatus(): Record<string, boolean>;
31
+ toggleLegend(column: string): this;
32
+ goToZoom(start: number, end: number): this;
33
+ scrollToTime(timestamp: number): this;
34
+ getTotalRows(): number;
35
+ getRangeValues(): [number, number];
36
+ toggleMarkers(id: number, dimName: string, shape: string): this;
37
+ clearMarkers(): this;
38
+ private normalizeDataset;
39
+ private normalizeSimpleObject;
40
+ private normalizeObjectArray;
41
+ private normalizeMatrix;
42
+ private createSeriesData;
43
+ private addSeries;
44
+ private syncSeriesMarkers;
45
+ private expandTimeRangeForMarkers;
46
+ private clearSeries;
47
+ private toLineData;
48
+ private toChartTime;
49
+ private forceTimeRangeToDataset;
50
+ private isPercentageDimension;
51
+ private syncPriceScales;
52
+ private mapMarkerShape;
53
+ private mapMarkerPosition;
54
+ }
55
+ export {};
56
+ //# sourceMappingURL=LightweightTimeSeriesChartBuilder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LightweightTimeSeriesChartBuilder.d.ts","sourceRoot":"","sources":["../src/lib/LightweightTimeSeriesChartBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,KAAK,SAAS,EASd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EACX,YAAY,EAGZ,8BAA8B,EAC9B,uBAAuB,EACvB,sBAAsB,EACtB,MAAM,gBAAgB,CAAC;AAExB,KAAK,aAAa,GAAG;IACpB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAeF,qBAAa,iCAAkC,YAAW,sBAAsB;IACxE,gBAAgB,EAAE,SAAS,CAAC;IACnC,OAAO,CAAC,aAAa,CAEnB;IACF,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,WAAW,CAAgB;IACnC,OAAO,CAAC,eAAe,CAAgB;IACvC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,MAAM,CAA+C;IAC7D,OAAO,CAAC,cAAc,CAAoD;IAC1E,OAAO,CAAC,OAAO,CAAoC;IACnD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,gBAAgB,CAAS;gBAErB,QAAQ,EAAE,SAAS,EAAE,aAAa,CAAC,EAAE,aAAa;IAK9D,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIlC,UAAU,CAAC,IAAI,EAAE,YAAY,EAAE,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI;IAejE,YAAY,CAAC,IAAI,EAAE,8BAA8B,EAAE,OAAO,EAAE,MAAM;IAclE,cAAc,CACb,EAAE,EAAE,MAAM,EACV,IAAI,EAAE;QACL,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;KACd,EACD,OAAO,CAAC,EAAE,uBAAuB,GAC/B,IAAI;IAmBP,KAAK;IAqCL,eAAe;IAIf,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAWlC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAuB1C,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAqBrC,YAAY;IAIZ,cAAc,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC;IAQlC,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAmBxD,YAAY,IAAI,IAAI;IAQpB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,qBAAqB;IAqB7B,OAAO,CAAC,oBAAoB;IA2B5B,OAAO,CAAC,eAAe;IAsCvB,OAAO,CAAC,gBAAgB;IAmBxB,OAAO,CAAC,SAAS;IAgBjB,OAAO,CAAC,iBAAiB;IAoCzB,OAAO,CAAC,yBAAyB;IA4BjC,OAAO,CAAC,WAAW;IAgBnB,OAAO,CAAC,UAAU;IAsBlB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,uBAAuB;IA4B/B,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,eAAe;IAcvB,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,iBAAiB;CAWzB"}
@@ -0,0 +1,428 @@
1
+ import { LineSeries, createSeriesMarkers } from 'lightweight-charts';
2
+ const SERIES_COLORS = ['#2563eb', '#16a34a', '#dc2626', '#7c3aed', '#d97706', '#0891b2'];
3
+ export class LightweightTimeSeriesChartBuilder {
4
+ LightweightChart;
5
+ builderConfig = {
6
+ externalManagerLegend: false
7
+ };
8
+ dataset = {};
9
+ yDimensions = [];
10
+ yDimensionNames = [];
11
+ _tsColumn = '_ts';
12
+ selected = {};
13
+ series = new Map();
14
+ markersPlugins = new Map();
15
+ markers = new Map();
16
+ _dataDirty = false;
17
+ _building = false;
18
+ _timeRangeForced = false;
19
+ constructor(instance, builderConfig) {
20
+ this.LightweightChart = instance;
21
+ this.builderConfig = { ...this.builderConfig, ...builderConfig };
22
+ }
23
+ setLegendIcon(_icon) {
24
+ return this;
25
+ }
26
+ setDataset(data, yDimensionsNames) {
27
+ const normalized = this.normalizeDataset(data, yDimensionsNames);
28
+ this.clearSeries();
29
+ this.dataset = normalized.dataset;
30
+ this.yDimensions = normalized.yDimensions;
31
+ this.yDimensionNames = normalized.yDimensionNames;
32
+ this._tsColumn = normalized.tsColumn;
33
+ this.selected = {};
34
+ this._dataDirty = true;
35
+ this.createSeriesData();
36
+ return this.build();
37
+ }
38
+ addDimension(data, dimName) {
39
+ if (!this.dataset[this._tsColumn]?.length) {
40
+ throw new Error('No source data or dimensions found. Before loading data');
41
+ }
42
+ this.dataset[dimName] = data[dimName];
43
+ this.yDimensions.push(dimName);
44
+ this.yDimensionNames.push(dimName);
45
+ this._dataDirty = true;
46
+ this.addSeries(dimName, dimName, true);
47
+ return this.build();
48
+ }
49
+ addMarkerPoint(id, data, options) {
50
+ const markerState = {
51
+ id,
52
+ time: this.toChartTime(data.timestamp),
53
+ color: options?.color ?? '#000000',
54
+ shape: this.mapMarkerShape(options?.icon),
55
+ position: this.mapMarkerPosition(options?.position),
56
+ text: data.name,
57
+ visible: true,
58
+ size: options?.symbolSize ?? 4
59
+ };
60
+ const markers = this.markers.get(data.dimName) ?? [];
61
+ markers.push(markerState);
62
+ this.markers.set(data.dimName, markers);
63
+ this.syncSeriesMarkers(data.dimName);
64
+ return this;
65
+ }
66
+ build() {
67
+ if (this._building) {
68
+ return this;
69
+ }
70
+ this._building = true;
71
+ const visibleLogicalRange = this.LightweightChart.timeScale().getVisibleLogicalRange();
72
+ const rebuildData = this._dataDirty;
73
+ this._dataDirty = false;
74
+ if (rebuildData) {
75
+ for (const dim of this.yDimensions) {
76
+ const series = this.series.get(dim);
77
+ if (!series) {
78
+ continue;
79
+ }
80
+ const lineData = this.toLineData(dim);
81
+ series.setData(lineData);
82
+ series.applyOptions({ visible: this.selected[dim] ?? false });
83
+ }
84
+ if (visibleLogicalRange) {
85
+ this.LightweightChart.timeScale().setVisibleLogicalRange(visibleLogicalRange);
86
+ }
87
+ else {
88
+ this.LightweightChart.timeScale().fitContent();
89
+ this.forceTimeRangeToDataset();
90
+ }
91
+ }
92
+ for (const dim of this.yDimensions) {
93
+ this.syncSeriesMarkers(dim);
94
+ }
95
+ this._building = false;
96
+ return this;
97
+ }
98
+ getLegendStatus() {
99
+ return this.selected;
100
+ }
101
+ toggleLegend(column) {
102
+ if (!column || !this.series.has(column)) {
103
+ return this;
104
+ }
105
+ const nextVisible = !(this.selected[column] ?? false);
106
+ this.selected[column] = nextVisible;
107
+ this.series.get(column)?.applyOptions({ visible: nextVisible });
108
+ return this;
109
+ }
110
+ goToZoom(start, end) {
111
+ const [min, max] = this.getRangeValues();
112
+ if (!min && !max) {
113
+ return this;
114
+ }
115
+ if (start <= 0 && end >= 100) {
116
+ this.LightweightChart.timeScale().fitContent();
117
+ return this;
118
+ }
119
+ const width = max - min;
120
+ const from = min + width * (start / 100);
121
+ const to = min + width * (end / 100);
122
+ this.LightweightChart.timeScale().setVisibleRange({
123
+ from: this.toChartTime(from),
124
+ to: this.toChartTime(to)
125
+ });
126
+ return this;
127
+ }
128
+ scrollToTime(timestamp) {
129
+ const timeSec = this.toChartTime(timestamp);
130
+ const timeScale = this.LightweightChart.timeScale();
131
+ const visibleRange = timeScale.getVisibleRange();
132
+ if (!visibleRange) {
133
+ timeScale.setVisibleRange({ from: timeSec, to: (timeSec + 60) });
134
+ return this;
135
+ }
136
+ const width = Number(visibleRange.to) - Number(visibleRange.from);
137
+ const halfWidth = Math.max(width / 2, 30);
138
+ timeScale.setVisibleRange({
139
+ from: (timeSec - halfWidth),
140
+ to: (timeSec + halfWidth)
141
+ });
142
+ return this;
143
+ }
144
+ getTotalRows() {
145
+ return this.dataset[this._tsColumn]?.length ?? 0;
146
+ }
147
+ getRangeValues() {
148
+ const timestamps = this.dataset[this._tsColumn] ?? [];
149
+ if (!timestamps.length) {
150
+ return [0, 0];
151
+ }
152
+ return [timestamps[0], timestamps[timestamps.length - 1]];
153
+ }
154
+ toggleMarkers(id, dimName, shape) {
155
+ const markers = this.markers.get(dimName);
156
+ if (!markers) {
157
+ return this;
158
+ }
159
+ const marker = markers.find((item) => item.id === id);
160
+ if (!marker) {
161
+ return this;
162
+ }
163
+ marker.visible = !marker.visible;
164
+ if (marker.visible) {
165
+ marker.shape = this.mapMarkerShape(shape);
166
+ }
167
+ this.syncSeriesMarkers(dimName);
168
+ return this;
169
+ }
170
+ clearMarkers() {
171
+ this.markers.clear();
172
+ for (const plugin of this.markersPlugins.values()) {
173
+ plugin.setMarkers([]);
174
+ }
175
+ return this;
176
+ }
177
+ normalizeDataset(data, yDimensionsNames) {
178
+ if (Array.isArray(data)) {
179
+ if (!data.length || Array.isArray(data[0])) {
180
+ return this.normalizeMatrix(data, yDimensionsNames);
181
+ }
182
+ return this.normalizeObjectArray(data, yDimensionsNames);
183
+ }
184
+ return this.normalizeSimpleObject(data, yDimensionsNames);
185
+ }
186
+ normalizeSimpleObject(data, yDimensionsNames) {
187
+ const keys = Object.keys(data);
188
+ const tsColumn = keys.shift();
189
+ if (!tsColumn) {
190
+ throw new Error('No time dimension found.');
191
+ }
192
+ if (yDimensionsNames?.length && yDimensionsNames.length !== keys.length + 1) {
193
+ throw new Error(`Dimensions length ${yDimensionsNames.length} does not match total columns ${keys.length + 1}.`);
194
+ }
195
+ return {
196
+ tsColumn,
197
+ dataset: data,
198
+ yDimensions: keys,
199
+ yDimensionNames: yDimensionsNames?.length ? yDimensionsNames.slice(1) : keys
200
+ };
201
+ }
202
+ normalizeObjectArray(data, yDimensionsNames) {
203
+ if (data.length < 2) {
204
+ throw new Error('Minimum data length is 2.');
205
+ }
206
+ const keys = Object.keys(data[0]);
207
+ const tsColumn = keys.shift();
208
+ if (!tsColumn) {
209
+ throw new Error('No time dimension found.');
210
+ }
211
+ const dataset = {
212
+ [tsColumn]: data.map((row) => Number(row[tsColumn]))
213
+ };
214
+ for (const key of keys) {
215
+ dataset[key] = data.map((row) => Number(row[key]));
216
+ }
217
+ return {
218
+ tsColumn,
219
+ dataset,
220
+ yDimensions: keys,
221
+ yDimensionNames: yDimensionsNames?.length ? yDimensionsNames.slice(1) : keys
222
+ };
223
+ }
224
+ normalizeMatrix(data, yDimensionsNames) {
225
+ if (data.length < 2) {
226
+ throw new Error('Minimum data length is 2.');
227
+ }
228
+ if (!yDimensionsNames?.length) {
229
+ throw new Error('Requires yDimensionsNames. e.g. ["v1", "v2", "v3"]');
230
+ }
231
+ const keys = [...yDimensionsNames];
232
+ const tsColumn = keys.shift();
233
+ if (!tsColumn) {
234
+ throw new Error('No time dimension found.');
235
+ }
236
+ const totalCol = data[0]?.length ?? 0;
237
+ if (totalCol !== yDimensionsNames.length) {
238
+ throw new Error(`Dimensions length ${yDimensionsNames.length} does not match total columns ${totalCol}.`);
239
+ }
240
+ const dataset = {
241
+ [tsColumn]: data.map((row) => Number(row[0]))
242
+ };
243
+ keys.forEach((key, index) => {
244
+ dataset[key] = data.map((row) => Number(row[index + 1]));
245
+ });
246
+ return {
247
+ tsColumn,
248
+ dataset,
249
+ yDimensions: keys,
250
+ yDimensionNames: keys
251
+ };
252
+ }
253
+ createSeriesData() {
254
+ if (!this.yDimensions.length || !this.yDimensionNames.length) {
255
+ throw new Error('No dimensions found.');
256
+ }
257
+ if (this.yDimensions.length !== this.yDimensionNames.length) {
258
+ throw new Error(`Dimensions length ${this.yDimensionNames.length} does not match total columns ${this.yDimensions.length}.`);
259
+ }
260
+ this.syncPriceScales();
261
+ this.yDimensions.forEach((dim, index) => {
262
+ const isSelected = (this.yDimensions.length > 1 && dim === 'price') || this.yDimensions.length === 1;
263
+ this.addSeries(dim, this.yDimensionNames[index], isSelected, index);
264
+ });
265
+ }
266
+ addSeries(dim, dimName, isSelected, index) {
267
+ const series = this.LightweightChart.addSeries(LineSeries, {
268
+ color: SERIES_COLORS[(index ?? this.series.size) % SERIES_COLORS.length],
269
+ lineWidth: 1,
270
+ title: dimName,
271
+ priceScaleId: this.isPercentageDimension(dim) ? 'left' : 'right',
272
+ visible: isSelected,
273
+ crosshairMarkerVisible: false,
274
+ lastValueVisible: true
275
+ });
276
+ this.series.set(dim, series);
277
+ this.selected[dim] = isSelected;
278
+ this.markersPlugins.set(dim, createSeriesMarkers(series, []));
279
+ }
280
+ syncSeriesMarkers(dimName) {
281
+ const plugin = this.markersPlugins.get(dimName);
282
+ if (!plugin) {
283
+ return;
284
+ }
285
+ // Lightweight Charts requires unique timestamps per series — duplicate UTCTimestamps
286
+ // within the same second are collapsed, keeping only the first marker at that time.
287
+ const seenTimes = new Set();
288
+ const markers = (this.markers.get(dimName) ?? [])
289
+ .filter((marker) => marker.visible)
290
+ .sort((a, b) => a.time - b.time)
291
+ .filter((marker) => {
292
+ const t = marker.time;
293
+ if (seenTimes.has(t))
294
+ return false;
295
+ seenTimes.add(t);
296
+ return true;
297
+ })
298
+ .map((marker) => ({
299
+ time: marker.time,
300
+ color: marker.color || '#888888',
301
+ shape: marker.shape,
302
+ position: marker.position,
303
+ size: marker.size,
304
+ ...(marker.text ? { text: marker.text } : {})
305
+ }));
306
+ plugin.setMarkers(markers);
307
+ if (markers.length > 0) {
308
+ this.expandTimeRangeForMarkers();
309
+ }
310
+ }
311
+ expandTimeRangeForMarkers() {
312
+ const timeScale = this.LightweightChart.timeScale();
313
+ const visibleRange = timeScale.getVisibleRange();
314
+ if (!visibleRange) {
315
+ return;
316
+ }
317
+ let minTime = visibleRange.from;
318
+ let maxTime = visibleRange.to;
319
+ for (const [, markerList] of this.markers) {
320
+ for (const m of markerList) {
321
+ const t = m.time;
322
+ if (t < minTime)
323
+ minTime = t;
324
+ if (t > maxTime)
325
+ maxTime = t;
326
+ }
327
+ }
328
+ if (minTime < visibleRange.from || maxTime > visibleRange.to) {
329
+ const padding = (maxTime - minTime) * 0.1;
330
+ timeScale.setVisibleRange({
331
+ from: (minTime - padding),
332
+ to: (maxTime + padding)
333
+ });
334
+ }
335
+ }
336
+ clearSeries() {
337
+ for (const plugin of this.markersPlugins.values()) {
338
+ plugin.detach();
339
+ }
340
+ for (const series of this.series.values()) {
341
+ this.LightweightChart.removeSeries(series);
342
+ }
343
+ this.series.clear();
344
+ this.markersPlugins.clear();
345
+ this.markers.clear();
346
+ this._timeRangeForced = false;
347
+ this._building = false;
348
+ }
349
+ toLineData(dim) {
350
+ const timestamps = this.dataset[this._tsColumn] ?? [];
351
+ const values = this.dataset[dim] ?? [];
352
+ if (timestamps.length === 0) {
353
+ return [];
354
+ }
355
+ const seen = new Set();
356
+ const result = [];
357
+ for (let i = 0; i < timestamps.length; i++) {
358
+ const t = this.toChartTime(timestamps[i]);
359
+ const tNum = t;
360
+ if (seen.has(tNum))
361
+ continue;
362
+ seen.add(tNum);
363
+ result.push({ time: t, value: values[i] });
364
+ }
365
+ return result;
366
+ }
367
+ toChartTime(timestamp) {
368
+ return Math.floor(timestamp / 1000);
369
+ }
370
+ forceTimeRangeToDataset() {
371
+ if (this._timeRangeForced) {
372
+ return;
373
+ }
374
+ this._timeRangeForced = true;
375
+ const timestamps = this.dataset[this._tsColumn] ?? [];
376
+ if (timestamps.length === 0) {
377
+ return;
378
+ }
379
+ let minMs = timestamps[0];
380
+ let maxMs = timestamps[0];
381
+ for (let i = 1; i < timestamps.length; i++) {
382
+ const t = timestamps[i];
383
+ if (t < minMs)
384
+ minMs = t;
385
+ if (t > maxMs)
386
+ maxMs = t;
387
+ }
388
+ const minSec = this.toChartTime(minMs);
389
+ const maxSec = this.toChartTime(maxMs);
390
+ this.LightweightChart.timeScale().setVisibleRange({
391
+ from: minSec,
392
+ to: maxSec
393
+ });
394
+ }
395
+ isPercentageDimension(dim) {
396
+ return !dim.startsWith('_') && dim.endsWith('%');
397
+ }
398
+ syncPriceScales() {
399
+ const hasPercentageSeries = this.yDimensions.some((dim) => this.isPercentageDimension(dim));
400
+ const hasValueSeries = this.yDimensions.some((dim) => !this.isPercentageDimension(dim));
401
+ this.LightweightChart.applyOptions({
402
+ leftPriceScale: {
403
+ visible: hasPercentageSeries
404
+ },
405
+ rightPriceScale: {
406
+ visible: hasValueSeries
407
+ }
408
+ });
409
+ }
410
+ mapMarkerShape(shape) {
411
+ if (shape === 'circle' || shape === 'arrowUp' || shape === 'arrowDown') {
412
+ return shape;
413
+ }
414
+ if (shape === 'none') {
415
+ return 'circle';
416
+ }
417
+ return 'square';
418
+ }
419
+ mapMarkerPosition(position) {
420
+ if (position === 'inside') {
421
+ return 'inBar';
422
+ }
423
+ if (position === 'aboveBar' || position === 'belowBar' || position === 'inBar') {
424
+ return position;
425
+ }
426
+ return 'aboveBar';
427
+ }
428
+ }
@@ -0,0 +1,249 @@
1
+ <script lang="ts">
2
+ import {
3
+ createChart,
4
+ type ChartOptions,
5
+ type DeepPartial,
6
+ type IChartApi,
7
+ type ISeriesApi,
8
+ type SeriesType,
9
+ type MouseEventParams,
10
+ type Time
11
+ } from 'lightweight-charts';
12
+
13
+ type LightweightChartsConfig = {
14
+ options?: DeepPartial<ChartOptions>;
15
+ };
16
+
17
+ type TooltipRow = { label: string; value: string };
18
+
19
+ let {
20
+ onLoad,
21
+ config,
22
+ loading = $bindable(false),
23
+ onClear = $bindable(),
24
+ isDark = false
25
+ }: {
26
+ onLoad: (instance: IChartApi) => Promise<void>;
27
+ config?: LightweightChartsConfig;
28
+ loading?: boolean;
29
+ onClear?: () => void;
30
+ isDark?: boolean;
31
+ } = $props();
32
+
33
+ let chart: IChartApi | undefined;
34
+
35
+ // Tooltip state
36
+ let tooltipVisible = $state(false);
37
+ let tooltipX = $state(0);
38
+ let tooltipY = $state(0);
39
+ let tooltipTime = $state('');
40
+ let tooltipRows = $state<TooltipRow[]>([]);
41
+
42
+ const baseOptions = $derived<DeepPartial<ChartOptions>>({
43
+ layout: {
44
+ background: {
45
+ color: isDark ? '#100c2a' : '#ffffff'
46
+ },
47
+ textColor: isDark ? '#f8fafc' : '#0f172a'
48
+ },
49
+ grid: {
50
+ vertLines: {
51
+ color: isDark ? 'rgba(148, 163, 184, 0.12)' : 'rgba(148, 163, 184, 0.2)'
52
+ },
53
+ horzLines: {
54
+ color: isDark ? 'rgba(148, 163, 184, 0.12)' : 'rgba(148, 163, 184, 0.2)'
55
+ }
56
+ },
57
+ rightPriceScale: {
58
+ borderColor: isDark ? '#334155' : '#cbd5e1'
59
+ },
60
+ leftPriceScale: {
61
+ borderColor: isDark ? '#334155' : '#cbd5e1'
62
+ },
63
+ timeScale: {
64
+ borderColor: isDark ? '#334155' : '#cbd5e1',
65
+ timeVisible: true
66
+ },
67
+ crosshair: {
68
+ vertLine: {
69
+ color: isDark ? '#64748b' : '#94a3b8'
70
+ },
71
+ horzLine: {
72
+ color: isDark ? '#64748b' : '#94a3b8'
73
+ }
74
+ }
75
+ });
76
+
77
+ const chartOptions = $derived({
78
+ ...baseOptions,
79
+ ...(config?.options ?? {})
80
+ });
81
+
82
+ function formatTime(time: Time): string {
83
+ const ts = (time as number) * 1000;
84
+ const d = new Date(ts);
85
+ return d.toISOString().replace('T', ' ').slice(0, 19);
86
+ }
87
+
88
+ function handleCrosshairMove(param: MouseEventParams<Time>, container: HTMLElement) {
89
+ if (!param.point || !param.time || param.seriesData.size === 0) {
90
+ tooltipVisible = false;
91
+ return;
92
+ }
93
+
94
+ const rows: TooltipRow[] = [];
95
+ for (const [series, data] of param.seriesData as Map<
96
+ ISeriesApi<SeriesType, Time>,
97
+ { value?: number }
98
+ >) {
99
+ if (!series.options().visible) continue;
100
+ const value = data?.value;
101
+ if (value === undefined || value === null) continue;
102
+ const label = (series.options() as { title?: string }).title ?? '';
103
+ rows.push({ label, value: value.toFixed(4) });
104
+ }
105
+
106
+ if (rows.length === 0) {
107
+ tooltipVisible = false;
108
+ return;
109
+ }
110
+
111
+ tooltipRows = rows;
112
+ tooltipTime = formatTime(param.time);
113
+
114
+ // Position tooltip near cursor, avoiding overflow
115
+ const containerRect = container.getBoundingClientRect();
116
+ const TOOLTIP_WIDTH = 160;
117
+ const TOOLTIP_OFFSET = 12;
118
+
119
+ let x = param.point.x + TOOLTIP_OFFSET;
120
+ if (x + TOOLTIP_WIDTH > containerRect.width) {
121
+ x = param.point.x - TOOLTIP_WIDTH - TOOLTIP_OFFSET;
122
+ }
123
+
124
+ tooltipX = x;
125
+ tooltipY = Math.max(0, param.point.y - 20);
126
+ tooltipVisible = true;
127
+ }
128
+
129
+ function chartAction(element: HTMLElement) {
130
+ chart = createChart(element, chartOptions);
131
+
132
+ const handleResize = () => {
133
+ chart?.applyOptions({
134
+ width: element.clientWidth,
135
+ height: element.clientHeight
136
+ });
137
+ };
138
+
139
+ const resizeObserver =
140
+ typeof ResizeObserver === 'undefined' ? undefined : new ResizeObserver(handleResize);
141
+
142
+ if (resizeObserver) {
143
+ resizeObserver.observe(element);
144
+ } else {
145
+ window.addEventListener('resize', handleResize);
146
+ }
147
+
148
+ const onCrosshairMove = (param: MouseEventParams<Time>) => handleCrosshairMove(param, element);
149
+ chart.subscribeCrosshairMove(onCrosshairMove);
150
+
151
+ handleResize();
152
+ onClear = () => undefined;
153
+ onLoad(chart);
154
+
155
+ return {
156
+ destroy() {
157
+ chart?.unsubscribeCrosshairMove(onCrosshairMove);
158
+ if (resizeObserver) {
159
+ resizeObserver.disconnect();
160
+ } else {
161
+ window.removeEventListener('resize', handleResize);
162
+ }
163
+ chart?.remove();
164
+ }
165
+ };
166
+ }
167
+
168
+ $effect(() => {
169
+ chart?.applyOptions(chartOptions);
170
+ });
171
+ </script>
172
+
173
+ <div style="position: relative; width: 100%; height: 100%;">
174
+ <div class="lightweight-charts" use:chartAction></div>
175
+
176
+ {#if tooltipVisible}
177
+ <div
178
+ class="lw-tooltip"
179
+ class:lw-tooltip--dark={isDark}
180
+ style="left: {tooltipX}px; top: {tooltipY}px;"
181
+ >
182
+ <div class="lw-tooltip__time">{tooltipTime}</div>
183
+ {#each tooltipRows as row (row.label)}
184
+ <div class="lw-tooltip__row">
185
+ {#if row.label}<span class="lw-tooltip__label">{row.label}</span>{/if}
186
+ <span class="lw-tooltip__value">{row.value}</span>
187
+ </div>
188
+ {/each}
189
+ </div>
190
+ {/if}
191
+ </div>
192
+
193
+ <style>
194
+ .lightweight-charts {
195
+ width: 100%;
196
+ height: 100%;
197
+ min-height: 100px;
198
+ position: relative;
199
+ z-index: 1;
200
+ }
201
+
202
+ .lw-tooltip {
203
+ position: absolute;
204
+ z-index: 10;
205
+ pointer-events: none;
206
+ background: rgba(255, 255, 255, 0.95);
207
+ border: 1px solid #cbd5e1;
208
+ border-radius: 6px;
209
+ padding: 6px 10px;
210
+ font-size: 12px;
211
+ color: #0f172a;
212
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
213
+ min-width: 120px;
214
+ max-width: 200px;
215
+ }
216
+
217
+ .lw-tooltip--dark {
218
+ background: rgba(20, 16, 50, 0.95);
219
+ border-color: #334155;
220
+ color: #f8fafc;
221
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
222
+ }
223
+
224
+ .lw-tooltip__time {
225
+ font-size: 11px;
226
+ opacity: 0.6;
227
+ margin-bottom: 4px;
228
+ white-space: nowrap;
229
+ }
230
+
231
+ .lw-tooltip__row {
232
+ display: flex;
233
+ justify-content: space-between;
234
+ gap: 8px;
235
+ line-height: 1.6;
236
+ }
237
+
238
+ .lw-tooltip__label {
239
+ opacity: 0.75;
240
+ overflow: hidden;
241
+ text-overflow: ellipsis;
242
+ white-space: nowrap;
243
+ }
244
+
245
+ .lw-tooltip__value {
246
+ font-weight: 600;
247
+ white-space: nowrap;
248
+ }
249
+ </style>
@@ -0,0 +1,15 @@
1
+ import { type ChartOptions, type DeepPartial, type IChartApi } from 'lightweight-charts';
2
+ type LightweightChartsConfig = {
3
+ options?: DeepPartial<ChartOptions>;
4
+ };
5
+ type $$ComponentProps = {
6
+ onLoad: (instance: IChartApi) => Promise<void>;
7
+ config?: LightweightChartsConfig;
8
+ loading?: boolean;
9
+ onClear?: () => void;
10
+ isDark?: boolean;
11
+ };
12
+ declare const SVELightweightCharts: import("svelte").Component<$$ComponentProps, {}, "loading" | "onClear">;
13
+ type SVELightweightCharts = ReturnType<typeof SVELightweightCharts>;
14
+ export default SVELightweightCharts;
15
+ //# sourceMappingURL=SVELightweightCharts.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SVELightweightCharts.svelte.d.ts","sourceRoot":"","sources":["../src/lib/SVELightweightCharts.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,SAAS,EAKd,MAAM,oBAAoB,CAAC;AAG5B,KAAK,uBAAuB,GAAG;IAC9B,OAAO,CAAC,EAAE,WAAW,CAAC,YAAY,CAAC,CAAC;CACpC,CAAC;AAGF,KAAK,gBAAgB,GAAI;IACxB,MAAM,EAAE,CAAC,QAAQ,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AA8KH,QAAA,MAAM,oBAAoB,yEAAwC,CAAC;AACnE,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACpE,eAAe,oBAAoB,CAAC"}
@@ -1,15 +1,9 @@
1
1
  import { type LineSeriesOption, type DataZoomComponentOption } from 'echarts';
2
2
  import { type ECharts } from './';
3
+ import type { ChartMarkerPointOptions } from './chartAdapter';
3
4
  import type { GridOption } from 'echarts/types/dist/shared';
4
5
  import type { ZRColor } from 'echarts/types/src/util/types.js';
5
6
  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
7
  type DatasetFormatSimpleObject = Record<string, number[]>;
14
8
  type DatasetFormatObject = Record<string, any>[];
15
9
  type DatasetFormatArray = number[][];
@@ -49,6 +43,8 @@ export declare class TimeSeriesChartBuilder {
49
43
  setDataset(data: DatasetFormatArray | DatasetFormatObject | DatasetFormatSimpleObject, yDimensionsNames?: string[]): this;
50
44
  toggleLegend(column: string): this;
51
45
  goToZoom(start: number, end: number): this;
46
+ scrollToTime(timestamp: number): this;
47
+ private findClosestDataIndex;
52
48
  /**
53
49
  * data: [1658870400, 823, 95.8, ...]
54
50
  * dimensionsNames: ['_ts', 'price', 'otherColumn', ...]
@@ -100,7 +96,7 @@ export declare class TimeSeriesChartBuilder {
100
96
  dimName: string;
101
97
  timestamp: number;
102
98
  name?: string;
103
- }, options?: Partial<MarkerPointOption>): this;
99
+ }, options?: ChartMarkerPointOptions): this;
104
100
  private isNumberArray;
105
101
  /**
106
102
  * Creates the series data
@@ -124,8 +120,9 @@ export declare class TimeSeriesChartBuilder {
124
120
  private isSimpleObject;
125
121
  private isRecordArray;
126
122
  private isNumberMatrix;
127
- getRangeValues(): any[];
128
- toggleMarkers(id: number, dimName: string, shape: string): this | undefined;
123
+ getRangeValues(): [number, number];
124
+ toggleMarkers(id: number, dimName: string, shape: string): this;
125
+ clearMarkers(): this;
129
126
  }
130
127
  export {};
131
128
  //# sourceMappingURL=TimeSeriesChartBuilder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TimeSeriesChartBuilder.d.ts","sourceRoot":"","sources":["../src/lib/TimeSeriesChartBuilder.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TimeSeriesChartBuilder.d.ts","sourceRoot":"","sources":["../src/lib/TimeSeriesChartBuilder.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,7 @@ export class TimeSeriesChartBuilder {
12
12
  constructor(instance, builderConfig) {
13
13
  this.ECharts = instance;
14
14
  this.builderConfig = { ...this.builderConfig, ...builderConfig };
15
+ this.option.useUTC = true;
15
16
  this.option.animation = false;
16
17
  this.option.legend = this.builderConfig.externalManagerLegend
17
18
  ? {
@@ -56,7 +57,13 @@ export class TimeSeriesChartBuilder {
56
57
  };
57
58
  this.option.xAxis = {
58
59
  type: 'time',
59
- axisLine: { show: true }
60
+ axisLine: { show: true },
61
+ axisLabel: {
62
+ formatter: (value) => {
63
+ const d = new Date(value);
64
+ return d.toTimeString().slice(0, 8);
65
+ }
66
+ }
60
67
  };
61
68
  this.option.yAxis = [
62
69
  {
@@ -129,6 +136,45 @@ export class TimeSeriesChartBuilder {
129
136
  });
130
137
  return this;
131
138
  }
139
+ scrollToTime(timestamp) {
140
+ const [min, max] = this.getRangeValues();
141
+ if (!min || !max)
142
+ return this;
143
+ const range = max - min;
144
+ const targetPercent = ((timestamp - min) / range) * 100;
145
+ const windowSize = 5;
146
+ const start = Math.max(0, targetPercent - windowSize / 2);
147
+ const end = Math.min(100, targetPercent + windowSize / 2);
148
+ this.ECharts.dispatchAction({
149
+ type: 'dataZoom',
150
+ start,
151
+ end
152
+ });
153
+ setTimeout(() => {
154
+ this.ECharts.dispatchAction({
155
+ type: 'showTip',
156
+ seriesIndex: 0,
157
+ dataIndex: this.findClosestDataIndex(timestamp)
158
+ });
159
+ }, 100);
160
+ return this;
161
+ }
162
+ findClosestDataIndex(timestamp) {
163
+ const dataset = this.option.dataset;
164
+ const ts = dataset?.source?.[this._tsColumn] ?? [];
165
+ if (!ts.length)
166
+ return 0;
167
+ let closest = 0;
168
+ let minDiff = Math.abs(ts[0] - timestamp);
169
+ for (let i = 1; i < ts.length; i++) {
170
+ const diff = Math.abs(ts[i] - timestamp);
171
+ if (diff < minDiff) {
172
+ minDiff = diff;
173
+ closest = i;
174
+ }
175
+ }
176
+ return closest;
177
+ }
132
178
  /**
133
179
  * data: [1658870400, 823, 95.8, ...]
134
180
  * dimensionsNames: ['_ts', 'price', 'otherColumn', ...]
@@ -547,7 +593,6 @@ export class TimeSeriesChartBuilder {
547
593
  if (this.yDimensions.length !== this.yDimensionNames.length) {
548
594
  throw new Error(`Dimensions length ${this.yDimensionNames.length} does not match total columns ${this.yDimensions.length}.`);
549
595
  }
550
- this.option.xAxis = { type: 'time', name: timeDimensionName };
551
596
  this.yDimensions.map((dim, inx) => this.addSeries(dim, this.yDimensionNames[inx], (this.yDimensions.length > 1 && dim === 'price') || this.yDimensions.length === 1));
552
597
  }
553
598
  /**
@@ -690,10 +735,22 @@ export class TimeSeriesChartBuilder {
690
735
  const markerPoints = seriesDimension?.markPoint.data;
691
736
  const point = markerPoints.find((mp) => mp.name === `markerpoint-${id}`);
692
737
  if (!point) {
693
- return;
738
+ return this;
694
739
  }
695
740
  point.symbol = point.symbol === 'none' ? this.getIcon(shape) : 'none';
696
741
  this.build();
697
742
  return this;
698
743
  }
744
+ clearMarkers() {
745
+ if (!Array.isArray(this.option.series)) {
746
+ return this;
747
+ }
748
+ for (const s of this.option.series) {
749
+ if (s.markPoint) {
750
+ s.markPoint.data = [];
751
+ }
752
+ }
753
+ this.build();
754
+ return this;
755
+ }
699
756
  }
@@ -0,0 +1,31 @@
1
+ export type ChartDatasetFormatSimpleObject = Record<string, number[]>;
2
+ export type ChartDatasetFormatObject = Record<string, any>[];
3
+ export type ChartDatasetFormatArray = number[][];
4
+ export type ChartDataset = ChartDatasetFormatArray | ChartDatasetFormatObject | ChartDatasetFormatSimpleObject;
5
+ export type ChartMarkerPoint = {
6
+ dimName: string;
7
+ timestamp: number;
8
+ name?: string;
9
+ };
10
+ export type ChartMarkerPointOptions = {
11
+ icon?: string;
12
+ color?: string;
13
+ position?: string;
14
+ symbolSize?: number;
15
+ };
16
+ export interface TimeSeriesChartAdapter {
17
+ setLegendIcon(icon: string): this;
18
+ setDataset(data: ChartDataset, yDimensionsNames?: string[]): this;
19
+ build(): this;
20
+ addDimension(data: ChartDatasetFormatSimpleObject, dimName: string): this;
21
+ addMarkerPoint(id: number, data: ChartMarkerPoint, options?: ChartMarkerPointOptions): this;
22
+ getLegendStatus(): Record<string, boolean>;
23
+ toggleLegend(column: string): this;
24
+ getRangeValues(): [number, number];
25
+ goToZoom(start: number, end: number): this;
26
+ scrollToTime(timestamp: number): this;
27
+ getTotalRows(): number;
28
+ toggleMarkers(id: number, dimName: string, shape: string): this;
29
+ clearMarkers(): this;
30
+ }
31
+ //# sourceMappingURL=chartAdapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chartAdapter.d.ts","sourceRoot":"","sources":["../src/lib/chartAdapter.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,8BAA8B,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;AACtE,MAAM,MAAM,wBAAwB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;AAC7D,MAAM,MAAM,uBAAuB,GAAG,MAAM,EAAE,EAAE,CAAC;AAEjD,MAAM,MAAM,YAAY,GACrB,uBAAuB,GACvB,wBAAwB,GACxB,8BAA8B,CAAC;AAElC,MAAM,MAAM,gBAAgB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACtC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,CAAC,IAAI,EAAE,YAAY,EAAE,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAClE,KAAK,IAAI,IAAI,CAAC;IACd,YAAY,CAAC,IAAI,EAAE,8BAA8B,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1E,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAAC;IAC5F,eAAe,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3C,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,cAAc,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,YAAY,IAAI,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAChE,YAAY,IAAI,IAAI,CAAC;CACrB"}
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.d.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  export { default as SVECharts } from './SVECharts.svelte';
2
+ export { default as SVELightweightCharts } from './SVELightweightCharts.svelte';
3
+ export type { TimeSeriesChartAdapter } from './chartAdapter';
4
+ export { LightweightTimeSeriesChartBuilder } from './LightweightTimeSeriesChartBuilder';
2
5
  export { TimeSeriesChartBuilder } from './TimeSeriesChartBuilder';
3
6
  export * from './types';
4
7
  //# 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,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,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,OAAO,IAAI,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAChF,YAAY,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EAAE,iCAAiC,EAAE,MAAM,qCAAqC,CAAC;AACxF,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,cAAc,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,3 +1,5 @@
1
1
  export { default as SVECharts } from './SVECharts.svelte';
2
+ export { default as SVELightweightCharts } from './SVELightweightCharts.svelte';
3
+ export { LightweightTimeSeriesChartBuilder } from './LightweightTimeSeriesChartBuilder';
2
4
  export { TimeSeriesChartBuilder } from './TimeSeriesChartBuilder';
3
5
  export * from './types';
package/dist/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export type { DataRange, DataZoomEvent } from './SVECharts.svelte';
2
2
  export type { ECharts } from 'echarts/types/dist/core';
3
3
  export type { EChartsOption } from 'echarts';
4
+ export type { IChartApi as LightweightChartApi, Time as LightweightChartTime } from 'lightweight-charts';
4
5
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/lib/types.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnE,YAAY,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACvD,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/lib/types.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnE,YAAY,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACvD,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,YAAY,EACX,SAAS,IAAI,mBAAmB,EAChC,IAAI,IAAI,oBAAoB,EAC5B,MAAM,oBAAoB,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@qtsurfer/sveltecharts",
4
- "version": "0.5.0",
4
+ "version": "0.6.0",
5
5
  "license": "Apache-2.0",
6
6
  "author": "QTSurfer (https://github.com/QTSurfer)",
7
7
  "homepage": "https://github.com/QTSurfer/svelte-timeseries/tree/main/packages/sveltecharts",
@@ -28,6 +28,7 @@
28
28
  "types": "./dist/index.d.ts",
29
29
  "peerDependencies": {
30
30
  "echarts": "^6.0.0",
31
+ "lightweight-charts": "^5.1.0",
31
32
  "svelte": "^5.43.14"
32
33
  },
33
34
  "devDependencies": {
@@ -52,6 +53,7 @@
52
53
  "publint": "^0.3.18",
53
54
  "svelte": "^5.55.0",
54
55
  "svelte-check": "^4.4.5",
56
+ "svelte-eslint-parser": "^1.6.0",
55
57
  "typescript": "^5.9.3",
56
58
  "vite": "^7.2.4",
57
59
  "vitest": "^4.1.0"