@operato/scene-scichart 8.0.0-beta.1 → 8.0.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,37 +0,0 @@
1
- import { AxisBase2D, EventHandler, NumberRange, VisibleRangeChangedArgs } from 'scichart'
2
-
3
- export class AxisSynchroniser {
4
- public visibleRange: NumberRange
5
- private axes: AxisBase2D[] = []
6
- public visibleRangeChanged: EventHandler<VisibleRangeChangedArgs> = new EventHandler<VisibleRangeChangedArgs>()
7
-
8
- public constructor(initialRange: NumberRange, axes?: AxisBase2D[]) {
9
- this.visibleRange = initialRange
10
- this.publishChange = this.publishChange.bind(this)
11
- if (axes) {
12
- axes.forEach(a => this.addAxis(a))
13
- }
14
- }
15
-
16
- public publishChange(data?: VisibleRangeChangedArgs) {
17
- this.visibleRange = data!.visibleRange
18
- this.axes.forEach(a => (a.visibleRange = this.visibleRange))
19
- this.visibleRangeChanged.raiseEvent(data)
20
- }
21
-
22
- public addAxis(axis: AxisBase2D) {
23
- if (!this.axes.includes(axis)) {
24
- this.axes.push(axis)
25
- axis.visibleRange = this.visibleRange
26
- axis.visibleRangeChanged.subscribe(this.publishChange)
27
- }
28
- }
29
-
30
- public removeAxis(axis: AxisBase2D) {
31
- const index = this.axes.findIndex(a => a === axis)
32
- if (index >= 0) {
33
- this.axes.splice(index, 1)
34
- axis.visibleRangeChanged.unsubscribe(this.publishChange)
35
- }
36
- }
37
- }
@@ -1,579 +0,0 @@
1
- import { LitElement, html, css } from 'lit'
2
- import { property, query, customElement } from 'lit/decorators.js'
3
- import { keyed } from 'lit/directives/keyed.js'
4
-
5
- import {
6
- calculatePrecision,
7
- buildSciChart,
8
- buildSciChartOverview,
9
- convertColor,
10
- DEFAULT_COLOR
11
- } from './scichart-builder'
12
- import { AxisSynchroniser } from './axis-synchronizer'
13
- import {
14
- EAutoRange,
15
- FastLineRenderableSeries,
16
- NumberRange,
17
- NumericAxis,
18
- scaleAxes,
19
- SciChartVerticalGroup,
20
- XyDataSeries
21
- } from 'scichart'
22
-
23
- import { ScrollbarStyles } from '@operato/styles'
24
-
25
- @customElement('ox-scichart-multiple')
26
- export class OxSciChartMultiple extends LitElement {
27
- @property({ type: Object }) config: OperatoChart.ChartConfig | null = null
28
- @property({ type: Array }) data: { [attr: string]: any }[] = []
29
- @property({ type: Array }) visibleSeries: string[] = []
30
- @property({ type: Boolean, attribute: 'show-overview' }) showOverview: boolean = true
31
-
32
- private synchronizer: AxisSynchroniser = new AxisSynchroniser(new NumberRange(200, 500))
33
- private verticalGroup: SciChartVerticalGroup = new SciChartVerticalGroup()
34
-
35
- private overviewChart: any = null
36
- private overviewDataSeries: any[] = []
37
- private groupCharts: {
38
- dataKey: string
39
- sciChartSurface: any
40
- dataSeries: any[]
41
- }[] = []
42
-
43
- private initializationQueue: Promise<void> = Promise.resolve()
44
-
45
- /*
46
- [주의]
47
- ox-scichart container의 id를 글로벌 유니크하게 해야한다.
48
- SciChart가 특별히 container의 id를 기반으로 하위 컴포넌트를 구성하고 있기 때문이다.
49
- shadowDom 안에 있는 container 이더라도, 글로벌 유니크한 id를 제공해야 한다.
50
- 그렇지 않으면, 단 하나의 차트만 제대로 렌더링된다.
51
- */
52
- private containerId: string = 'ox-scichart-multiple' + ++OxSciChartMultiple.idx
53
-
54
- @query('.overview') overviewContainer!: HTMLDivElement
55
-
56
- static idx: number = 0
57
-
58
- static styles = [
59
- ScrollbarStyles,
60
- css`
61
- :host {
62
- display: flex;
63
- flex-direction: column;
64
-
65
- width: 100%;
66
- height: 100%;
67
- }
68
-
69
- .overview {
70
- height: 80px;
71
- }
72
-
73
- #chart-group {
74
- flex: 1;
75
-
76
- display: flex;
77
- flex-direction: column;
78
- overflow-y: auto;
79
- }
80
-
81
- .grouped-chart {
82
- flex: 1;
83
-
84
- min-height: 25%;
85
- }
86
-
87
- [hidden] {
88
- display: none;
89
- }
90
- `
91
- ]
92
-
93
- async initializeSciChart() {
94
- this.cleanup()
95
- await this.createOverviewChart()
96
- }
97
-
98
- dispose() {
99
- // 그룹 차트 자원 해제
100
- this.groupCharts.forEach(chart => {
101
- if (chart.sciChartSurface) {
102
- this.synchronizer.removeAxis(chart.sciChartSurface.xAxes.get(0))
103
- this.verticalGroup.removeSurface(chart.sciChartSurface)
104
-
105
- // 각 데이터 시리즈 명시적으로 해제
106
- chart.dataSeries.forEach(ds => ds.delete())
107
-
108
- // 차트 삭제
109
- chart.sciChartSurface.delete()
110
- }
111
- })
112
-
113
- // 오버뷰 차트 자원 해제
114
- if (this.overviewChart) {
115
- this.overviewChart.sciChartSurface.renderableSeries.clear()
116
- this.overviewDataSeries.forEach(ds => ds.delete())
117
- this.overviewChart.sciChartSurface.delete()
118
- this.overviewDataSeries.length = 0
119
- }
120
-
121
- // 사용된 그룹, 오버뷰 데이터 초기화
122
- this.groupCharts.length = 0
123
- this.overviewChart = null
124
- }
125
-
126
- async createOverviewChart() {
127
- const { chart, dataSeries } =
128
- (await buildSciChartOverview(
129
- {
130
- ...this.config,
131
- data: { datasets: [] }
132
- },
133
- this.overviewContainer,
134
- {},
135
- this.synchronizer
136
- )) || {}
137
-
138
- this.verticalGroup.addSurfaceToGroup(chart.sciChartSurface)
139
-
140
- this.overviewChart = chart
141
- this.overviewDataSeries = dataSeries!
142
- }
143
-
144
- async updated(changedProperties: Map<string | number | symbol, unknown>) {
145
- this.initializationQueue = this.initializationQueue
146
- .then(async () => {
147
- let needUpdateDataSeries = false
148
- let needBuildChartGroup = false
149
-
150
- if (changedProperties.has('config') && this.config) {
151
- await this.initializeSciChart()
152
- needBuildChartGroup = true
153
- needUpdateDataSeries = true
154
- }
155
-
156
- if (changedProperties.has('visibleSeries')) {
157
- await this.updateSeries(this.visibleSeries, changedProperties.get('visibleSeries') as string[])
158
- /* [중요] buildChartGroup 을 visibleSeries 수정때마다 하는 이유는, render() 의 캐시 컨트롤이 안되기 때문이다. 가급적 시도하지 말라. */
159
- needBuildChartGroup = true
160
- needUpdateDataSeries = true
161
- }
162
-
163
- if (changedProperties.has('data')) {
164
- needUpdateDataSeries = true
165
- }
166
-
167
- if (needBuildChartGroup) {
168
- await this.buildChartGroup()
169
- }
170
-
171
- if (needUpdateDataSeries) {
172
- await this.updateDataSeries()
173
- }
174
- })
175
- .catch((error: any) => {
176
- console.error('Error in updated queue:', error)
177
- })
178
- }
179
-
180
- cleanup() {
181
- this.cleanupGroup()
182
- this.cleanupOverview()
183
- }
184
-
185
- cleanupGroup() {
186
- this.groupCharts.forEach(chart => {
187
- if (chart.sciChartSurface) {
188
- this.synchronizer.removeAxis(chart.sciChartSurface.xAxes.get(0))
189
- this.verticalGroup.removeSurface(chart.sciChartSurface)
190
- chart.sciChartSurface.delete()
191
- }
192
- })
193
-
194
- this.groupCharts.length = 0
195
- }
196
-
197
- cleanupOverview() {
198
- if (this.overviewChart) {
199
- this.overviewChart.sciChartSurface.renderableSeries.clear()
200
- this.overviewDataSeries.forEach(ds => ds.delete())
201
- this.overviewDataSeries.length = 0
202
- }
203
-
204
- this.overviewChart = null
205
- }
206
-
207
- async updateDataSeries() {
208
- const { config, data } = this
209
- const { datasets = [], labelDataKey: attrX } = config?.data || {}
210
-
211
- if (!(data instanceof Array) || !attrX) {
212
- return []
213
- }
214
-
215
- const newData = this.dataSet
216
-
217
- this.groupCharts.forEach(({ dataKey, sciChartSurface, dataSeries }) => {
218
- try {
219
- // dataKey로 시작하는 모든 시리즈를 업데이트
220
- const relatedDatasets = datasets.filter(dataset => dataset.dataKey?.startsWith(dataKey))
221
-
222
- // 각 relatedDatasets에 대해 해당하는 dataSeries를 업데이트
223
- dataSeries.forEach((ds, index) => {
224
- ds.clear()
225
-
226
- const relatedDataset = relatedDatasets[index]
227
- if (relatedDataset) {
228
- const filteredData = newData[datasets.findIndex(ds => ds.dataKey === relatedDataset.dataKey)].filter(
229
- d => typeof d.yValue === 'number'
230
- )
231
-
232
- if (filteredData.length > 0) {
233
- ds.appendRange(
234
- filteredData.map(d => d.xValue),
235
- filteredData.map(d => d.yValue)
236
- )
237
- }
238
- }
239
- })
240
-
241
- sciChartSurface.zoomExtents()
242
- sciChartSurface.invalidateElement()
243
- } catch (error) {
244
- console.error('Error updating data series:', error)
245
- }
246
- })
247
-
248
- try {
249
- // Overview 차트 데이터 업데이트
250
- this.overviewDataSeries.forEach((ds, index) => {
251
- const visibleKey = this.visibleSeries[index]
252
- const dataset = datasets.find(dataset => dataset.dataKey === visibleKey)
253
- if (!dataset) {
254
- return
255
- }
256
-
257
- const dataIndex = datasets.findIndex(ds => ds.dataKey === dataset.dataKey)
258
- const filteredData = newData[dataIndex]?.filter(d => typeof d.yValue === 'number') || []
259
-
260
- ds.clear()
261
- if (filteredData.length > 0) {
262
- ds.appendRange(
263
- filteredData.map(d => d.xValue),
264
- filteredData.map(d => d.yValue)
265
- )
266
- }
267
- })
268
-
269
- // this.overviewDataSeries.forEach(ds => ds.clear())
270
-
271
- // newData.forEach((data, index) => {
272
- // if (this.visibleSeries.includes(datasets[index].dataKey!)) {
273
- // const filteredData = data.filter(d => typeof d.yValue === 'number')
274
- // if (filteredData.length > 0) {
275
- // this.overviewDataSeries[index].appendRange(
276
- // filteredData.map(d => d.xValue),
277
- // filteredData.map(d => d.yValue)
278
- // )
279
- // }
280
- // }
281
- // })
282
- } catch (error) {
283
- console.error('Error updating overview data series:', error)
284
- }
285
- }
286
-
287
- get dataSet(): { xValue: number; yValue: number }[][] {
288
- const { config, data } = this
289
- const { datasets = [], labelDataKey: attrX } = config?.data || {}
290
-
291
- if (!(data instanceof Array) || !attrX) {
292
- return []
293
- }
294
-
295
- return datasets.map(dataset => {
296
- return data
297
- .map(item => {
298
- if (!item || typeof item !== 'object') {
299
- return
300
- }
301
-
302
- const xValue = new Date(item[attrX])
303
- if (isNaN(xValue.getTime())) {
304
- console.error('Invalid date:', item[attrX])
305
- return
306
- }
307
-
308
- return {
309
- xValue: xValue.getTime() / 1000,
310
- yValue: item[dataset.dataKey!]
311
- }
312
- })
313
- .filter(Boolean) as { xValue: number; yValue: number }[]
314
- })
315
- }
316
-
317
- render() {
318
- const { datasets = [] } = this.config?.data || {}
319
-
320
- return html`
321
- <div id=${this.containerId + '-overview'} class="overview" ?hidden=${!this.showOverview}></div>
322
- <div id="chart-group">
323
- ${this.visibleSeries.map(dataKey =>
324
- keyed(
325
- dataKey,
326
- html`
327
- <div
328
- id=${this.containerId + '-' + dataKey}
329
- class="grouped-chart"
330
- ?hidden=${!this.visibleSeries.includes(dataKey!)}
331
- ></div>
332
- `
333
- )
334
- )}
335
- </div>
336
- `
337
- }
338
-
339
- async buildChartGroup() {
340
- this.cleanupGroup()
341
-
342
- const { config } = this
343
- const { datasets = [] } = config?.data || {}
344
-
345
- await Promise.all(
346
- datasets
347
- .filter(dataset => this.visibleSeries.includes(dataset.dataKey!))
348
- .map(async dataset => {
349
- await this.addChart(dataset.dataKey!)
350
- })
351
- )
352
- }
353
-
354
- async updateSeries(after: string[], before: string[]) {
355
- const addSeries = after?.filter(series => !before?.includes(series)) || []
356
- const removeSeries = before?.filter(series => !after?.includes(series)) || []
357
-
358
- for (const series of removeSeries) {
359
- await this.removeChart(series)
360
- }
361
-
362
- for (const series of addSeries) {
363
- await this.addChart(series)
364
- }
365
- }
366
-
367
- async addChart(dataKey: string) {
368
- const chartData = await this.createChart(dataKey)
369
-
370
- if (chartData) {
371
- const { chart, dataSeries, dataKey } = chartData
372
- this.verticalGroup.addSurfaceToGroup(chart.sciChartSurface)
373
-
374
- this.groupCharts = this.groupSorter([
375
- ...this.groupCharts,
376
- { dataKey, sciChartSurface: chart.sciChartSurface, dataSeries }
377
- ])
378
-
379
- await this.addSeriesToOverviewChart(dataKey)
380
- }
381
- }
382
-
383
- async removeChart(dataKey: string) {
384
- const index = this.groupCharts.findIndex(chart => chart.dataKey == dataKey)
385
- if (index === -1) return
386
-
387
- const [groupedChart] = this.groupCharts.splice(index, 1)
388
- this.destroyChart(groupedChart)
389
-
390
- this.groupCharts = this.groupSorter(this.groupCharts)
391
-
392
- await this.removeSeriesFromOverviewChart(dataKey)
393
- }
394
-
395
- groupSorter(group: any[]) {
396
- return group.sort(
397
- (a, b) =>
398
- this.visibleSeries.findIndex((s: any) => s.dataKey == a.dataKey) -
399
- this.visibleSeries.findIndex((s: any) => s.dataKey == b.dataKey)
400
- )
401
- }
402
-
403
- async appendData(appendum: { [attr: string]: any }[]) {}
404
-
405
- private async createChart(dataKey: string) {
406
- const { data = {}, options = {} } = this.config || {}
407
- const { datasets = [] } = data as OperatoChart.ChartData
408
- const primaryDataset = datasets.find(dataset => dataset.dataKey == dataKey)
409
-
410
- if (!primaryDataset) {
411
- return null
412
- }
413
-
414
- const relatedDatasets = datasets.filter(dataset => dataset.dataKey?.startsWith(dataKey))
415
-
416
- const yAxis = {
417
- ...options.scales?.yAxes?.[0],
418
- axisTitle: primaryDataset?.label
419
- }
420
-
421
- const config = {
422
- ...this.config,
423
- data: {
424
- datasets: relatedDatasets
425
- },
426
- options: {
427
- ...options,
428
- scales: {
429
- ...options.scales,
430
- yAxes: [yAxis]
431
- }
432
- }
433
- }
434
-
435
- const container = this.renderRoot.querySelector(`#${this.containerId + '-' + dataKey}`)
436
- const { chart, dataSeries } = (await buildSciChart(
437
- config,
438
- container,
439
- { fontSize: 14, fontFamily: 'Roboto', fontColor: undefined },
440
- {
441
- precision: primaryDataset.valueFormat ? calculatePrecision(primaryDataset.valueFormat) : undefined,
442
- grouped: this.containerId
443
- }
444
- ))!
445
-
446
- this.synchronizer.addAxis(chart.sciChartSurface.xAxes.get(0))
447
-
448
- // 각 시리즈에 대해 올바른 데이터를 추가
449
- const newData = this.dataSet
450
- dataSeries.forEach((ds, seriesIndex) => {
451
- const dataset = relatedDatasets[seriesIndex]
452
- const filteredData = newData[seriesIndex]?.filter(d => typeof d.yValue === 'number') || []
453
- if (filteredData.length > 0) {
454
- ds.appendRange(
455
- filteredData.map(d => d.xValue),
456
- filteredData.map(d => d.yValue)
457
- )
458
- }
459
- })
460
-
461
- return { chart, dataSeries, dataKey }
462
- }
463
-
464
- private destroyChart(groupedChart: { dataKey: string; sciChartSurface: any; dataSeries: any[] }) {
465
- this.verticalGroup.removeSurface(groupedChart.sciChartSurface)
466
- this.synchronizer.removeAxis(groupedChart.sciChartSurface.xAxes.get(0))
467
- groupedChart.sciChartSurface.delete()
468
- }
469
-
470
- private async addSeriesToOverviewChart(dataKey: string) {
471
- if (!this.overviewChart || !this.overviewDataSeries) {
472
- console.error('Overview chart is not initialized.')
473
- return
474
- }
475
-
476
- // Check if the series already exists in the overview chart
477
- const existingSeries = this.overviewChart.sciChartSurface.renderableSeries.asArray().find((series: any) => {
478
- return series.dataSeries.dataSeriesName === dataKey
479
- })
480
-
481
- if (existingSeries) {
482
- console.warn(`Series for dataKey ${dataKey} already exists in the overview chart.`)
483
- return // Exit the function without adding the series again
484
- }
485
-
486
- const dataset = this.config?.data.datasets.find(dataset => dataset.dataKey === dataKey)
487
- if (!dataset) {
488
- console.error('Dataset not found for dataKey:', dataKey)
489
- return
490
- }
491
-
492
- const newSeries = await this.createSeriesForOverview(dataset)
493
-
494
- if (newSeries) {
495
- this.overviewChart.sciChartSurface.renderableSeries.add(newSeries.series)
496
- this.overviewDataSeries.push(newSeries.dataSeries)
497
- }
498
- }
499
-
500
- private async removeSeriesFromOverviewChart(dataKey: string) {
501
- if (!this.overviewChart || !this.overviewDataSeries) {
502
- console.error('Overview chart is not initialized.')
503
- return
504
- }
505
-
506
- const { sciChartSurface } = this.overviewChart
507
-
508
- // 오버뷰 차트의 renderableSeries에서 해당 시리즈 제거
509
- const seriesIndex = sciChartSurface.renderableSeries.asArray().findIndex((series: any) => {
510
- return series.dataSeries.dataSeriesName === dataKey
511
- })
512
-
513
- if (seriesIndex !== -1) {
514
- const series = sciChartSurface.renderableSeries.get(seriesIndex)
515
- const yAxisId = series.yAxisId
516
-
517
- // 시리즈 제거
518
- sciChartSurface.renderableSeries.removeAt(seriesIndex)
519
- this.overviewDataSeries.splice(seriesIndex, 1) // 데이터 시리즈도 제거
520
-
521
- // 고유 Y축 제거
522
- if (yAxisId) {
523
- const yAxisIndex = sciChartSurface.yAxes.asArray().findIndex((axis: any) => axis.id === yAxisId)
524
- if (yAxisIndex !== -1) {
525
- sciChartSurface.yAxes.removeAt(yAxisIndex)
526
- }
527
- }
528
- } else {
529
- console.error('Series not found in overview chart for dataKey:', dataKey)
530
- }
531
- }
532
-
533
- private async createSeriesForOverview(dataset: any) {
534
- if (!this.overviewChart) return null
535
-
536
- const { sciChartSurface, wasmContext } = this.overviewChart
537
- const dataSeries = new XyDataSeries(wasmContext, {
538
- dataSeriesName: dataset.dataKey,
539
- containsNaN: false
540
- })
541
-
542
- // 새로운 Y축을 추가하여 노멀라이즈 효과를 제공
543
- const yAxisId = `yAxis_${dataset.dataKey}`
544
- const yAxis = new NumericAxis(wasmContext, {
545
- id: yAxisId,
546
- autoRange: EAutoRange.Always,
547
- drawLabels: false,
548
- drawMajorTickLines: false,
549
- drawMinorTickLines: false,
550
- drawMajorGridLines: false,
551
- drawMinorGridLines: false
552
- })
553
-
554
- sciChartSurface.yAxes.add(yAxis)
555
-
556
- const series = new FastLineRenderableSeries(wasmContext, {
557
- dataSeries,
558
- strokeThickness: 1,
559
- stroke: convertColor(dataset.color, DEFAULT_COLOR),
560
- yAxisId
561
- })
562
-
563
- // 초기 데이터 추가
564
- const newData = this.dataSet.find((data, index) => this.config?.data.datasets[index].dataKey === dataset.dataKey)
565
- if (newData && newData.length > 0) {
566
- const filteredData = newData.filter(d => typeof d.yValue === 'number')
567
- if (filteredData.length > 0) {
568
- dataSeries.appendRange(
569
- filteredData.map(d => d.xValue),
570
- filteredData.map(d => d.yValue)
571
- )
572
- } else {
573
- console.warn('No valid yValues found for dataset:', dataset.dataKey)
574
- }
575
- }
576
-
577
- return { series, dataSeries }
578
- }
579
- }