@operato/chart 7.1.31 → 7.1.33
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.
- package/CHANGELOG.md +21 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/.editorconfig +0 -29
- package/.storybook/main.js +0 -3
- package/.storybook/preview.js +0 -52
- package/.storybook/server.mjs +0 -8
- package/demo/index.html +0 -33
- package/src/chartjs/config-converter.ts +0 -341
- package/src/chartjs/ox-chart-js.ts +0 -207
- package/src/editors/configurer.ts +0 -336
- package/src/editors/index.ts +0 -8
- package/src/editors/input-chart-abstract.ts +0 -202
- package/src/editors/input-chart-styles.ts +0 -206
- package/src/editors/ox-input-chart-hbar.ts +0 -157
- package/src/editors/ox-input-chart-mixed.ts +0 -241
- package/src/editors/ox-input-chart-pie.ts +0 -69
- package/src/editors/ox-input-chart-radar.ts +0 -56
- package/src/editors/ox-input-chart-timeseries.ts +0 -279
- package/src/editors/ox-property-editor-chart.ts +0 -72
- package/src/editors/templates/annotations.ts +0 -295
- package/src/editors/templates/display-value.ts +0 -111
- package/src/editors/templates/series.ts +0 -316
- package/src/index.ts +0 -0
- package/src/progress/ox-progress-circle.ts +0 -133
- package/src/scichart/ox-scichart.ts +0 -151
- package/src/scichart/scichart-builder.ts +0 -490
- package/stories/common.ts +0 -87
- package/stories/ox-input-chart-bar.stories.ts +0 -188
- package/stories/ox-input-chart-doughnut.stories.ts +0 -130
- package/stories/ox-input-chart-hbar.stories.ts +0 -188
- package/stories/ox-input-chart-line.stories.ts +0 -198
- package/stories/ox-input-chart-pie.stories.ts +0 -130
- package/stories/ox-input-chart-polar-area.stories.ts +0 -130
- package/stories/ox-input-chart-radar.stories.ts +0 -141
- package/stories/ox-input-chart-timeseries.stories.ts +0 -268
- package/tsconfig.json +0 -25
- package/web-dev-server.config.mjs +0 -27
- package/web-test-runner.config.mjs +0 -41
- /package/{src → types}/global.d.ts +0 -0
- /package/{src → types}/types.d.ts +0 -0
package/.storybook/preview.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { i18next } from '@operato/i18n'
|
|
2
|
-
|
|
3
|
-
export const globalTypes = {
|
|
4
|
-
locale: {
|
|
5
|
-
name: 'Locale',
|
|
6
|
-
description: 'Internationalization locale',
|
|
7
|
-
toolbar: {
|
|
8
|
-
icon: 'globe',
|
|
9
|
-
items: [
|
|
10
|
-
{ value: 'en', right: '🇺🇸', title: 'English' },
|
|
11
|
-
{ value: 'ko', right: '🇰🇷', title: '한국어' },
|
|
12
|
-
{ value: 'zh', right: '🇨🇳', title: '中文' },
|
|
13
|
-
{ value: 'ja', right: '🇯🇵', title: '日本語' },
|
|
14
|
-
{ value: 'ms', right: '🇲🇾', title: 'Bahasa Melayu' }
|
|
15
|
-
],
|
|
16
|
-
showName: true
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
theme: {
|
|
20
|
-
name: 'Theme',
|
|
21
|
-
description: 'Global theme for components',
|
|
22
|
-
toolbar: {
|
|
23
|
-
icon: 'paintbrush',
|
|
24
|
-
items: [
|
|
25
|
-
{ value: 'light', title: 'Light' },
|
|
26
|
-
{ value: 'dark', title: 'Dark' }
|
|
27
|
-
],
|
|
28
|
-
showName: true
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export const decorators = [
|
|
34
|
-
(Story, context) => {
|
|
35
|
-
const { locale, theme } = context.globals
|
|
36
|
-
|
|
37
|
-
if (locale) {
|
|
38
|
-
i18next.changeLanguage(locale)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Set the theme class for the document
|
|
42
|
-
if (theme === 'dark') {
|
|
43
|
-
document.documentElement.classList.add('dark')
|
|
44
|
-
document.documentElement.classList.remove('light')
|
|
45
|
-
} else {
|
|
46
|
-
document.documentElement.classList.add('light')
|
|
47
|
-
document.documentElement.classList.remove('dark')
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return Story()
|
|
51
|
-
}
|
|
52
|
-
]
|
package/.storybook/server.mjs
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { storybookPlugin } from '@web/dev-server-storybook'
|
|
2
|
-
import baseConfig from '../web-dev-server.config.mjs'
|
|
3
|
-
|
|
4
|
-
export default /** @type {import('@web/dev-server').DevServerConfig} */ ({
|
|
5
|
-
...baseConfig,
|
|
6
|
-
open: '/',
|
|
7
|
-
plugins: [storybookPlugin({ type: 'web-components' }), ...baseConfig.plugins]
|
|
8
|
-
})
|
package/demo/index.html
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en-GB">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1" />
|
|
6
|
-
<style>
|
|
7
|
-
body {
|
|
8
|
-
background: #fafafa;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
a {
|
|
12
|
-
display: block;
|
|
13
|
-
}
|
|
14
|
-
</style>
|
|
15
|
-
<link href="/themes/app-theme.css" rel="stylesheet" />
|
|
16
|
-
<link href="/themes/input-theme.css" rel="stylesheet" />
|
|
17
|
-
<link
|
|
18
|
-
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL@20..48,100..700,0..1"
|
|
19
|
-
rel="stylesheet"
|
|
20
|
-
/>
|
|
21
|
-
<link
|
|
22
|
-
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL@20..48,100..700,0..1"
|
|
23
|
-
rel="stylesheet"
|
|
24
|
-
/>
|
|
25
|
-
<link
|
|
26
|
-
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp:opsz,wght,FILL@20..48,100..700,0..1"
|
|
27
|
-
rel="stylesheet"
|
|
28
|
-
/>
|
|
29
|
-
</head>
|
|
30
|
-
<body>
|
|
31
|
-
<a href="./index-select.html">select</a>
|
|
32
|
-
</body>
|
|
33
|
-
</html>
|
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
import { TinyColor } from '@ctrl/tinycolor'
|
|
2
|
-
import { format as formatText } from '@operato/utils/format.js'
|
|
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
|
-
|
|
12
|
-
export function convertConfigure(
|
|
13
|
-
config: OperatoChart.ChartConfig,
|
|
14
|
-
{ fontSize, fontFamily, fontColor }: { fontSize?: number; fontFamily?: string; fontColor?: string }
|
|
15
|
-
) {
|
|
16
|
-
if (!config) {
|
|
17
|
-
return
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const { type: chartType, options, data: fromData } = config
|
|
21
|
-
var {
|
|
22
|
-
theme,
|
|
23
|
-
animation,
|
|
24
|
-
tooltip,
|
|
25
|
-
stacked,
|
|
26
|
-
legend,
|
|
27
|
-
scales: fromScales,
|
|
28
|
-
xGridLine,
|
|
29
|
-
yGridLine,
|
|
30
|
-
y2ndGridLine,
|
|
31
|
-
multiAxis
|
|
32
|
-
} = options || {}
|
|
33
|
-
const { datasets = [] } = fromData || {}
|
|
34
|
-
|
|
35
|
-
if (theme === 'auto') {
|
|
36
|
-
theme = getThemeFromBrowser()
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const { xAxes = [], yAxes = [] } = fromScales || {}
|
|
40
|
-
const toScales = {} as any
|
|
41
|
-
|
|
42
|
-
chartType != 'pie' &&
|
|
43
|
-
xAxes.forEach((xAxis, index) => {
|
|
44
|
-
const { axisTitle, barSpacing, categorySpacing, barPercentage, ticks } = xAxis
|
|
45
|
-
const id = xAxes.length > 1 ? `x${index + 1}` : 'x'
|
|
46
|
-
|
|
47
|
-
toScales[id] = setupScale(
|
|
48
|
-
{
|
|
49
|
-
axis: 'x',
|
|
50
|
-
id,
|
|
51
|
-
position: 'bottom',
|
|
52
|
-
display: true,
|
|
53
|
-
title: {
|
|
54
|
-
display: !!axisTitle,
|
|
55
|
-
text: axisTitle
|
|
56
|
-
},
|
|
57
|
-
grid: {
|
|
58
|
-
display: xGridLine
|
|
59
|
-
},
|
|
60
|
-
beginAtZero: false,
|
|
61
|
-
ticks
|
|
62
|
-
},
|
|
63
|
-
{ fontSize, fontFamily, fontColor, theme }
|
|
64
|
-
)
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
chartType != 'pie' &&
|
|
68
|
-
yAxes.forEach((yAxis, index) => {
|
|
69
|
-
const { axisTitle, barSpacing, categorySpacing, barPercentage, ticks } = yAxis
|
|
70
|
-
const id = yAxes.length > 1 ? `right` : 'left'
|
|
71
|
-
|
|
72
|
-
toScales[id] = setupScale(
|
|
73
|
-
{
|
|
74
|
-
axis: 'y',
|
|
75
|
-
id,
|
|
76
|
-
position: id,
|
|
77
|
-
display: true,
|
|
78
|
-
title: {
|
|
79
|
-
display: !!axisTitle,
|
|
80
|
-
text: axisTitle
|
|
81
|
-
},
|
|
82
|
-
grid: {
|
|
83
|
-
display: index == 0 ? yGridLine : y2ndGridLine
|
|
84
|
-
},
|
|
85
|
-
ticks,
|
|
86
|
-
stacked: stacked ? true : false
|
|
87
|
-
},
|
|
88
|
-
{ fontSize, fontFamily, fontColor, theme }
|
|
89
|
-
)
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
// setup series configure
|
|
93
|
-
// for (let i in datasets) {
|
|
94
|
-
// let series = datasets[i]
|
|
95
|
-
//
|
|
96
|
-
// /*
|
|
97
|
-
// * TODO from chartjs 2.9, categoryPercentage, barPercentage properties move to dataset.
|
|
98
|
-
// * so need to move related properties - categorySpacing, barSpacing should be moved to series.
|
|
99
|
-
// */
|
|
100
|
-
// if (chartType == 'bar') {
|
|
101
|
-
// let categorySpacing = (xAxes && xAxes[0].categorySpacing) || 0
|
|
102
|
-
// let barSpacing = (xAxes && xAxes[0].barSpacing) || 0
|
|
103
|
-
|
|
104
|
-
// series.categoryPercentage = 1 - categorySpacing || 1
|
|
105
|
-
// series.barPercentage = 1 - barSpacing || 0.8
|
|
106
|
-
// } else if (chartType == 'horizontalBar') {
|
|
107
|
-
// let categorySpacing = (yAxes && yAxes[0].categorySpacing) || 0
|
|
108
|
-
// let barSpacing = (yAxes && yAxes[0].barSpacing) || 0
|
|
109
|
-
|
|
110
|
-
// series.categoryPercentage = 1 - categorySpacing || 1
|
|
111
|
-
// series.barPercentage = 1 - barSpacing || 0.8
|
|
112
|
-
// }
|
|
113
|
-
// }
|
|
114
|
-
|
|
115
|
-
const converted = {
|
|
116
|
-
type: chartType == 'horizontalBar' ? 'bar' : chartType,
|
|
117
|
-
options: {
|
|
118
|
-
animation,
|
|
119
|
-
stacked,
|
|
120
|
-
maintainAspectRatio: false,
|
|
121
|
-
// parsing: false /* recommendations for performance */,
|
|
122
|
-
// normalized: true /* recommendations for performance */,
|
|
123
|
-
plugins: {
|
|
124
|
-
legend: setupLegend(legend || {}, { fontSize, fontFamily, fontColor, theme }),
|
|
125
|
-
tooltip: setupTooltip(
|
|
126
|
-
{
|
|
127
|
-
enabled: tooltip,
|
|
128
|
-
mode: 'interpolate',
|
|
129
|
-
intersect: false
|
|
130
|
-
},
|
|
131
|
-
{ fontSize, fontFamily }
|
|
132
|
-
),
|
|
133
|
-
title: {
|
|
134
|
-
/* new option candidate - subtitle */
|
|
135
|
-
display: false,
|
|
136
|
-
text: 'Custom Chart Title'
|
|
137
|
-
},
|
|
138
|
-
subtitle: {
|
|
139
|
-
/* new option candidate - subtitle */
|
|
140
|
-
display: false,
|
|
141
|
-
text: 'Custom Chart Subtitle'
|
|
142
|
-
},
|
|
143
|
-
crosshair: {
|
|
144
|
-
line: {
|
|
145
|
-
color: '#F66', // crosshair line color
|
|
146
|
-
width: 1 // crosshair line width
|
|
147
|
-
},
|
|
148
|
-
sync: {
|
|
149
|
-
enabled: true, // enable trace line syncing with other charts
|
|
150
|
-
group: 1, // chart group
|
|
151
|
-
suppressTooltips: false // suppress tooltips when showing a synced tracer
|
|
152
|
-
},
|
|
153
|
-
zoom: {
|
|
154
|
-
enabled: true, // enable zooming
|
|
155
|
-
zoomboxBackgroundColor: 'rgba(66,133,244,0.2)', // background color of zoom box
|
|
156
|
-
zoomboxBorderColor: '#48F', // border color of zoom box
|
|
157
|
-
zoomButtonText: 'Reset Zoom', // reset zoom button text
|
|
158
|
-
zoomButtonClass: 'reset-zoom' // reset zoom button class
|
|
159
|
-
},
|
|
160
|
-
callbacks: {
|
|
161
|
-
beforeZoom: () =>
|
|
162
|
-
function (start: number, end: number) {
|
|
163
|
-
// called before zoom, return false to prevent zoom
|
|
164
|
-
return true
|
|
165
|
-
},
|
|
166
|
-
afterZoom: () =>
|
|
167
|
-
function (start: number, end: number) {
|
|
168
|
-
// called after zoom
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
},
|
|
173
|
-
scales: toScales,
|
|
174
|
-
indexAxis: chartType == 'horizontalBar' ? 'y' : chartType != 'pie' ? 'x' : ''
|
|
175
|
-
},
|
|
176
|
-
data: {
|
|
177
|
-
datasets: datasets.map((dataset, index) => {
|
|
178
|
-
return setupSeries(chartType!, {
|
|
179
|
-
...dataset,
|
|
180
|
-
stack:
|
|
181
|
-
chartType == 'pie' || chartType == 'doughnut'
|
|
182
|
-
? undefined
|
|
183
|
-
: stacked && !dataset.stack
|
|
184
|
-
? '__all__'
|
|
185
|
-
: `__${index}__`,
|
|
186
|
-
type: dataset.type == 'horizontalBar' ? 'bar' : dataset.type
|
|
187
|
-
})
|
|
188
|
-
})
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
console.error('converted', config, converted)
|
|
193
|
-
return converted
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function setupLegend(
|
|
197
|
-
legend: any,
|
|
198
|
-
{
|
|
199
|
-
fontSize,
|
|
200
|
-
fontFamily,
|
|
201
|
-
fontColor,
|
|
202
|
-
theme
|
|
203
|
-
}: { fontSize?: number; fontFamily?: string; fontColor?: string; theme?: 'light' | 'dark' | 'auto' }
|
|
204
|
-
) {
|
|
205
|
-
legend.labels = legend.labels ? legend.labels : {}
|
|
206
|
-
|
|
207
|
-
if (fontSize) {
|
|
208
|
-
legend.labels.fontSize = fontSize
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (fontFamily) {
|
|
212
|
-
legend.labels.fontFamily = fontFamily
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
var baseColor = getBaseColorFromTheme(theme)
|
|
216
|
-
|
|
217
|
-
legend.labels = legend.labels ? legend.labels : {}
|
|
218
|
-
legend.labels.fontColor = fontColor ? fontColor : baseColor.clone().setAlpha(0.5).toString()
|
|
219
|
-
// legend.labels.usePointStyle = true
|
|
220
|
-
|
|
221
|
-
return legend
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function setupTooltip(
|
|
225
|
-
tooltip: any,
|
|
226
|
-
{
|
|
227
|
-
fontSize,
|
|
228
|
-
fontFamily,
|
|
229
|
-
fontColor,
|
|
230
|
-
theme
|
|
231
|
-
}: { fontSize?: number; fontFamily?: string; fontColor?: string; theme?: 'light' | 'dark' }
|
|
232
|
-
) {
|
|
233
|
-
if (fontSize) {
|
|
234
|
-
tooltip.titleFontSize = tooltip.bodyFontSize = tooltip.footerFontSize = fontSize
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (fontFamily) {
|
|
238
|
-
tooltip.titleFontFamily = tooltip.bodyFontFamily = tooltip.footerFontFamily = fontFamily
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
tooltip.mode = 'index'
|
|
242
|
-
tooltip.intersect = false
|
|
243
|
-
tooltip.callbacks = {
|
|
244
|
-
...tooltip.callbacks,
|
|
245
|
-
label: function ({ dataset, label }: { dataset: any; label: any }) {
|
|
246
|
-
var prefix = dataset.valuePrefix || ''
|
|
247
|
-
var suffix = dataset.valueSuffix || ''
|
|
248
|
-
var format = dataset.valueFormat || ''
|
|
249
|
-
|
|
250
|
-
var stringValue = format ? formatText(format, Number(label)) : Number(label).toLocaleString()
|
|
251
|
-
|
|
252
|
-
return `${prefix + stringValue + suffix}`
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return tooltip
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function setupScale(
|
|
260
|
-
axis: any,
|
|
261
|
-
{
|
|
262
|
-
fontSize,
|
|
263
|
-
fontFamily,
|
|
264
|
-
fontColor,
|
|
265
|
-
theme
|
|
266
|
-
}: { fontSize?: number; fontFamily?: string; fontColor?: string; theme?: 'light' | 'dark' | 'auto' }
|
|
267
|
-
) {
|
|
268
|
-
axis.ticks = axis.ticks ? axis.ticks : {}
|
|
269
|
-
|
|
270
|
-
if (fontSize) {
|
|
271
|
-
axis.ticks.fontSize = fontSize
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (fontFamily) {
|
|
275
|
-
axis.ticks.fontFamily = fontFamily
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
axis.pointLabels = {
|
|
279
|
-
fontSize,
|
|
280
|
-
fontFamily
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/* TODO move min, max, autoMin, autoMax from ticks to axis */
|
|
284
|
-
const { min, max, autoMin, autoMax } = axis.ticks
|
|
285
|
-
|
|
286
|
-
axis.min = min
|
|
287
|
-
axis.max = max
|
|
288
|
-
|
|
289
|
-
if (autoMin) {
|
|
290
|
-
delete axis.min
|
|
291
|
-
}
|
|
292
|
-
if (autoMax) {
|
|
293
|
-
delete axis.max
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
var baseColor = getBaseColorFromTheme(theme)
|
|
297
|
-
|
|
298
|
-
axis.grid = axis.grid ? axis.grid : {}
|
|
299
|
-
if (axis.grid) {
|
|
300
|
-
axis.grid.tickColor = baseColor.clone().setAlpha(0.5).toString()
|
|
301
|
-
axis.grid.color = baseColor.clone().setAlpha(0.1).toString()
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
axis.ticks = axis.ticks ? axis.ticks : {}
|
|
305
|
-
axis.ticks.color = baseColor.clone().setAlpha(0.5).toString()
|
|
306
|
-
axis.ticks.textStrokeColor = fontColor ? fontColor : baseColor.clone().setAlpha(0.5).toString()
|
|
307
|
-
|
|
308
|
-
return axis
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function setupSeries(chartType: string, series: OperatoChart.Dataset) {
|
|
312
|
-
var type = series.type || chartType
|
|
313
|
-
|
|
314
|
-
// series.parsing = {
|
|
315
|
-
// yAxisKey: series.dataKey
|
|
316
|
-
// } /* 이 형식의 data는 잘 안되었음. */
|
|
317
|
-
|
|
318
|
-
var color = series.color ? series.color : series.backgroundColor
|
|
319
|
-
|
|
320
|
-
switch (type) {
|
|
321
|
-
case 'bar':
|
|
322
|
-
case 'horizontalBar':
|
|
323
|
-
series.borderColor = series.backgroundColor = color
|
|
324
|
-
break
|
|
325
|
-
|
|
326
|
-
case 'line':
|
|
327
|
-
case 'radar':
|
|
328
|
-
color = series.color ? series.color : series.borderColor
|
|
329
|
-
series.pointBackgroundColor = series.pointBorderColor = series.borderColor = series.backgroundColor = color
|
|
330
|
-
series.pointBorderWidth = (series.borderWidth as number) * 0.5
|
|
331
|
-
series.pointHoverRadius = series.pointRadius
|
|
332
|
-
if (series.fill == undefined) series.fill = false
|
|
333
|
-
break
|
|
334
|
-
|
|
335
|
-
default:
|
|
336
|
-
series.borderColor = series.backgroundColor = color
|
|
337
|
-
break
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
return series
|
|
341
|
-
}
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import ChartDataLabels from 'chartjs-plugin-datalabels'
|
|
2
|
-
|
|
3
|
-
import { LitElement, html, css } from 'lit'
|
|
4
|
-
import { property, query, state, customElement } from 'lit/decorators.js'
|
|
5
|
-
import { Chart, ChartConfiguration } from 'chart.js/auto'
|
|
6
|
-
import { format as formatText } from '@operato/utils/format.js'
|
|
7
|
-
import { convertConfigure } from './config-converter'
|
|
8
|
-
|
|
9
|
-
Chart.register(ChartDataLabels)
|
|
10
|
-
|
|
11
|
-
@customElement('ox-chart-js')
|
|
12
|
-
export class OxChartJs extends LitElement {
|
|
13
|
-
@property({ type: Object }) config: OperatoChart.ChartConfig | null = null
|
|
14
|
-
@property({ type: Array }) data: { [key: string]: any }[] = []
|
|
15
|
-
@property({ type: Number }) width!: number
|
|
16
|
-
@property({ type: Number }) height!: number
|
|
17
|
-
|
|
18
|
-
private chart: Chart | null = null
|
|
19
|
-
private chartjsConfig: ChartConfiguration | null = null
|
|
20
|
-
|
|
21
|
-
@query('canvas') canvas!: HTMLCanvasElement
|
|
22
|
-
|
|
23
|
-
static styles = css`
|
|
24
|
-
:host {
|
|
25
|
-
display: block;
|
|
26
|
-
|
|
27
|
-
background-color: var(--ox-chart-background-color, var(--md-sys-color-surface));
|
|
28
|
-
color: var(--ox-chart-color, var(--md-sys-color-on-surface));
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
canvas {
|
|
32
|
-
width: 100%;
|
|
33
|
-
height: 100%;
|
|
34
|
-
}
|
|
35
|
-
`
|
|
36
|
-
|
|
37
|
-
disconnectedCallback(): void {
|
|
38
|
-
super.disconnectedCallback()
|
|
39
|
-
if (this.chart) {
|
|
40
|
-
this.chart.destroy()
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
updated(changedProperties: Map<string | number | symbol, unknown>) {
|
|
45
|
-
var needUpdateData = false
|
|
46
|
-
|
|
47
|
-
if (changedProperties.has('width') || changedProperties.has('height')) {
|
|
48
|
-
this.updateChartSize()
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (changedProperties.has('config') && this.config) {
|
|
52
|
-
this.chartjsConfig = convertConfigure(this.config, {}) as any
|
|
53
|
-
|
|
54
|
-
this.attachPluginOptions(this.chartjsConfig!.options as any)
|
|
55
|
-
|
|
56
|
-
this.chart && this.chart.destroy()
|
|
57
|
-
this.chart = new Chart(this.canvas, this.chartjsConfig!)
|
|
58
|
-
|
|
59
|
-
needUpdateData = true
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (changedProperties.has('data')) {
|
|
63
|
-
needUpdateData = true
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (needUpdateData) {
|
|
67
|
-
this.updateData()
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
updateData() {
|
|
72
|
-
if (!this.chart) {
|
|
73
|
-
return
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
var data = this.data
|
|
77
|
-
|
|
78
|
-
if (this.data[0]?.hasOwnProperty('__field1')) {
|
|
79
|
-
/* DEPRECATED 이 케이스는 앞으로 지원하지 않는 것이 좋다. 하위 호환성 때문에 제공함. 사용빈도 낮음. */
|
|
80
|
-
data = toObjectArrayValue(this.data) as { [key: string]: any }[]
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const labelDataKey = this.config!.data.labelDataKey || ''
|
|
84
|
-
|
|
85
|
-
labelDataKey && (this.chart!.config.data!.labels = data.map((data: any) => data[labelDataKey]))
|
|
86
|
-
|
|
87
|
-
const datasets = this.chart!.config.data.datasets
|
|
88
|
-
for (let key in datasets) {
|
|
89
|
-
const dataset = datasets[Number(key)]
|
|
90
|
-
const dataKey = (dataset as any).dataKey
|
|
91
|
-
dataKey && (dataset.data = data.map(d => d[dataKey]) as any)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
this.chart.update()
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
updateChartSize() {
|
|
98
|
-
const width = Math.floor(this.width)
|
|
99
|
-
const height = Math.floor(this.height)
|
|
100
|
-
|
|
101
|
-
this.canvas.style.width = `${width}px`
|
|
102
|
-
this.canvas.style.height = `${height}px`
|
|
103
|
-
|
|
104
|
-
const _ = () => {
|
|
105
|
-
if (this.canvas.offsetWidth == 0 || this.canvas.offsetHeight == 0) {
|
|
106
|
-
requestAnimationFrame(_)
|
|
107
|
-
} else {
|
|
108
|
-
/*
|
|
109
|
-
주의 : chart.resize() 내에서 pixel ratio를 감안해서, canvas 의 width, height를 설정하기때문에,
|
|
110
|
-
별도 처리가 필요하지 않다.
|
|
111
|
-
*/
|
|
112
|
-
this.chart!.resize()
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
requestAnimationFrame(_)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
render() {
|
|
120
|
-
return html`<canvas></canvas>`
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
attachPluginOptions(options: OperatoChart.ChartOptions) {
|
|
124
|
-
if (!options.plugins) {
|
|
125
|
-
options.plugins = {}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
this.attachDatalabelPluginOptions(options.plugins)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
attachDatalabelPluginOptions(pluginOptions: any) {
|
|
132
|
-
pluginOptions.datalabels = {
|
|
133
|
-
...pluginOptions.datalabels,
|
|
134
|
-
display: function (context: any) {
|
|
135
|
-
return !!context.dataset.displayValue
|
|
136
|
-
},
|
|
137
|
-
anchor: function (context: any) {
|
|
138
|
-
return context.dataset.dataLabelAnchor || 'center'
|
|
139
|
-
},
|
|
140
|
-
offset: function (context: any) {
|
|
141
|
-
return context.dataset.dataLabelOffset || 0
|
|
142
|
-
},
|
|
143
|
-
align: function (context: any) {
|
|
144
|
-
return context.dataset.dataLabelAnchor || 'center'
|
|
145
|
-
},
|
|
146
|
-
rotation: function (context: any) {
|
|
147
|
-
return context.dataset.dataLabelRotation || 0
|
|
148
|
-
},
|
|
149
|
-
color: function (context: any) {
|
|
150
|
-
return context.dataset?.defaultFontColor || '#000'
|
|
151
|
-
},
|
|
152
|
-
font: function (context: any) {
|
|
153
|
-
return {
|
|
154
|
-
size: context.dataset?.defaultFontSize,
|
|
155
|
-
family: context.chart.options?.defaultFontFamily
|
|
156
|
-
}
|
|
157
|
-
},
|
|
158
|
-
clamp: true,
|
|
159
|
-
formatter: function (value: string, context: any) {
|
|
160
|
-
var prefix = context.dataset.valuePrefix || ''
|
|
161
|
-
var suffix = context.dataset.valueSuffix || ''
|
|
162
|
-
var format = context.dataset.valueFormat || ''
|
|
163
|
-
|
|
164
|
-
if (value === undefined) {
|
|
165
|
-
return value
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
var stringValue = format ? formatText(format, Number(value)) : Number(value).toLocaleString()
|
|
169
|
-
return prefix + stringValue + suffix
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function toObjectArrayValue(array: any[]): any[] | null {
|
|
176
|
-
if (!array || array.length === 0) {
|
|
177
|
-
return null
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
let indexKeyMap: any = {}
|
|
181
|
-
let value = []
|
|
182
|
-
|
|
183
|
-
for (let key in array[0]) {
|
|
184
|
-
indexKeyMap[key] = array[0][key]
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
for (var i = 1; i < array.length; i++) {
|
|
188
|
-
let object: any = {}
|
|
189
|
-
let thisObject = array[i]
|
|
190
|
-
|
|
191
|
-
for (let key in indexKeyMap) {
|
|
192
|
-
let k = indexKeyMap[key]
|
|
193
|
-
let v = thisObject[key]
|
|
194
|
-
object[k] = v
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
value.push(object)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return value
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
declare global {
|
|
204
|
-
interface HTMLElementTagNameMap {
|
|
205
|
-
'ox-chart-js': OxChartJs
|
|
206
|
-
}
|
|
207
|
-
}
|