@operato/chart 7.0.1 → 7.0.4

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,10 +1,18 @@
1
1
  import { TinyColor } from '@ctrl/tinycolor'
2
2
  import { format as formatText } from '../utils/text-formatter'
3
3
 
4
+ function getBaseColorFromTheme(theme?: 'light' | 'dark' | 'auto') {
5
+ return new TinyColor(theme == 'dark' ? '#fff' : '#000')
6
+ }
7
+
8
+ function getThemeFromBrowser() {
9
+ return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
10
+ }
11
+
4
12
  export async function buildSciChart(
5
13
  config: OperatoChart.ChartConfig | undefined | null,
6
14
  container: any,
7
- { fontSize, fontFamily, fontColor = 'red' }: { fontSize?: number; fontFamily?: string; fontColor?: string }
15
+ { fontSize, fontFamily, fontColor }: { fontSize?: number; fontFamily?: string; fontColor?: string }
8
16
  ): Promise<{ chart: any; dataSeries: any[] } | undefined> {
9
17
  if (!config) {
10
18
  return
@@ -12,10 +20,16 @@ export async function buildSciChart(
12
20
 
13
21
  const {
14
22
  SciChartSurface,
15
- SciChartJsNavyTheme,
23
+ SciChartJSLightTheme,
24
+ SciChartJSDarkv2Theme,
16
25
  XyDataSeries,
17
26
  FastLineRenderableSeries,
27
+ SplineLineRenderableSeries,
18
28
  FastColumnRenderableSeries,
29
+ StackedColumnRenderableSeries,
30
+ StackedMountainRenderableSeries,
31
+ StackedColumnCollection,
32
+ StackedMountainCollection,
19
33
  NumericAxis,
20
34
  DateTimeNumericAxis,
21
35
  EAutoRange,
@@ -24,25 +38,60 @@ export async function buildSciChart(
24
38
  MouseWheelZoomModifier,
25
39
  RubberBandXyZoomModifier,
26
40
  ZoomExtentsModifier,
27
- RolloverModifier
41
+ RolloverModifier,
42
+ SmartDateLabelProvider,
43
+ EllipsePointMarker,
44
+ SquarePointMarker,
45
+ TrianglePointMarker,
46
+ CrossPointMarker,
47
+ XPointMarker,
48
+ WaveAnimation,
49
+ LegendModifier
28
50
  } = SciChart
29
51
 
30
52
  const { type: chartType, options, data: fromData } = config
31
- const { theme, legend, scales: fromScales, xGridLine, yGridLine, y2ndGridLine } = options || {}
32
53
  const { datasets = [] } = fromData || {}
54
+ var {
55
+ theme,
56
+ tooltip,
57
+ animation,
58
+ legend,
59
+ scales: fromScales,
60
+ xGridLine,
61
+ yGridLine,
62
+ y2ndGridLine,
63
+ stacked
64
+ } = options || {}
65
+
66
+ var baseColor = getBaseColorFromTheme(theme)
67
+
68
+ if (theme === 'auto') {
69
+ theme = getThemeFromBrowser()
70
+ }
71
+
72
+ fontColor = fontColor || baseColor.clone().toString()
33
73
 
34
74
  const { xAxes = [], yAxes = [] } = fromScales || {}
35
75
 
36
76
  const chart = await SciChartSurface.create(container, {
37
- theme: new SciChartJsNavyTheme()
77
+ theme: theme == 'dark' ? new SciChartJSDarkv2Theme() : new SciChartJSLightTheme()
38
78
  })
39
79
  const { sciChartSurface, wasmContext } = chart
40
80
 
41
81
  // X 축 설정
42
82
  xAxes.forEach((axis, index) => {
43
83
  const { axisTitle, ticks } = axis
44
- const { autoMax, autoMin, min, max, stepSize, beginAtZero } = ticks || {}
45
- const id = xAxes.length > 1 ? `x${index + 1}` : 'x'
84
+ const {
85
+ autoMax,
86
+ autoMin,
87
+ min,
88
+ max,
89
+ stepSize,
90
+ beginAtZero,
91
+ color = fontColor,
92
+ textStrokeColor = fontColor,
93
+ display = !!axisTitle
94
+ } = ticks || {}
46
95
 
47
96
  const xAxis = new DateTimeNumericAxis(wasmContext, {
48
97
  axisTitle,
@@ -54,13 +103,14 @@ export async function buildSciChart(
54
103
  labelStyle: {
55
104
  fontFamily,
56
105
  fontSize,
57
- color: fontColor
106
+ color
58
107
  },
59
108
  axisTitleStyle: {
60
109
  fontFamily,
61
110
  fontSize,
62
- color: fontColor
63
- }
111
+ color: textStrokeColor
112
+ },
113
+ labelProvider: new SmartDateLabelProvider()
64
114
  })
65
115
 
66
116
  sciChartSurface.xAxes.add(xAxis)
@@ -70,12 +120,12 @@ export async function buildSciChart(
70
120
  yAxes.forEach((axis, index) => {
71
121
  const { axisTitle, ticks } = axis
72
122
  const { autoMax, autoMin, min, max, stepSize, beginAtZero } = ticks || {}
73
- const id = yAxes.length > 1 ? `right` : 'left'
74
123
 
75
124
  const yAxis = new NumericAxis(wasmContext, {
125
+ id: `yAxis${index}`,
76
126
  axisTitle,
77
127
  autoRange: autoMin || autoMax ? EAutoRange.Always : undefined,
78
- axisAlignment: yAxes.length > 1 ? EAxisAlignment.Right : EAxisAlignment.Left,
128
+ axisAlignment: index === 0 ? EAxisAlignment.Left : EAxisAlignment.Right,
79
129
  visibleRange: min !== undefined && max !== undefined ? new NumberRange(min, max) : undefined,
80
130
  majorDelta: stepSize,
81
131
  growBy: beginAtZero ? new NumberRange(0.1, 0.1) : undefined,
@@ -90,6 +140,7 @@ export async function buildSciChart(
90
140
  color: fontColor
91
141
  }
92
142
  })
143
+
93
144
  sciChartSurface.yAxes.add(yAxis)
94
145
  })
95
146
 
@@ -100,36 +151,158 @@ export async function buildSciChart(
100
151
  containsNaN: false
101
152
  })
102
153
 
154
+ const yAxisId = dataset.yAxisID || 'yAxis0'
155
+ const stackGroupId = dataset.stack || `__stack${index}__`
156
+
103
157
  let series: any
104
158
  if (dataset.type === 'bar') {
105
- series = new FastColumnRenderableSeries(wasmContext, {
106
- dataSeries,
107
- strokeThickness: dataset.borderWidth || 2,
108
- fill: dataset.backgroundColor || '#FF6600'
109
- })
159
+ if (stacked) {
160
+ series = new StackedColumnRenderableSeries(wasmContext, {
161
+ dataSeries,
162
+ strokeThickness: dataset.borderWidth || 2,
163
+ fill: dataset.backgroundColor || '#FF6600',
164
+ yAxisId,
165
+ stackedGroupId: stackGroupId
166
+ })
167
+ } else {
168
+ series = new FastColumnRenderableSeries(wasmContext, {
169
+ dataSeries,
170
+ strokeThickness: dataset.borderWidth || 2,
171
+ fill: dataset.backgroundColor || '#FF6600',
172
+ animation: animation && new WaveAnimation({ duration: 1000, fadeEffect: true }),
173
+ yAxisId
174
+ })
175
+ }
110
176
  } else {
111
- series = new FastLineRenderableSeries(wasmContext, {
112
- dataSeries,
113
- strokeThickness: dataset.borderWidth || 2,
114
- stroke: dataset.color || '#FF6600'
115
- })
177
+ const { pointStyle, lineTension } = dataset
178
+ let pointMarker
179
+
180
+ switch (pointStyle) {
181
+ case 'circle':
182
+ pointMarker = new EllipsePointMarker(wasmContext, {
183
+ width: 10,
184
+ height: 10,
185
+ strokeThickness: 2,
186
+ fill: dataset.color || '#FF6600',
187
+ stroke: '#000000'
188
+ })
189
+ break
190
+ case 'triangle':
191
+ pointMarker = new TrianglePointMarker(wasmContext, {
192
+ width: 10,
193
+ height: 10,
194
+ strokeThickness: 2,
195
+ fill: dataset.color || '#FF6600',
196
+ stroke: '#000000'
197
+ })
198
+ break
199
+ case 'rect':
200
+ pointMarker = new SquarePointMarker(wasmContext, {
201
+ width: 10,
202
+ height: 10,
203
+ strokeThickness: 2,
204
+ fill: dataset.color || '#FF6600',
205
+ stroke: '#000000'
206
+ })
207
+ break
208
+ case 'cross':
209
+ pointMarker = new CrossPointMarker(wasmContext, {
210
+ width: 10,
211
+ height: 10,
212
+ strokeThickness: 2,
213
+ fill: dataset.color || '#FF6600',
214
+ stroke: '#000000'
215
+ })
216
+ break
217
+ case 'crossRot':
218
+ pointMarker = new XPointMarker(wasmContext, {
219
+ width: 10,
220
+ height: 10,
221
+ strokeThickness: 2,
222
+ fill: dataset.color || '#FF6600',
223
+ stroke: '#000000'
224
+ })
225
+ break
226
+ default:
227
+ pointMarker = new EllipsePointMarker(wasmContext, {
228
+ width: 10,
229
+ height: 10,
230
+ strokeThickness: 2,
231
+ fill: dataset.color || '#FF6600',
232
+ stroke: '#000000'
233
+ })
234
+ }
235
+
236
+ if (stacked) {
237
+ series = new StackedMountainRenderableSeries(wasmContext, {
238
+ dataSeries,
239
+ strokeThickness: dataset.borderWidth || 2,
240
+ stroke: dataset.color || '#FF6600',
241
+ fill: dataset.backgroundColor || '#FF6600',
242
+ yAxisId,
243
+ stackedGroupId: stackGroupId
244
+ })
245
+ } else {
246
+ series =
247
+ !!lineTension && lineTension > 0
248
+ ? new SplineLineRenderableSeries(wasmContext, {
249
+ dataSeries,
250
+ strokeThickness: dataset.borderWidth || 2,
251
+ stroke: dataset.color || '#FF6600',
252
+ pointMarker,
253
+ animation: animation && new WaveAnimation({ duration: 1000, fadeEffect: true }),
254
+ yAxisId
255
+ })
256
+ : new FastLineRenderableSeries(wasmContext, {
257
+ dataSeries,
258
+ strokeThickness: dataset.borderWidth || 2,
259
+ stroke: dataset.color || '#FF6600',
260
+ pointMarker,
261
+ animation: animation && new WaveAnimation({ duration: 1000, fadeEffect: true }),
262
+ yAxisId
263
+ })
264
+ }
116
265
  }
117
266
 
118
267
  sciChartSurface.renderableSeries.add(series)
119
268
 
120
- const rolloverModifier = new RolloverModifier({
121
- showTooltip: true,
122
- showAxisLabels: true,
123
- tooltipColor: 'white',
124
- tooltipBackgroundColor: 'rgba(0, 0, 0, 0.7)',
125
- rollOverDataSeries: dataSeries
126
- })
269
+ if (tooltip) {
270
+ const rolloverModifier = new RolloverModifier({
271
+ showTooltip: true,
272
+ showAxisLabels: true,
273
+ tooltipColor: 'white',
274
+ tooltipBackgroundColor: 'rgba(0, 0, 0, 0.7)',
275
+ rollOverDataSeries: dataSeries
276
+ })
127
277
 
128
- sciChartSurface.chartModifiers.add(rolloverModifier)
278
+ sciChartSurface.chartModifiers.add(rolloverModifier)
279
+ }
129
280
 
130
281
  return dataSeries
131
282
  })
132
283
 
284
+ // Stacked collections 추가
285
+ if (stacked) {
286
+ const stackedColumnCollection = new StackedColumnCollection(wasmContext)
287
+ const stackedMountainCollection = new StackedMountainCollection(wasmContext)
288
+
289
+ sciChartSurface.renderableSeries.asArray().forEach((series: any) => {
290
+ if (series instanceof StackedColumnRenderableSeries) {
291
+ stackedColumnCollection.add(series)
292
+ } else if (series instanceof StackedMountainRenderableSeries) {
293
+ stackedMountainCollection.add(series)
294
+ }
295
+ })
296
+
297
+ if (stackedColumnCollection.size() > 0) {
298
+ sciChartSurface.renderableSeries.add(stackedColumnCollection)
299
+ }
300
+
301
+ if (stackedMountainCollection.size() > 0) {
302
+ sciChartSurface.renderableSeries.add(stackedMountainCollection)
303
+ }
304
+ }
305
+
133
306
  // 줌인/줌아웃 모디파이어 추가
134
307
  sciChartSurface.chartModifiers.add(
135
308
  new RubberBandXyZoomModifier(),
@@ -137,5 +310,16 @@ export async function buildSciChart(
137
310
  new ZoomExtentsModifier()
138
311
  )
139
312
 
313
+ // Legend 설정
314
+ if (legend?.display) {
315
+ const legendModifier = new LegendModifier({
316
+ showCheckboxes: true,
317
+ showSeriesMarkers: true,
318
+ showLegend: true,
319
+ placement: legend.position || 'bottom-right'
320
+ })
321
+ sciChartSurface.chartModifiers.add(legendModifier)
322
+ }
323
+
140
324
  return { chart, dataSeries: dataSeriesArray }
141
325
  }
package/src/types.d.ts CHANGED
@@ -24,7 +24,17 @@ declare namespace OperatoChart {
24
24
  stack?: string
25
25
  fill?: boolean
26
26
  lineTension?: number
27
- pointStyle?: string
27
+ pointStyle?:
28
+ | 'circle'
29
+ | 'triangle'
30
+ | 'rect'
31
+ | 'rectRot'
32
+ | 'cross'
33
+ | 'crossRot'
34
+ | 'star'
35
+ | 'line'
36
+ | 'dash'
37
+ | undefined
28
38
  pointRadius?: number
29
39
  valuePrefix?: string
30
40
  valueSuffix?: string
@@ -34,7 +44,7 @@ declare namespace OperatoChart {
34
44
  }
35
45
 
36
46
  export interface ChartOptions {
37
- theme?: 'dark' | 'light'
47
+ theme?: 'dark' | 'light' | 'auto'
38
48
  tooltip?: boolean
39
49
  animation?: boolean
40
50
  legend?: LegendOptions
@@ -74,5 +84,7 @@ declare namespace OperatoChart {
74
84
  max?: number
75
85
  stepSize?: number
76
86
  beginAtZero?: boolean
87
+ color?: string
88
+ textStrokeColor?: string
77
89
  }
78
90
  }
package/stories/common.ts CHANGED
@@ -63,10 +63,10 @@ function getRandomInRange(min: number, max: number) {
63
63
  // 랜덤 데이터를 생성하는 함수
64
64
  function generateRandomData(count: number) {
65
65
  const randomData = []
66
- const startTimestamp = Math.floor(Date.now() / 1000) // 현재 시간을 Unix 타임스탬프로 설정
66
+ const startTimestamp = Math.floor(Date.now()) // 현재 시간을 Unix 타임스탬프로 설정
67
67
 
68
68
  for (let i = 0; i < count; i++) {
69
- const timestamp = startTimestamp + i * 3 // 3초씩 증가하는 타임스탬프 설정
69
+ const timestamp = startTimestamp + i * 360 * 30 * 1000 // 3초씩 증가하는 타임스탬프 설정
70
70
  const randomCount = getRandomInRange(5, 35) // count 값을 5에서 35 사이로 랜덤 생성
71
71
  const randomAverage = getRandomInRange(50, 150) // average 값을 50에서 150 사이로 랜덤 생성
72
72
 
@@ -105,7 +105,7 @@ const Template: Story<ArgTypes> = ({ value }: ArgTypes) => html`
105
105
  ></ox-input-chart-timeseries>
106
106
  <div id="charts">
107
107
  <ox-scichart id="scichart" .data=${data} attr-x="timestamp" attr-y="count"></ox-scichart>
108
- <ox-chart-js id="chartjs" .data=${data}></ox-chart-js>
108
+ <!-- <ox-chart-js id="chartjs" .data=${data}></ox-chart-js> -->
109
109
  </div>
110
110
  </div>
111
111
  `
@@ -141,8 +141,22 @@ WithData.args = {
141
141
  },
142
142
  options: {
143
143
  scales: {
144
- xAxes: [{ ticks: { beginAtZero: true } }],
145
- yAxes: [{ ticks: { beginAtZero: true } }]
144
+ xAxes: [
145
+ {
146
+ axisTitle: 'timestamp',
147
+ ticks: { beginAtZero: true }
148
+ }
149
+ ],
150
+ yAxes: [
151
+ {
152
+ axisTitle: 'count',
153
+ ticks: { beginAtZero: true }
154
+ },
155
+ {
156
+ axisTitle: 'average',
157
+ ticks: { beginAtZero: true }
158
+ }
159
+ ]
146
160
  },
147
161
  legend: { display: true }
148
162
  }
@@ -153,16 +167,13 @@ export const MultiAxis = Template.bind({})
153
167
  MultiAxis.args = {
154
168
  value: {
155
169
  ...getDefaultChartConfig('line'),
156
- options: {
157
- ...getDefaultChartConfig('line').options,
158
- multiAxis: true
159
- },
160
170
  data: {
161
171
  datasets: [
162
172
  {
163
173
  label: 'Bar Series',
164
174
  type: 'bar',
165
175
  dataKey: 'count',
176
+ axisTitle: 'count',
166
177
  backgroundColor: 'rgba(255, 99, 132, 0.2)',
167
178
  borderColor: 'rgba(255, 99, 132, 1)',
168
179
  borderWidth: 1,
@@ -172,16 +183,39 @@ MultiAxis.args = {
172
183
  label: 'Line Series',
173
184
  type: 'line',
174
185
  dataKey: 'average',
186
+ axisTitle: 'average',
175
187
  color: 'rgba(54, 162, 235, 1)',
176
188
  borderWidth: 1,
177
189
  fill: false,
178
190
  lineTension: 0.4,
179
- pointStyle: 'circle',
191
+ pointStyle: 'rect',
180
192
  pointRadius: 3,
181
193
  yAxisID: 'right'
182
194
  }
183
195
  ],
184
196
  labelDataKey: 'timestamp'
197
+ },
198
+ options: {
199
+ scales: {
200
+ xAxes: [
201
+ {
202
+ axisTitle: 'timestamp',
203
+ ticks: { beginAtZero: true }
204
+ }
205
+ ],
206
+ yAxes: [
207
+ {
208
+ axisTitle: 'count',
209
+ ticks: { beginAtZero: true }
210
+ },
211
+ {
212
+ axisTitle: 'average',
213
+ ticks: { beginAtZero: true }
214
+ }
215
+ ]
216
+ },
217
+ multiAxis: true,
218
+ legend: { display: true }
185
219
  }
186
220
  }
187
221
  }