@operato/chart 7.0.37 → 7.0.40

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.
Files changed (61) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/src/editors/configurer.d.ts +4 -2
  3. package/dist/src/editors/configurer.js +27 -14
  4. package/dist/src/editors/configurer.js.map +1 -1
  5. package/dist/src/editors/input-chart-abstract.d.ts +0 -26
  6. package/dist/src/editors/input-chart-abstract.js +1 -607
  7. package/dist/src/editors/input-chart-abstract.js.map +1 -1
  8. package/dist/src/editors/input-chart-styles.js +27 -0
  9. package/dist/src/editors/input-chart-styles.js.map +1 -1
  10. package/dist/src/editors/ox-input-chart-hbar.d.ts +1 -1
  11. package/dist/src/editors/ox-input-chart-hbar.js +2 -69
  12. package/dist/src/editors/ox-input-chart-hbar.js.map +1 -1
  13. package/dist/src/editors/ox-input-chart-mixed.d.ts +1 -0
  14. package/dist/src/editors/ox-input-chart-mixed.js +2 -2
  15. package/dist/src/editors/ox-input-chart-mixed.js.map +1 -1
  16. package/dist/src/editors/ox-input-chart-pie.d.ts +1 -0
  17. package/dist/src/editors/ox-input-chart-pie.js +3 -1
  18. package/dist/src/editors/ox-input-chart-pie.js.map +1 -1
  19. package/dist/src/editors/ox-input-chart-radar.d.ts +1 -0
  20. package/dist/src/editors/ox-input-chart-radar.js +2 -2
  21. package/dist/src/editors/ox-input-chart-radar.js.map +1 -1
  22. package/dist/src/editors/ox-input-chart-timeseries.d.ts +2 -0
  23. package/dist/src/editors/ox-input-chart-timeseries.js +9 -3
  24. package/dist/src/editors/ox-input-chart-timeseries.js.map +1 -1
  25. package/dist/src/editors/ox-property-editor-chart.js.map +1 -1
  26. package/dist/src/editors/templates/annotations.d.ts +23 -0
  27. package/dist/src/editors/templates/annotations.js +270 -0
  28. package/dist/src/editors/templates/annotations.js.map +1 -0
  29. package/dist/src/editors/templates/display-value.d.ts +12 -0
  30. package/dist/src/editors/templates/display-value.js +105 -0
  31. package/dist/src/editors/templates/display-value.js.map +1 -0
  32. package/dist/src/editors/templates/series.d.ts +31 -0
  33. package/dist/src/editors/templates/series.js +277 -0
  34. package/dist/src/editors/templates/series.js.map +1 -0
  35. package/dist/src/scichart/ox-scichart.js +4 -3
  36. package/dist/src/scichart/ox-scichart.js.map +1 -1
  37. package/dist/src/scichart/scichart-builder.js +25 -7
  38. package/dist/src/scichart/scichart-builder.js.map +1 -1
  39. package/dist/stories/common.d.ts +2 -2
  40. package/dist/stories/common.js +6 -3
  41. package/dist/stories/common.js.map +1 -1
  42. package/dist/stories/ox-input-chart-timeseries.stories.js +2 -2
  43. package/dist/stories/ox-input-chart-timeseries.stories.js.map +1 -1
  44. package/dist/tsconfig.tsbuildinfo +1 -1
  45. package/package.json +2 -2
  46. package/src/editors/configurer.ts +28 -15
  47. package/src/editors/input-chart-abstract.ts +1 -655
  48. package/src/editors/input-chart-styles.ts +27 -0
  49. package/src/editors/ox-input-chart-hbar.ts +3 -73
  50. package/src/editors/ox-input-chart-mixed.ts +2 -2
  51. package/src/editors/ox-input-chart-pie.ts +3 -2
  52. package/src/editors/ox-input-chart-radar.ts +3 -2
  53. package/src/editors/ox-input-chart-timeseries.ts +10 -3
  54. package/src/editors/ox-property-editor-chart.ts +1 -1
  55. package/src/editors/templates/annotations.ts +287 -0
  56. package/src/editors/templates/display-value.ts +110 -0
  57. package/src/editors/templates/series.ts +304 -0
  58. package/src/scichart/ox-scichart.ts +7 -3
  59. package/src/scichart/scichart-builder.ts +30 -6
  60. package/stories/common.ts +6 -3
  61. package/stories/ox-input-chart-timeseries.stories.ts +2 -2
@@ -0,0 +1,304 @@
1
+ import '@material/web/icon/icon.js'
2
+
3
+ import { LitElement, html, PropertyValues } from 'lit'
4
+ import { customElement, property, query } from 'lit/decorators.js'
5
+ import { keyed } from 'lit/directives/keyed.js'
6
+ import { MdIcon } from '@material/web/icon/icon.js'
7
+ import { random as randomColor, TinyColor } from '@ctrl/tinycolor'
8
+
9
+ import { Configurer } from '../configurer'
10
+ import './display-value'
11
+
12
+ @customElement('ox-chart-series')
13
+ export class MultipleSeriesTemplate extends LitElement {
14
+ createRenderRoot() {
15
+ return this
16
+ }
17
+
18
+ @property({ type: Object }) configurer!: Configurer
19
+ @property({ type: String }) chartType?: 'bar' | 'horizontalBar' | 'line' | 'radar' | 'pie' | 'doughnut' | 'polarArea'
20
+
21
+ @query('#series-tabs') seriesTabs!: HTMLElement
22
+ @query('#series-tab-nav-left-button') seriesTabNavLeftButton!: MdIcon
23
+ @query('#series-tab-nav-right-button') seriesTabNavRightButton!: MdIcon
24
+
25
+ protected updated(_changedProperties: PropertyValues): void {
26
+ if (_changedProperties.has('configurer') || _changedProperties.has('chartType')) {
27
+ this.requestUpdate()
28
+ }
29
+ }
30
+
31
+ render() {
32
+ return keyed(
33
+ this.configurer.currentSeriesIndex,
34
+ html`
35
+ <div id="series-properties-container" fullwidth>
36
+ <div id="series-tab-header" @wheel=${this._onWheelScroll}>
37
+ <md-icon id="series-tab-nav-left-button" @click=${this._scrollLeft}>chevron_left</md-icon>
38
+ <div id="series-tabs" active-tab-index=${this.configurer.currentSeriesIndex} fit-container>
39
+ ${this._renderTabs()}
40
+ </div>
41
+ <md-icon id="series-tab-nav-right-button" @click=${this._scrollRight}>chevron_right</md-icon>
42
+ </div>
43
+ <div id="add-series-button-container">
44
+ <md-icon id="add-series-button" @click=${this._addSeries}>add</md-icon>
45
+ </div>
46
+ ${this._renderSeriesForm()}
47
+ </div>
48
+ `
49
+ )
50
+ }
51
+
52
+ private _renderTabs() {
53
+ return this.configurer.datasets.map(
54
+ (dataset: OperatoChart.Dataset, index: number) => html`
55
+ <div
56
+ data-series=${index + 1}
57
+ data-tab-index=${index}
58
+ tab
59
+ ?selected=${index === this.configurer.currentSeriesIndex}
60
+ @click=${() => this._selectTab(index)}
61
+ >
62
+ ${index + 1}
63
+ ${this.configurer.datasets.length > 1 && this.configurer.currentSeriesIndex === index
64
+ ? html`<md-icon @click=${() => this._removeSeries(index)}>close</md-icon>`
65
+ : html``}
66
+ </div>
67
+ `
68
+ )
69
+ }
70
+
71
+ private _renderSeriesForm() {
72
+ const configurer = this.configurer
73
+ const chartType = this.chartType
74
+
75
+ return html`
76
+ <div class="tab-content">
77
+ <label for="data-key"> <ox-i18n msgid="label.data-key">Data Key</ox-i18n> </label>
78
+ <input id="data-key" type="text" value-key="dataKey" .value=${configurer.dataKey || ''} />
79
+
80
+ ${chartType === 'line' || chartType === 'bar'
81
+ ? html`
82
+ <label for="series-type"> <ox-i18n msgid="label.series-type">Type</ox-i18n> </label>
83
+ <select
84
+ id="series-type"
85
+ class="select-content"
86
+ value-key="series.type"
87
+ .value=${configurer.series.type || ''}
88
+ >
89
+ <option value="bar" selected>Bar</option>
90
+ <option value="line">Line</option>
91
+ </select>
92
+ `
93
+ : html``}
94
+
95
+ <label for="series-label"> <ox-i18n msgid="label.label">Label</ox-i18n> </label>
96
+ <input id="series-label" type="text" value-key="series.label" .value=${configurer.series.label || ''} />
97
+
98
+ ${configurer.series.type === 'line'
99
+ ? html`
100
+ <label for="series-line-tension"> <ox-i18n msgid="label.line-tension">Line Tension</ox-i18n> </label>
101
+ <select
102
+ id="series-line-tension"
103
+ class="select-content"
104
+ value-key="series.lineTension"
105
+ .value=${String(configurer.series.lineTension || 0)}
106
+ >
107
+ <option value="0.4">Smooth</option>
108
+ <option value="0">Angled</option>
109
+ </select>
110
+ `
111
+ : html``}
112
+ ${configurer.series.type === 'line'
113
+ ? html`
114
+ <label for="series-border-width"> <ox-i18n msgid="label.border-width">Border Width</ox-i18n> </label>
115
+ <input
116
+ id="series-border-width"
117
+ type="number"
118
+ value-key="series.borderWidth"
119
+ .value=${String(configurer.series.borderWidth || 0)}
120
+ />
121
+ `
122
+ : html``}
123
+
124
+ <label for="series-color"> <ox-i18n msgid="label.color">Color</ox-i18n> </label>
125
+ <ox-input-color id="series-color" value-key="color" .value=${configurer.color}></ox-input-color>
126
+
127
+ ${configurer.series.type === 'line'
128
+ ? html`
129
+ <label for="series-point-style"> <ox-i18n msgid="label.point-shape">Point Shape</ox-i18n> </label>
130
+ <select
131
+ id="series-point-style"
132
+ class="select-content"
133
+ value-key="series.pointStyle"
134
+ .value=${configurer.series.pointStyle || ''}
135
+ >
136
+ <option value="">&nbsp;</option>
137
+ <option value="circle">⚬</option>
138
+ <option value="triangle">▵</option>
139
+ <option value="rect">□</option>
140
+ <option value="rectRot">◇</option>
141
+ <option value="cross">+</option>
142
+ <option value="crossRot">⨉</option>
143
+ <option value="star">✱</option>
144
+ <option value="line">―</option>
145
+ <option value="dash">┄</option>
146
+ </select>
147
+
148
+ <label for="series-point-radius"> <ox-i18n msgid="label.point-size">Point Size</ox-i18n> </label>
149
+ <input
150
+ id="series-point-radius"
151
+ type="number"
152
+ value-key="series.pointRadius"
153
+ .value=${String(configurer.series.pointRadius || 0)}
154
+ />
155
+ `
156
+ : html``}
157
+
158
+ <label for="series-stack"> <ox-i18n msgid="label.stack-group">Stack group</ox-i18n> </label>
159
+ <input id="series-stack" type="text" value-key="series.stack" .value=${configurer.series.stack || ''} />
160
+
161
+ ${configurer.series.type === 'line'
162
+ ? html`
163
+ <input id="series-fill" type="checkbox" value-key="series.fill" ?checked=${configurer.series.fill} />
164
+ <label for="series-fill"> <ox-i18n msgid="label.fill">Fill</ox-i18n> </label>
165
+ `
166
+ : html``}
167
+ ${configurer.multiAxis && configurer.series.type !== 'horizontalBar'
168
+ ? html`
169
+ <label for="series-y-axis-id"> <ox-i18n msgid="label.target-axis">Target Axis</ox-i18n> </label>
170
+ <select
171
+ id="series-y-axis-id"
172
+ class="select-content"
173
+ value-key="series.yAxisID"
174
+ .value=${configurer.series.yAxisID || ''}
175
+ >
176
+ <option value="left">Left</option>
177
+ <option value="right">Right</option>
178
+ </select>
179
+ `
180
+ : html``}
181
+
182
+ <label></label>
183
+ <ox-chart-display-value .configurer=${configurer} fullwidth></ox-chart-display-value>
184
+ </div>
185
+ `
186
+ }
187
+
188
+ _getSeriesModel({
189
+ chartType,
190
+ datasetsLength,
191
+ lastSeriesColor
192
+ }: {
193
+ chartType: string
194
+ datasetsLength: number
195
+ lastSeriesColor: TinyColor
196
+ }) {
197
+ const addSeriesOption: any = {
198
+ label: `series ${datasetsLength + 1}`,
199
+ data: [],
200
+ borderWidth: 1,
201
+ dataKey: '',
202
+ yAxisID: 'left',
203
+ color: randomColor({
204
+ hue: lastSeriesColor.toHsv().h
205
+ }).toRgbString(),
206
+ stack: ''
207
+ }
208
+
209
+ addSeriesOption.type = addSeriesOption.chartType = chartType
210
+ return addSeriesOption
211
+ }
212
+
213
+ private _selectTab(index: number) {
214
+ this.configurer.setCurrentSeriesIndex(index)
215
+ this.requestUpdate()
216
+ }
217
+
218
+ private _removeSeries(index: number) {
219
+ this.configurer.removeSeries(index)
220
+ this.requestUpdate()
221
+ }
222
+
223
+ private _addSeries() {
224
+ const configurer = this.configurer
225
+
226
+ if (!configurer.config.data.datasets) {
227
+ return
228
+ }
229
+
230
+ const lastSeriesIndex = configurer.config.data.datasets.length
231
+ const chartType = configurer.series.type || configurer.config.type
232
+ const color = configurer.datasets[lastSeriesIndex - 1]?.backgroundColor
233
+ const lastSeriesColor = new TinyColor(Array.isArray(color) ? color[0] : color)
234
+
235
+ const seriesModel = this._getSeriesModel({
236
+ chartType: chartType!,
237
+ datasetsLength: lastSeriesIndex,
238
+ lastSeriesColor
239
+ })
240
+
241
+ this.configurer.addSeries(seriesModel)
242
+ this.requestUpdate()
243
+ }
244
+
245
+ private _onWheelScroll(event: WheelEvent) {
246
+ const tabContainer = this.seriesTabs
247
+ if (tabContainer) {
248
+ event.preventDefault()
249
+
250
+ tabContainer.scrollLeft += event.deltaY
251
+
252
+ this._onTabScroll()
253
+ }
254
+ }
255
+
256
+ private _scrollLeft() {
257
+ this._scrollTabContainer(-1)
258
+ }
259
+
260
+ private _scrollRight() {
261
+ this._scrollTabContainer(1)
262
+ }
263
+
264
+ private _scrollTabContainer(direction: number) {
265
+ const tabContainer = this.renderRoot!.querySelector<HTMLElement>('#series-tabs')
266
+ if (tabContainer) {
267
+ tabContainer.style.scrollBehavior = 'smooth'
268
+ tabContainer.scrollLeft += direction * tabContainer.clientWidth
269
+ }
270
+
271
+ setTimeout(() => {
272
+ tabContainer!.style.scrollBehavior = 'auto'
273
+ this._onTabScroll()
274
+ }, 300)
275
+ }
276
+
277
+ private _onTabScroll() {
278
+ let tabContainer: HTMLElement | null | undefined
279
+ let tabNavLeftButton: MdIcon
280
+ let tabNavRightButton: MdIcon
281
+
282
+ tabContainer = this.seriesTabs
283
+ tabNavLeftButton = this.seriesTabNavLeftButton
284
+ tabNavRightButton = this.seriesTabNavRightButton
285
+
286
+ if (!tabContainer) {
287
+ return
288
+ }
289
+
290
+ if (tabContainer.clientWidth == tabContainer.scrollWidth) {
291
+ tabNavLeftButton.setAttribute('disabled', '')
292
+ tabNavRightButton.setAttribute('disabled', '')
293
+ } else if (tabContainer.scrollLeft <= 0) {
294
+ tabNavLeftButton.setAttribute('disabled', '')
295
+ tabNavRightButton.removeAttribute('disabled')
296
+ } else if (tabContainer.scrollLeft + tabContainer.clientWidth >= tabContainer.scrollWidth) {
297
+ tabNavLeftButton.removeAttribute('disabled')
298
+ tabNavRightButton.setAttribute('disabled', '')
299
+ } else {
300
+ tabNavLeftButton.removeAttribute('disabled')
301
+ tabNavRightButton.removeAttribute('disabled')
302
+ }
303
+ }
304
+ }
@@ -95,10 +95,14 @@ class OxSciChart extends LitElement {
95
95
  const newData = this.dataSet
96
96
 
97
97
  newData.forEach((data, index) => {
98
- const xValues = data.map(d => d.xValue)
99
- const yValues = data.map(d => d.yValue || 0)
98
+ const filteredData = data.filter(d => typeof d.yValue === 'number')
100
99
 
101
- this.dataSeries[index].appendRange(xValues, yValues)
100
+ if (filteredData.length > 0) {
101
+ this.dataSeries[index].appendRange(
102
+ filteredData.map(d => d.xValue),
103
+ filteredData.map(d => d.yValue)
104
+ )
105
+ }
102
106
  })
103
107
 
104
108
  this.chart?.sciChartSurface.zoomExtents()
@@ -19,6 +19,19 @@ function getLocalTimeOffset() {
19
19
  return now.getTimezoneOffset() * -60
20
20
  }
21
21
 
22
+ function calculatePrecision(stepSize: number = 1) {
23
+ // stepSize를 문자열로 변환한 다음, 소수점 이후 자릿수를 계산
24
+ const stepSizeString = stepSize.toString()
25
+
26
+ // 소수점이 있는 경우, 소수점 이후 자릿수 계산
27
+ if (stepSizeString.indexOf('.') !== -1) {
28
+ return stepSizeString.split('.')[1].length
29
+ }
30
+
31
+ // 소수점이 없는 경우, precision은 0
32
+ return 0
33
+ }
34
+
22
35
  export async function buildSciChart(
23
36
  config: OperatoChart.ChartConfig | undefined | null,
24
37
  container: any,
@@ -55,6 +68,7 @@ export async function buildSciChart(
55
68
  ZoomExtentsModifier,
56
69
  RolloverModifier,
57
70
  SmartDateLabelProvider,
71
+ NumericLabelProvider,
58
72
  EllipsePointMarker,
59
73
  SquarePointMarker,
60
74
  TrianglePointMarker,
@@ -135,13 +149,12 @@ export async function buildSciChart(
135
149
  } = ticks || {}
136
150
 
137
151
  const labelProvider = new SmartDateLabelProvider({
138
- labelFormat: ENumericFormat.Date_HHMMSS,
139
152
  showWiderDateOnFirstLabel: true,
140
153
  showYearOnWiderDate: true,
141
154
  dateOffset: getLocalTimeOffset()
142
155
  })
143
156
 
144
- labelProvider.cursorNumericFormat = ENumericFormat.Date_DDMMHHMM
157
+ labelProvider.cursorNumericFormat = ENumericFormat.Date_MMHHSS
145
158
 
146
159
  const xAxis = new DateTimeNumericAxis(wasmContext, {
147
160
  axisTitle,
@@ -169,7 +182,15 @@ export async function buildSciChart(
169
182
  // Y 축 설정
170
183
  ;(multiAxis ? yAxes : [yAxes[0]]).forEach((axis, index) => {
171
184
  const { axisTitle, ticks } = axis
172
- const { autoMax, autoMin, min, max, stepSize, beginAtZero } = ticks || {}
185
+ const { autoMax, autoMin, min, max, stepSize, beginAtZero, display } = ticks || {}
186
+
187
+ const labelProvider = display ? new NumericLabelProvider() : undefined
188
+
189
+ if (labelProvider) {
190
+ labelProvider.numericFormat = ENumericFormat.Decimal
191
+ labelProvider.precision = calculatePrecision(stepSize || 0.1)
192
+ labelProvider.cursorNumericFormat = ENumericFormat.NoFormat
193
+ }
173
194
 
174
195
  const yAxis = new NumericAxis(wasmContext, {
175
196
  id: index == 0 ? undefined : `yAxis${index}`,
@@ -177,9 +198,11 @@ export async function buildSciChart(
177
198
  autoRange: autoMin || autoMax ? EAutoRange.Always : undefined,
178
199
  axisAlignment: index === 0 ? EAxisAlignment.Left : EAxisAlignment.Right,
179
200
  visibleRange: min !== undefined && max !== undefined ? new NumberRange(min, max) : undefined,
180
- majorDelta: stepSize,
201
+ majorDelta: stepSize || 0.1,
202
+ minorDelta: (stepSize || 1) * 0.1,
181
203
  growBy: beginAtZero ? new NumberRange(0.1, 0.1) : undefined,
182
204
  labelStyle: {
205
+ display: !!display,
183
206
  fontFamily,
184
207
  fontSize,
185
208
  color: fontColor
@@ -188,7 +211,8 @@ export async function buildSciChart(
188
211
  fontFamily,
189
212
  fontSize,
190
213
  color: fontColor
191
- }
214
+ },
215
+ labelProvider
192
216
  })
193
217
 
194
218
  sciChartSurface.yAxes.add(yAxis)
@@ -321,7 +345,7 @@ export async function buildSciChart(
321
345
  if (tooltip) {
322
346
  const rolloverModifier = new RolloverModifier({
323
347
  showTooltip: true,
324
- showAxisLabels: true,
348
+ showAxisLabel: false /* 한글의 크기를 잘 계산하지 못하므로 false */,
325
349
  tooltipColor: 'white',
326
350
  tooltipBackgroundColor: 'rgba(0, 0, 0, 0.7)',
327
351
  rollOverDataSeries: dataSeries
package/stories/common.ts CHANGED
@@ -56,7 +56,10 @@ export function getDefaultAxisOptions(): OperatoChart.AxisOptions {
56
56
  // { timestamp: 2016, count: 28, average: 90 }
57
57
  // ]
58
58
  // 랜덤한 범위의 숫자를 생성하는 함수
59
- function getRandomInRange(min: number, max: number) {
59
+ function getRandomInRange(i: number, min: number, max: number) {
60
+ if (!(i % 11)) {
61
+ return null
62
+ }
60
63
  return Math.floor(Math.random() * (max - min + 1)) + min
61
64
  }
62
65
 
@@ -67,8 +70,8 @@ function generateRandomData(count: number) {
67
70
 
68
71
  for (let i = 0; i < count; i++) {
69
72
  const timestamp = startTimestamp + i * 360 * 30 * 1000 // 3초씩 증가하는 타임스탬프 설정
70
- const randomCount = getRandomInRange(5, 35) // count 값을 5에서 35 사이로 랜덤 생성
71
- const randomAverage = getRandomInRange(50, 150) // average 값을 50에서 150 사이로 랜덤 생성
73
+ const randomCount = getRandomInRange(i, 5, 35) // count 값을 5에서 35 사이로 랜덤 생성
74
+ const randomAverage = getRandomInRange(i, 50, 150) // average 값을 50에서 150 사이로 랜덤 생성
72
75
 
73
76
  randomData.push({
74
77
  timestamp: timestamp,
@@ -121,14 +121,14 @@ WithData.args = {
121
121
  datasets: [
122
122
  {
123
123
  dataKey: 'count',
124
- label: 'Line Series count',
124
+ label: '라인 시리즈 카운트',
125
125
  type: 'line',
126
126
  color: 'rgba(255, 99, 132, 1)',
127
127
  borderWidth: 1
128
128
  },
129
129
  {
130
130
  dataKey: 'average',
131
- label: 'Line Series average',
131
+ label: '라인 시리즈 평균값',
132
132
  type: 'line',
133
133
  color: 'rgba(54, 162, 235, 1)',
134
134
  fill: false,