stz-chart-maker 1.0.1
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/dist/chart-toolbox.min.js +1 -0
- package/dist/index.d.ts +1022 -0
- package/dist/index.js +2179 -0
- package/dist/index.js.map +1 -0
- package/package.json +68 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ═════════════════════════════════════════════════════════════
|
|
3
|
+
* 📄 FILE : Logger.ts
|
|
4
|
+
* 📁 PACKAGE : chartjs-toolbox-
|
|
5
|
+
* 👤 AUTHOR : stz
|
|
6
|
+
* 🕒 CREATED : 25. 7. 18.
|
|
7
|
+
* ═════════════════════════════════════════════════════════════
|
|
8
|
+
* ═════════════════════════════════════════════════════════════
|
|
9
|
+
* 📝 DESCRIPTION
|
|
10
|
+
* -
|
|
11
|
+
* ═════════════════════════════════════════════════════════════
|
|
12
|
+
* ═════════════════════════════════════════════════════════════
|
|
13
|
+
* 🔄 CHANGE LOG
|
|
14
|
+
* - DATE : 2025/07/18 | Author : stz | 최초 생성
|
|
15
|
+
* ═════════════════════════════════════════════════════════════
|
|
16
|
+
*/
|
|
17
|
+
/** ■■■■■■■■■■■■■■■■ GLOBAL Logging contr ■■■■■■■■■■■■■■■■*/
|
|
18
|
+
const LoggerConfig = {
|
|
19
|
+
errorLogging: true,
|
|
20
|
+
debugLogging: true,
|
|
21
|
+
};
|
|
22
|
+
/** ■■■■■■■■■■■■■■■■ Apple System Default Color ■■■■■■■■■■■■■■■■*/
|
|
23
|
+
const CHART_COLOR = [
|
|
24
|
+
'#FF3B30', // System Red
|
|
25
|
+
'#FF9500', // System Orange
|
|
26
|
+
'#FFCC00', // System Yellow
|
|
27
|
+
'#34C759', // System Green
|
|
28
|
+
'#00C7BE', // System Mint
|
|
29
|
+
'#30B0C7', // System Teal
|
|
30
|
+
'#32ADE6', // System Cyan
|
|
31
|
+
'#007AFF', // System Blue
|
|
32
|
+
'#5856D6', // System Indigo
|
|
33
|
+
'#AF52DE', // System Purple
|
|
34
|
+
];
|
|
35
|
+
const originalConsoleLog = console.log;
|
|
36
|
+
console.log = (...args) => {
|
|
37
|
+
if (LoggerConfig.debugLogging) {
|
|
38
|
+
originalConsoleLog.apply(console, args);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
var ErrorMsg;
|
|
43
|
+
(function (ErrorMsg) {
|
|
44
|
+
// 공통 에러
|
|
45
|
+
ErrorMsg["CHART_TYPE_REQUIRED"] = "Chart type is required.";
|
|
46
|
+
ErrorMsg["INVALID_CHART_TYPE"] = "Invalid chart type. Supported types are bar, line, scatter, bubble, etc.";
|
|
47
|
+
ErrorMsg["DATASET_REQUIRED"] = "Datasets cannot be empty.";
|
|
48
|
+
ErrorMsg["NOT_SUPPORTED_OPTION"] = "It does not support that option.";
|
|
49
|
+
ErrorMsg["LABELS_REQUIRED"] = "Labels cannot be empty.";
|
|
50
|
+
ErrorMsg["OPTIONS_REQUIRED"] = "Options cannot be null or undefined.";
|
|
51
|
+
ErrorMsg["NOT_REGISTERED_CHART_TYPE"] = "This chart type is not registered. Please call ChartWrapper.register().";
|
|
52
|
+
// 데이터 관련 에러
|
|
53
|
+
ErrorMsg["EMPTY_REQUIRED_PROPERTY"] = "A required property is missing in the dataset or options.";
|
|
54
|
+
ErrorMsg["INVALID_DATA_STRUCTURE"] = "Invalid data structure. Please check the dataset or labels format.";
|
|
55
|
+
ErrorMsg["AXIS_KEY_REQUIRED"] = "xAxisKey or yAxisKey is required for parsing.";
|
|
56
|
+
ErrorMsg["DATA_LENGTH_MISMATCH"] = "Labels length does not match datasets data length.";
|
|
57
|
+
ErrorMsg["IMAGE_PROPERTY_MISSING"] = "Image property is missing in the dataset. Please provide a valid image URL.";
|
|
58
|
+
// 플러그인 관련 에러
|
|
59
|
+
ErrorMsg["PLUGIN_NOT_FOUND"] = "Plugin not found in options.plugins.";
|
|
60
|
+
ErrorMsg["PLUGIN_ALREADY_EXISTS"] = "Plugin already exists. Use setPlugin() to override.";
|
|
61
|
+
ErrorMsg["PLUGIN_REMOVE_FAILED"] = "Cannot remove a non-existing plugin.";
|
|
62
|
+
// Chart 인스턴스 관련 에러
|
|
63
|
+
ErrorMsg["CHART_INSTANCE_NOT_FOUND"] = "Chart instance is not initialized.";
|
|
64
|
+
ErrorMsg["CHART_ALREADY_INITIALIZED"] = "Chart instance is already initialized.";
|
|
65
|
+
// BarChart 전용 에러
|
|
66
|
+
ErrorMsg["INVALID_BAR_THICKNESS"] = "Bar thickness must be a positive number.";
|
|
67
|
+
ErrorMsg["INVALID_BAR_PERCENTAGE"] = "Bar percentage must be between 0 and 1.";
|
|
68
|
+
ErrorMsg["INVALID_CATEGORY_PERCENTAGE"] = "Category percentage must be between 0 and 1.";
|
|
69
|
+
// 기타
|
|
70
|
+
ErrorMsg["UNKNOWN_ERROR"] = "An unknown error has occurred.";
|
|
71
|
+
//스케일 관련 에러
|
|
72
|
+
ErrorMsg["INVALID_AXIS_KEY"] = "Invalid axis key provided. Please check the axis configuration.";
|
|
73
|
+
})(ErrorMsg || (ErrorMsg = {}));
|
|
74
|
+
var ErrorCode;
|
|
75
|
+
(function (ErrorCode) {
|
|
76
|
+
// 공통 (1200)
|
|
77
|
+
ErrorCode[ErrorCode["CHART_TYPE_REQUIRED"] = 1200] = "CHART_TYPE_REQUIRED";
|
|
78
|
+
ErrorCode[ErrorCode["INVALID_CHART_TYPE"] = 1201] = "INVALID_CHART_TYPE";
|
|
79
|
+
ErrorCode[ErrorCode["OPTIONS_REQUIRED"] = 1202] = "OPTIONS_REQUIRED";
|
|
80
|
+
ErrorCode[ErrorCode["NOT_REGISTERED_CHART_TYPE"] = 1203] = "NOT_REGISTERED_CHART_TYPE";
|
|
81
|
+
// 데이터 관련 (2200)
|
|
82
|
+
ErrorCode[ErrorCode["DATASET_REQUIRED"] = 2200] = "DATASET_REQUIRED";
|
|
83
|
+
ErrorCode[ErrorCode["LABELS_REQUIRED"] = 2211] = "LABELS_REQUIRED";
|
|
84
|
+
ErrorCode[ErrorCode["EMPTY_REQUIRED_PROPERTY"] = 2212] = "EMPTY_REQUIRED_PROPERTY";
|
|
85
|
+
ErrorCode[ErrorCode["INVALID_DATA_STRUCTURE"] = 2213] = "INVALID_DATA_STRUCTURE";
|
|
86
|
+
ErrorCode[ErrorCode["AXIS_KEY_REQUIRED"] = 2214] = "AXIS_KEY_REQUIRED";
|
|
87
|
+
ErrorCode[ErrorCode["DATA_LENGTH_MISMATCH"] = 2215] = "DATA_LENGTH_MISMATCH";
|
|
88
|
+
ErrorCode[ErrorCode["NOT_SUPPORTED_OPTION"] = 2216] = "NOT_SUPPORTED_OPTION";
|
|
89
|
+
ErrorCode[ErrorCode["IMAGE_PROPERTY_MISSING"] = 2217] = "IMAGE_PROPERTY_MISSING";
|
|
90
|
+
// 플러그인 관련 (3220)
|
|
91
|
+
ErrorCode[ErrorCode["PLUGIN_NOT_FOUND"] = 3220] = "PLUGIN_NOT_FOUND";
|
|
92
|
+
ErrorCode[ErrorCode["PLUGIN_ALREADY_EXISTS"] = 3221] = "PLUGIN_ALREADY_EXISTS";
|
|
93
|
+
ErrorCode[ErrorCode["PLUGIN_REMOVE_FAILED"] = 3222] = "PLUGIN_REMOVE_FAILED";
|
|
94
|
+
// 인스턴스 관련 (4230)
|
|
95
|
+
ErrorCode[ErrorCode["CHART_INSTANCE_NOT_FOUND"] = 4230] = "CHART_INSTANCE_NOT_FOUND";
|
|
96
|
+
ErrorCode[ErrorCode["CHART_ALREADY_INITIALIZED"] = 4231] = "CHART_ALREADY_INITIALIZED";
|
|
97
|
+
// 기타 (5240)
|
|
98
|
+
ErrorCode[ErrorCode["INVALID_BAR_THICKNESS"] = 5240] = "INVALID_BAR_THICKNESS";
|
|
99
|
+
ErrorCode[ErrorCode["INVALID_BAR_PERCENTAGE"] = 5241] = "INVALID_BAR_PERCENTAGE";
|
|
100
|
+
ErrorCode[ErrorCode["INVALID_CATEGORY_PERCENTAGE"] = 5242] = "INVALID_CATEGORY_PERCENTAGE";
|
|
101
|
+
ErrorCode[ErrorCode["UNKNOWN_ERROR"] = 5249] = "UNKNOWN_ERROR";
|
|
102
|
+
// 스케일 관련 (6250)
|
|
103
|
+
ErrorCode[ErrorCode["INVALID_AXIS_KEY"] = 6250] = "INVALID_AXIS_KEY";
|
|
104
|
+
})(ErrorCode || (ErrorCode = {}));
|
|
105
|
+
const ErrorMessage = {
|
|
106
|
+
// 공통
|
|
107
|
+
[ErrorCode.CHART_TYPE_REQUIRED]: ErrorMsg.CHART_TYPE_REQUIRED,
|
|
108
|
+
[ErrorCode.INVALID_CHART_TYPE]: ErrorMsg.INVALID_CHART_TYPE,
|
|
109
|
+
[ErrorCode.OPTIONS_REQUIRED]: ErrorMsg.OPTIONS_REQUIRED,
|
|
110
|
+
[ErrorCode.NOT_REGISTERED_CHART_TYPE]: ErrorMsg.NOT_REGISTERED_CHART_TYPE,
|
|
111
|
+
// 데이터 관련
|
|
112
|
+
[ErrorCode.DATASET_REQUIRED]: ErrorMsg.DATASET_REQUIRED,
|
|
113
|
+
[ErrorCode.LABELS_REQUIRED]: ErrorMsg.LABELS_REQUIRED,
|
|
114
|
+
[ErrorCode.EMPTY_REQUIRED_PROPERTY]: ErrorMsg.EMPTY_REQUIRED_PROPERTY,
|
|
115
|
+
[ErrorCode.INVALID_DATA_STRUCTURE]: ErrorMsg.INVALID_DATA_STRUCTURE,
|
|
116
|
+
[ErrorCode.AXIS_KEY_REQUIRED]: ErrorMsg.AXIS_KEY_REQUIRED,
|
|
117
|
+
[ErrorCode.DATA_LENGTH_MISMATCH]: ErrorMsg.DATA_LENGTH_MISMATCH,
|
|
118
|
+
[ErrorCode.NOT_SUPPORTED_OPTION]: ErrorMsg.NOT_SUPPORTED_OPTION,
|
|
119
|
+
// 플러그인 관련
|
|
120
|
+
[ErrorCode.PLUGIN_NOT_FOUND]: ErrorMsg.PLUGIN_NOT_FOUND,
|
|
121
|
+
[ErrorCode.PLUGIN_ALREADY_EXISTS]: ErrorMsg.PLUGIN_ALREADY_EXISTS,
|
|
122
|
+
[ErrorCode.PLUGIN_REMOVE_FAILED]: ErrorMsg.PLUGIN_REMOVE_FAILED,
|
|
123
|
+
// 인스턴스 관련
|
|
124
|
+
[ErrorCode.CHART_INSTANCE_NOT_FOUND]: ErrorMsg.CHART_INSTANCE_NOT_FOUND,
|
|
125
|
+
[ErrorCode.CHART_ALREADY_INITIALIZED]: ErrorMsg.CHART_ALREADY_INITIALIZED,
|
|
126
|
+
// 기타
|
|
127
|
+
[ErrorCode.INVALID_BAR_THICKNESS]: ErrorMsg.INVALID_BAR_THICKNESS,
|
|
128
|
+
[ErrorCode.INVALID_BAR_PERCENTAGE]: ErrorMsg.INVALID_BAR_PERCENTAGE,
|
|
129
|
+
[ErrorCode.INVALID_CATEGORY_PERCENTAGE]: ErrorMsg.INVALID_CATEGORY_PERCENTAGE,
|
|
130
|
+
[ErrorCode.UNKNOWN_ERROR]: ErrorMsg.UNKNOWN_ERROR,
|
|
131
|
+
[ErrorCode.IMAGE_PROPERTY_MISSING]: ErrorMsg.IMAGE_PROPERTY_MISSING,
|
|
132
|
+
// 스케일 관련
|
|
133
|
+
[ErrorCode.INVALID_AXIS_KEY]: ErrorMsg.INVALID_AXIS_KEY,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
class CustomError extends Error {
|
|
137
|
+
code;
|
|
138
|
+
detail;
|
|
139
|
+
constructor(code, detail) {
|
|
140
|
+
const ErrMsg = detail ? `${ErrorMessage[code]} - ${detail}` : ErrorMessage[code];
|
|
141
|
+
super(ErrMsg);
|
|
142
|
+
this.code = code;
|
|
143
|
+
this.detail = detail;
|
|
144
|
+
this.name = 'CustomError';
|
|
145
|
+
if (LoggerConfig.errorLogging) {
|
|
146
|
+
const codeStyle = 'color: green; font-weight: bold;';
|
|
147
|
+
const messageStyle = 'color: orange;';
|
|
148
|
+
console.error(`%c[${code}] %c${ErrMsg}`, codeStyle, messageStyle);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
class ChartWrapper {
|
|
154
|
+
type;
|
|
155
|
+
labels;
|
|
156
|
+
datasets;
|
|
157
|
+
options;
|
|
158
|
+
plugins;
|
|
159
|
+
static registry = new Map();
|
|
160
|
+
constructor(type, labels, datasets, options, plugins) {
|
|
161
|
+
this.type = type;
|
|
162
|
+
this.labels = labels;
|
|
163
|
+
this.datasets = datasets;
|
|
164
|
+
this.options = options;
|
|
165
|
+
this.plugins = plugins;
|
|
166
|
+
}
|
|
167
|
+
//TODO : factory pattern dynamic method for maintenance sex sex big ass doggy styleQWA?. MN
|
|
168
|
+
static create(type, labels, datasets, options, plugins) {
|
|
169
|
+
const WrapperClass = this.registry.get(type);
|
|
170
|
+
if (!WrapperClass) {
|
|
171
|
+
throw new CustomError(ErrorCode.NOT_REGISTERED_CHART_TYPE, `type=${type}`);
|
|
172
|
+
}
|
|
173
|
+
return new WrapperClass(type, labels, datasets, options, plugins);
|
|
174
|
+
}
|
|
175
|
+
static register(type, wrapperClass) {
|
|
176
|
+
this.registry.set(type, wrapperClass);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* 플러그인을 추가합니다.
|
|
180
|
+
* @param plugin
|
|
181
|
+
* @param replaceIfExists
|
|
182
|
+
*
|
|
183
|
+
*/
|
|
184
|
+
setPlugin(plugin, replaceIfExists = true) {
|
|
185
|
+
if (!this.plugins) {
|
|
186
|
+
this.plugins = [];
|
|
187
|
+
}
|
|
188
|
+
if (plugin.id && replaceIfExists) {
|
|
189
|
+
const existingIndex = this.plugins.findIndex((p) => p.id === plugin.id);
|
|
190
|
+
if (existingIndex !== -1) {
|
|
191
|
+
this.plugins[existingIndex] = plugin;
|
|
192
|
+
return this;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
this.plugins.push(plugin);
|
|
196
|
+
return this;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* 차트 설정 객체를 생성합니다.
|
|
200
|
+
* @param {string} id
|
|
201
|
+
* @returns {ChartConfig}
|
|
202
|
+
* @Description 레거시 지원용
|
|
203
|
+
*/
|
|
204
|
+
makeConfig(id) {
|
|
205
|
+
return this.build(id);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* 플러그인을 제거합니다.
|
|
209
|
+
* @param pluginId
|
|
210
|
+
*/
|
|
211
|
+
removePlugin(pluginId) {
|
|
212
|
+
if (!this.plugins || !Array.isArray(this.plugins)) {
|
|
213
|
+
return this;
|
|
214
|
+
}
|
|
215
|
+
this.plugins.length;
|
|
216
|
+
this.plugins = this.plugins.filter((p) => p.id !== pluginId);
|
|
217
|
+
return this;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* 플러그인이 존재하는지 확인합니다.
|
|
221
|
+
* @param pluginId
|
|
222
|
+
*/
|
|
223
|
+
hasPlugin(pluginId) {
|
|
224
|
+
if (!this.plugins || !Array.isArray(this.plugins)) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
return this.plugins.some((p) => p.id === pluginId);
|
|
228
|
+
}
|
|
229
|
+
build(id) {
|
|
230
|
+
this.normalize();
|
|
231
|
+
const chartId = id || `${this.type}_${Math.random()}`;
|
|
232
|
+
const config = {
|
|
233
|
+
_chartId: chartId,
|
|
234
|
+
type: this.type,
|
|
235
|
+
data: { labels: this.labels, datasets: this.datasets },
|
|
236
|
+
options: { ...this.options, _chartId: chartId },
|
|
237
|
+
plugins: this.plugins,
|
|
238
|
+
};
|
|
239
|
+
return this.configAop(config);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function a0_0x4af2(_0x4297ef,_0x3c4d84){const _0x5eed3d=a0_0x5eed();return a0_0x4af2=function(_0x4af297,_0x1d0aaf){_0x4af297=_0x4af297-0x195;let _0x2ac4f5=_0x5eed3d[_0x4af297];if(a0_0x4af2['cnrCGs']===undefined){var _0x2f632c=function(_0x4e29ba){const _0x3cc3f0='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x19591f='',_0x2d1ac1='';for(let _0x3bddd5=0x0,_0x2b03c7,_0x34d1c4,_0xa8194c=0x0;_0x34d1c4=_0x4e29ba['charAt'](_0xa8194c++);~_0x34d1c4&&(_0x2b03c7=_0x3bddd5%0x4?_0x2b03c7*0x40+_0x34d1c4:_0x34d1c4,_0x3bddd5++%0x4)?_0x19591f+=String['fromCharCode'](0xff&_0x2b03c7>>(-2*_0x3bddd5&0x6)):0x0){_0x34d1c4=_0x3cc3f0['indexOf'](_0x34d1c4);}for(let _0x2672da=0x0,_0x5a9dfe=_0x19591f['length'];_0x2672da<_0x5a9dfe;_0x2672da++){_0x2d1ac1+='%'+('00'+_0x19591f['charCodeAt'](_0x2672da)['toString'](0x10))['slice'](-2);}return decodeURIComponent(_0x2d1ac1);};a0_0x4af2['sZUXQc']=_0x2f632c,_0x4297ef=arguments,a0_0x4af2['cnrCGs']=!![];}const _0x4ea7a7=_0x5eed3d[0x0],_0x399fa1=_0x4af297+_0x4ea7a7,_0x1caf89=_0x4297ef[_0x399fa1];return !_0x1caf89?(_0x2ac4f5=a0_0x4af2['sZUXQc'](_0x2ac4f5),_0x4297ef[_0x399fa1]=_0x2ac4f5):_0x2ac4f5=_0x1caf89,_0x2ac4f5;},a0_0x4af2(_0x4297ef,_0x3c4d84);}function a0_0x5eed(){const _0x4c3bf0=['rxPzCwq','BwfW','AuHMCLK','Dg9mB2nHBgvtDhjPBMC','mJy3odKXnLviDvvpyW','DMPiAKO','yxbWzw5Kq2HPBgq','zxjYB3i','Bg9N','rfLQquu','tNvSBa','cGRIOidIOidIOzxIOzxIOzxIOzxIOzxIOzxIOzxIOzxIOzxIOzxIOzxIOzxIOyxIOidIOPdIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOPxIOidIOiak4Qgh4Qgh4Qgh4Qgh4Qca4Qca4Qca4Qca4Qca4Qca4Qca4Qca4Qca4Qkh4Qkh4Qkp4Qko4Qko4Qcg4Qca4Qca4Qca4Qca4Qca4Qca4Qca4Qca4Qca4Qca4QkO4QkQ4QkQ4QgQ4Qgc4Qca4Qca4Qca4Qca4Qca4Qca4Qca4Qca4Qca4Qca4Qca4Qca4QkQ4QkQ4QkQ4QcgcUkHH+kHH+kGGokGGokGGokGQokHQUkHQUkHQUkHJUkHRUkGGokGGokGGokIGokIH+kIH+kIH+kIH+kIH+kIL+kILEkGHokGGokGGokGGokHH+kJH+kIH+kIH+kIH+kIH+kIH+kIH+kIH+kIH+kIL+kILEkILEkILEkIHEkGGokGGokGGokGGokGGokIQUkIQUkIQUkHGWRIOyFIOyFIOydIOidIOidIOkJIOARIOBRIOBJIOBJIOBJIOBhIOO3IOOFIOOFIOOFIOQFIOQpIOipIOidIOQJIOQRIOidIOidIOidIOidIOyFIOyFIOyFIOAFIOzpIOzxIORxIOidIOidIOPZIOPZIOPZIOOZIOidIOidIOidIOidIOidIOQJIOQpIORpIORhIORhIOOuk4Qgo4Qgo4Qgo4Qgh4Qca4Qca4Qca4Qca4Qca4Qca4Qca4Qca4Qca4QgJ4QgJ4QgZ4QgX4QgX4Qgb4Qca4QkO4QkQ4Qca4Qca4Qca4Qca4Qgh4Qgh4Qgh4Qgh4Qca4Qca4Qca4Qg44Qg54Qg44Qg44Qca4Qca4Qca4Qca4Qca4QkO4Qk54Qk44Qg44QkC4QkC4QkC4QcgcUkIH+kIH+kIH+kIH+kIJ+kIJUkIH+kIJ+kIJUkIRUkIQUkGGokGGokGGokGGokHH+kHH+kHP+kHGEkGGokGUokHUokGGokGGokGGokGGokHH+kHH+kHH+kHH+kGGokGUokHUokHUokHUokGGokGGokGGokGGokGGokGOokHO+kHO+kHO+kHS+kHUokHSEkHSEkGGokGGaRIOOFIOOFIOidIOidIOidIOkJIOARIOARIOARIOARIOARIOidIOidIOidIOidIO4FIOOFIOQFIOihIOidIORJIORJIOidIOidIOidIOidIOyFIOyFIOyFIOyFIOidIORJIORJIOidIOidIOidIOidIOidIOidIOyFIOyFIOyFIOzFIOzxIOzxIOzxIO5xIOPxIOidIOiak4QkJ4QkJ4QkR4Qkk4Qca4Qca4Qca4Qca4Qca4Qca4Qca4Qca4Qga4QgD4QgC4QgC4QgC4QgC4Qcm4Qca4QkO4QkQ4Qca4Qca4Qca4Qca4Qgh4Qgh4Qgh4Qgh4Qca4QkW4QkX4Qcb4Qca4Qca4Qca4Qca4Qca4Qca4Qca4Qca4Qca4Qca4Qca4Qca4Qg44Qg44QgX4QgXcUkGGokGGokIH+kIH+kIH+kIJ+kIJUkIH+kIJ+kGRUkHUEkHUokHSokILEkILEkGTEkHSEkHUEkGKokGGokGQokHQUkHQUkHO+kHQ+kHQUkHQUkHQUkIJUkGHUkGGokIMokINokINokILEkINEkINokILEkINEkINokILEkINEkINokILEkINEkINokINokINokINokGRaRIOidIOidIOidIOidIOyFIOyFIOyFIOyFIOyFIOyFIO4FIOOFIOQFIOQpIOQpIOQVIOylIOidIOidIOidIOidIOidIOQRIOQRIOQRIOQRIOQRIOQRIOQpIOipIOidIOidIOidIORJIORJIORJIORJIORJIORJIORJIORJIORJIORJIORJIORJIORJIORJIORJIORJIOzekcG','B21PDa','BKD6u2q','yM9KEq','A0LUs1O','ChjVDg90ExbL','w29IAMvJDcbpyMPLy3rD','Dgv4DenVBNrLBNq','y2HPBgrYzw4','zxzLCNK','AxnpyMPLy3q','oty4nte1B0TWEvD6','y29UC3rYDwn0B3i','t2jQzwn0','CMvKDwnL','C2vQCe0','C2XPy2u','DgfIBgvFzgf0ys5JC3y','AgfZ','uK9JAxq','DujQAwO','DMfSDwvZ','u3rYAw5N','zNjVBq','mJm2odmWngXpufHsva','AxriyNe','zM9YrwfJAa','zLHzqvi','uvHmBgy','Dg9tDhjPBMC','quzxz04','ChvZAa','BgvUz3rO','yLHKBMi','C2v0','C3rYAw5N','z2v0vhLWzq','mtq1mgLMqxbHAW','y3jLyxrLt2jQzwn0vvjm','CMvTB3zLq2HPBgq','qxjYyxK','z2v0','mtu1mZK2nMLIDgXoBW','zg93BMXVywq','y2XPy2S','B1zLwMi','s1jZv2O','odKYmZiYmNzJAMTKCq','mtq1nKfTEhnYzG','Aw5JBhvKzxm','x19WCM90B19F','DNfRze0','nLzhDK1xva','BwvYz2u','mti4n0jmtKrxqG','AgfZt3DUuhjVCgvYDhK','mwfRseXsDG','C3vIC3rYAw5N','y2fSBa','CxvLCNLtzwXLy3rVCKfSBa','twP6DMW','Bhj6DeC','q1PmAvG','DgGSihrK','y2XVBMvezwvW','mJm3odrwELfADhK','AurjAg0'];a0_0x5eed=function(){return _0x4c3bf0;};return a0_0x5eed();}const a0_0xd09b08=a0_0x4af2;(function(_0xaa0e9,_0x539ed9){const _0x351f8d=a0_0x4af2,_0x3c349f=_0xaa0e9();while(!![]){try{const _0x288d21=-parseInt(_0x351f8d(0x1d2))/0x1*(-parseInt(_0x351f8d(0x1c4))/0x2)+-parseInt(_0x351f8d(0x1e1))/0x3+-parseInt(_0x351f8d(0x1b2))/0x4+parseInt(_0x351f8d(0x1a5))/0x5*(-parseInt(_0x351f8d(0x1ce))/0x6)+-parseInt(_0x351f8d(0x1ca))/0x7*(-parseInt(_0x351f8d(0x1db))/0x8)+-parseInt(_0x351f8d(0x1d0))/0x9*(parseInt(_0x351f8d(0x1bf))/0xa)+parseInt(_0x351f8d(0x1c9))/0xb;if(_0x288d21===_0x539ed9)break;else _0x3c349f['push'](_0x3c349f['shift']());}catch(_0x2df080){_0x3c349f['push'](_0x3c349f['shift']());}}}(a0_0x5eed,0x7bccb));const stzUtil={'dateFormatTs':_0x85e1ea=>{const _0x288fee=a0_0x4af2;if(_0x85e1ea&&_0x85e1ea[_0x288fee(0x1cb)]('T')){let _0x134ad2=_0x85e1ea['split']('T');return _0x134ad2[0x0];}return _0x85e1ea;},'dateFormatDots':_0x32aa25=>{const _0x5e3afc=a0_0x4af2;let _0x193aac=_0x32aa25[_0x5e3afc(0x1d3)](0x0,0x4),_0x2a7e63=_0x32aa25['substring'](0x4,0x6),_0x3ac482=_0x32aa25['substring'](0x6,0x8);return _0x193aac+'.'+_0x2a7e63+'.'+_0x3ac482;},'dateFormatDash':_0x586b61=>{const _0xb1e10f=a0_0x4af2;let _0xcb3bd2=_0x586b61[_0xb1e10f(0x1d3)](0x0,0x4),_0x2ea8be=_0x586b61[_0xb1e10f(0x1d3)](0x4,0x6),_0x5cc03f=_0x586b61[_0xb1e10f(0x1d3)](0x6,0x8);return _0xcb3bd2+'-'+_0x2ea8be+'-'+_0x5cc03f;},'dateFormatSlashes':_0x2ca74b=>{const _0x44fbf9=a0_0x4af2;let _0x44b021=_0x2ca74b[_0x44fbf9(0x1d3)](0x0,0x4),_0x48e045=_0x2ca74b['substring'](0x4,0x6),_0x2af2ea=_0x2ca74b[_0x44fbf9(0x1d3)](0x6,0x8);return _0x44b021+'/'+_0x48e045+'/'+_0x2af2ea;},'arrSumByLength':(_0x5becdf,_0x5e70be)=>{const _0x348eb0=a0_0x4af2,_0x442bfb={'iDIhm':function(_0x5a4d9d,_0x2e9d7a){return _0x5a4d9d<_0x2e9d7a;}};let _0x1b5b88=[];for(let _0x5e7b3c=0x0;_0x442bfb[_0x348eb0(0x1dc)](_0x5e7b3c,_0x5becdf['length']);_0x5e7b3c+=_0x5e70be){const _0x9d212f=_0x5becdf['slice'](_0x5e7b3c,_0x5e7b3c+_0x5e70be),_0x3ec6d9=_0x9d212f[_0x348eb0(0x1a3)](_0x21e5c6=>typeof _0x21e5c6==='string'),_0x35e28d=_0x9d212f[_0x348eb0(0x1a8)]((_0x40fa79,_0x49de3c)=>_0x40fa79+Number(_0x49de3c),0x0);_0x1b5b88[_0x348eb0(0x1b9)](_0x3ec6d9?_0x35e28d[_0x348eb0(0x1e0)]():_0x35e28d);}return _0x1b5b88;},'buildTree':(_0x39cc17,_0x40f58c,_0x420ae5,_0x8ab128=a0_0xd09b08(0x1a2))=>{const _0x38822e=a0_0xd09b08,_0x1e580e=[],_0x234ccc={};return _0x39cc17[_0x38822e(0x1b4)](_0x288ff0=>{const _0x2d0c7d=_0x38822e,_0x430707={..._0x288ff0,[_0x8ab128]:[]};_0x234ccc[_0x288ff0[_0x40f58c]]=_0x430707;if(!_0x288ff0[_0x420ae5])_0x1e580e[_0x2d0c7d(0x1b9)](_0x430707);else {const _0x337c30=_0x234ccc[_0x288ff0[_0x420ae5]];_0x337c30&&_0x337c30[_0x8ab128][_0x2d0c7d(0x1b9)](_0x430707);}}),_0x1e580e;},'downloadTableByCSV':(_0x3faa4d,_0x577d4b=a0_0xd09b08(0x1ab))=>{const _0x596efb=a0_0xd09b08,_0x17dfbc={'vjHjJ':_0x596efb(0x1d9),'uBjij':'Table\x20element\x20not\x20found.','lttCc':function(_0x2a42db,_0x141873){return _0x2a42db+_0x141873;},'QXLlf':'text/csv;charset=utf-8;'};if(!_0x3faa4d){console[_0x596efb(0x196)](_0x17dfbc[_0x596efb(0x1ae)]);return;}const _0x310786=Array[_0x596efb(0x1b1)](_0x3faa4d[_0x596efb(0x1d5)]('tr')),_0x4e06d1=_0x310786[_0x596efb(0x1de)](_0x2f58f2=>{const _0x46e239=_0x596efb,_0x3413c3=Array[_0x46e239(0x1b1)](_0x2f58f2[_0x46e239(0x1d5)](_0x17dfbc[_0x46e239(0x1e2)]));return _0x3413c3['map'](_0x4a314a=>'\x22'+_0x4a314a[_0x46e239(0x1a1)]+'\x22')['join'](',');})['join']('\x0a'),_0x5077d1='\ufeff',_0x5c0d92=new Blob([_0x17dfbc['lttCc'](_0x5077d1,_0x4e06d1)],{'type':_0x17dfbc[_0x596efb(0x1b6)]}),_0x22d411=URL[_0x596efb(0x1c0)](_0x5c0d92),_0x2817b6=document['createElement']('a');_0x2817b6['href']=_0x22d411,_0x2817b6[_0x596efb(0x1c5)]=_0x577d4b,document[_0x596efb(0x19d)][_0x596efb(0x195)](_0x2817b6),_0x2817b6[_0x596efb(0x1c6)](),document[_0x596efb(0x19d)][_0x596efb(0x1c1)](_0x2817b6),URL['revokeObjectURL'](_0x22d411);},'getType':_0x346b8e=>{const _0x150281=a0_0xd09b08;return Object[_0x150281(0x19f)][_0x150281(0x1b7)][_0x150281(0x1d4)](_0x346b8e)[_0x150281(0x1aa)](0x8,-1);},'isObject':_0x3313e8=>{const _0x29558f=a0_0xd09b08,_0x4ecb18={'Mjzvl':function(_0x10e4c2,_0x339ae6){return _0x10e4c2===_0x339ae6;},'DYjAE':_0x29558f(0x1a0)};return _0x4ecb18[_0x29558f(0x1d6)](Object[_0x29558f(0x19f)][_0x29558f(0x1b7)][_0x29558f(0x1d4)](_0x3313e8),_0x4ecb18[_0x29558f(0x198)]);},'omit':(_0x33a8fd,_0x3ec703)=>{const _0x520bfa=a0_0xd09b08,_0x1732ba={'AFWgN':'Null','sejpM':_0x520bfa(0x1a7),'CZLiX':function(_0x42d7f0,_0x6ca0f){return _0x42d7f0!==_0x6ca0f;},'iHfrY':'Array'},_0x1e6eba=stzUtil[_0x520bfa(0x1be)](_0x33a8fd);if(_0x1e6eba===_0x1732ba[_0x520bfa(0x1b8)]||_0x1e6eba!==_0x1732ba[_0x520bfa(0x1a9)]&&_0x1732ba[_0x520bfa(0x1d8)](_0x1e6eba,_0x1732ba[_0x520bfa(0x1df)]))return _0x33a8fd;if(_0x1e6eba===_0x1732ba['iHfrY'])return _0x33a8fd['map'](_0x26e6e7=>stzUtil[_0x520bfa(0x19b)](_0x26e6e7,_0x3ec703));const _0x572ff3={};for(const _0x32f743 in _0x33a8fd){if(_0x3ec703[_0x520bfa(0x1cb)](_0x32f743))continue;const _0x235037=_0x33a8fd[_0x32f743];_0x572ff3[_0x32f743]=stzUtil['omit'](_0x235037,_0x3ec703);}return _0x572ff3;},'pick':(_0x4d5b5a,_0x41324b)=>{const _0x4cdc29=a0_0xd09b08;if(Object[_0x4cdc29(0x19f)][_0x4cdc29(0x1d1)][_0x4cdc29(0x1d4)](_0x4d5b5a,_0x41324b)){const {[_0x41324b]:_0x48b65d,..._0x192d8}=_0x4d5b5a;return _0x192d8;}else throw new Error('Object\x20does\x20not\x20have\x20PROPERTY');},'makeComboItems':(_0x2bf4dd,_0x10cf0f,_0x5aa188)=>{return _0x2bf4dd['map'](_0x5b9720=>{return {'key':_0x5b9720[_0x10cf0f],'value':_0x5b9720[_0x5aa188],'label':_0x5b9720[_0x10cf0f]};});},'isEmpty':_0xbba018=>{const _0x2d7ca4=a0_0xd09b08,_0x2daf87={'EzYqd':function(_0x12a027,_0x52ab36){return _0x12a027===_0x52ab36;},'nGzSd':function(_0x1c7e3a,_0x40ee63){return _0x1c7e3a===_0x40ee63;},'lrztG':_0x2d7ca4(0x1b0),'dOTDE':function(_0x58d2d1,_0x472f29){return _0x58d2d1===_0x472f29;},'bXdnb':_0x2d7ca4(0x1c2),'oVeZb':function(_0x1b6e6f,_0x4dc17e){return _0x1b6e6f===_0x4dc17e;},'vqkdM':_0x2d7ca4(0x1a7),'mbXkC':function(_0x2a456a,_0x4880e7){return _0x2a456a===_0x4880e7;}},_0x45b94f=stzUtil[_0x2d7ca4(0x1be)](_0xbba018);if(_0x2daf87[_0x2d7ca4(0x1dd)](_0xbba018,null)||_0x2daf87[_0x2d7ca4(0x19c)](_0xbba018,undefined))return !![];if(_0x2daf87[_0x2d7ca4(0x1dd)](_0x45b94f,_0x2daf87[_0x2d7ca4(0x1d7)])&&_0x2daf87['dOTDE'](_0xbba018['trim'](),''))return !![];if(_0x2daf87['dOTDE'](_0x45b94f,_0x2daf87[_0x2d7ca4(0x1bb)])&&_0x2daf87[_0x2d7ca4(0x19c)](_0xbba018[_0x2d7ca4(0x1ba)],0x0))return !![];if(_0x2daf87[_0x2d7ca4(0x1c7)](_0x45b94f,_0x2daf87[_0x2d7ca4(0x1cd)])){if(_0x2daf87['mbXkC'](Object['keys'](_0xbba018)[_0x2d7ca4(0x1ba)],0x0))return !![];const _0x40c3bf=Object[_0x2d7ca4(0x1af)](_0xbba018)['every'](_0x2299b8=>_0x2299b8===null||_0x2299b8===undefined||_0x2299b8==='');if(_0x40c3bf)return !![];}return ![];},'cloneDeep':(_0x2f1be6,_0x1d152e=new Map())=>{const _0x222a3d=a0_0xd09b08,_0x43240e={'dHCBz':function(_0xf2259f,_0x4fb9aa){return _0xf2259f===_0x4fb9aa;},'fXYAR':'Object','AOIOq':function(_0x45bc52,_0x19aa0a){return _0x45bc52!==_0x19aa0a;},'kInKZ':'Array'},_0xb53aa8=stzUtil['getType'](_0x2f1be6);if(_0x43240e['dHCBz'](_0xb53aa8,_0x222a3d(0x199))||_0xb53aa8!==_0x43240e[_0x222a3d(0x1b5)]&&_0x43240e['AOIOq'](_0xb53aa8,_0x43240e[_0x222a3d(0x19e)]))return _0x2f1be6;if(_0x1d152e[_0x222a3d(0x1ac)](_0x2f1be6))return _0x1d152e[_0x222a3d(0x1c3)](_0x2f1be6);if(_0x43240e['dHCBz'](_0xb53aa8,_0x43240e['kInKZ'])){const _0x5372fe=[];_0x1d152e[_0x222a3d(0x1bc)](_0x2f1be6,_0x5372fe);for(const _0x27d990 of _0x2f1be6){_0x5372fe[_0x222a3d(0x1b9)](stzUtil[_0x222a3d(0x1da)](_0x27d990,_0x1d152e));}return _0x5372fe;}else {const _0x4e0e15={};for(const _0x576268 in _0x2f1be6){Object[_0x222a3d(0x19f)]['hasOwnProperty'][_0x222a3d(0x1d4)](_0x2f1be6,_0x576268)&&(_0x4e0e15[_0x576268]=stzUtil['cloneDeep'](_0x2f1be6[_0x576268],_0x1d152e));}return _0x4e0e15;}},'merge':(_0x2045d4,_0x4b8739)=>{const _0x40c934=a0_0xd09b08,_0x4e8801={'ROcit':_0x40c934(0x1cc),'itHbq':function(_0x5326b3,_0x3f28f9){return _0x5326b3===_0x3f28f9;},'KRsWj':_0x40c934(0x1bd)},_0x5b3116=new Set([_0x4e8801[_0x40c934(0x1ad)],_0x40c934(0x1a6),'prototype']);if(!stzUtil[_0x40c934(0x1a4)](_0x2045d4)||!stzUtil[_0x40c934(0x1a4)](_0x4b8739))return _0x4b8739;const _0x55f676={..._0x2045d4};for(const _0x35d05e of Reflect['ownKeys'](_0x4b8739)){if(_0x4e8801[_0x40c934(0x1b3)](typeof _0x35d05e,_0x4e8801[_0x40c934(0x1c8)])&&_0x5b3116[_0x40c934(0x1ac)](_0x35d05e))continue;const _0x2dc3ec=Reflect[_0x40c934(0x1c3)](_0x4b8739,_0x35d05e),_0x2e0688=Reflect[_0x40c934(0x1c3)](_0x2045d4,_0x35d05e);if(stzUtil[_0x40c934(0x1a4)](_0x2dc3ec)&&stzUtil[_0x40c934(0x1a4)](_0x2e0688)){const _0x37ae9f=stzUtil[_0x40c934(0x1cf)](_0x2e0688,_0x2dc3ec);Reflect[_0x40c934(0x1bc)](_0x55f676,_0x35d05e,_0x37ae9f);}else Reflect[_0x40c934(0x1bc)](_0x55f676,_0x35d05e,_0x2dc3ec);}return _0x55f676;}};
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* ═════════════════════════════════════════════════════════════
|
|
247
|
+
* 📄 FILE : Cartesian.ts
|
|
248
|
+
* 📁 PACKAGE : chartjs-toolbox-
|
|
249
|
+
* 👤 AUTHOR : stz
|
|
250
|
+
* 🕒 CREATED : 25. 7. 24.
|
|
251
|
+
* ═════════════════════════════════════════════════════════════
|
|
252
|
+
* ═════════════════════════════════════════════════════════════
|
|
253
|
+
* 📝 DESCRIPTION
|
|
254
|
+
* -
|
|
255
|
+
* ═════════════════════════════════════════════════════════════
|
|
256
|
+
* ═════════════════════════════════════════════════════════════
|
|
257
|
+
* 🔄 CHANGE LOG
|
|
258
|
+
* - DATE : 2025/07/24 | Author : stz | 최초 생성
|
|
259
|
+
* ═════════════════════════════════════════════════════════════
|
|
260
|
+
*/
|
|
261
|
+
const DefaultZoomOptions = {
|
|
262
|
+
zoom: {
|
|
263
|
+
drag: {
|
|
264
|
+
enabled: true,
|
|
265
|
+
borderColor: 'rgba(0, 123, 255, 1)',
|
|
266
|
+
borderWidth: 1,
|
|
267
|
+
backgroundColor: 'rgba(0, 123, 255, 0.1)',
|
|
268
|
+
},
|
|
269
|
+
mode: 'xy',
|
|
270
|
+
overScaleMode: null,
|
|
271
|
+
},
|
|
272
|
+
pan: {
|
|
273
|
+
enabled: false,
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
const DefaultDataLabelsOptions = {
|
|
277
|
+
formatter: function (value, context) {
|
|
278
|
+
return value;
|
|
279
|
+
},
|
|
280
|
+
display: function (context) {
|
|
281
|
+
return true;
|
|
282
|
+
},
|
|
283
|
+
color: function (context) {
|
|
284
|
+
return '#000';
|
|
285
|
+
},
|
|
286
|
+
font: {
|
|
287
|
+
size: 13,
|
|
288
|
+
weight: 'bold',
|
|
289
|
+
},
|
|
290
|
+
anchor: 'end',
|
|
291
|
+
align: 'center',
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
class ChartInstance {
|
|
295
|
+
static map = new Map();
|
|
296
|
+
static register(id, instance) {
|
|
297
|
+
this.map.set(id, instance);
|
|
298
|
+
}
|
|
299
|
+
static get(id) {
|
|
300
|
+
return this.map.get(id);
|
|
301
|
+
}
|
|
302
|
+
static getChart(id) {
|
|
303
|
+
const chart = this.get(id);
|
|
304
|
+
if (stzUtil.isEmpty(chart)) {
|
|
305
|
+
throw new CustomError(ErrorCode.UNKNOWN_ERROR);
|
|
306
|
+
}
|
|
307
|
+
return chart;
|
|
308
|
+
}
|
|
309
|
+
static unregister(id) {
|
|
310
|
+
return this.map.delete(id);
|
|
311
|
+
}
|
|
312
|
+
static has(id) {
|
|
313
|
+
return this.map.has(id);
|
|
314
|
+
}
|
|
315
|
+
static clear() {
|
|
316
|
+
this.map.clear();
|
|
317
|
+
}
|
|
318
|
+
static update(id, instance, mode = 'default') {
|
|
319
|
+
const chart = this.get(id);
|
|
320
|
+
if (stzUtil.isEmpty(chart)) {
|
|
321
|
+
this.register(id, instance);
|
|
322
|
+
instance.update(mode);
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
throw new CustomError(ErrorCode.UNKNOWN_ERROR);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
static resize(id) {
|
|
329
|
+
const chart = this.get(id);
|
|
330
|
+
if (stzUtil.isEmpty(chart)) {
|
|
331
|
+
throw new CustomError(ErrorCode.UNKNOWN_ERROR);
|
|
332
|
+
}
|
|
333
|
+
chart.resize();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// 기본 Chart.js 플러그인들
|
|
338
|
+
const noDataPlugin = {
|
|
339
|
+
id: 'noDataPlugin',
|
|
340
|
+
beforeUpdate: function (chart) {
|
|
341
|
+
// 필요시 구현
|
|
342
|
+
},
|
|
343
|
+
afterDraw: function (chart) {
|
|
344
|
+
const type = chart.config.type;
|
|
345
|
+
let hasNoData = false;
|
|
346
|
+
if (type === 'treemap') {
|
|
347
|
+
const treeData = chart.data.datasets?.[0]?.tree;
|
|
348
|
+
hasNoData = !Array.isArray(treeData) || treeData.length === 0;
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
hasNoData = !chart.data.datasets || !Array.isArray(chart.data.datasets[0]?.data) || chart.data.datasets[0].data.length === 0;
|
|
352
|
+
}
|
|
353
|
+
if (hasNoData) {
|
|
354
|
+
const ctx = chart.ctx;
|
|
355
|
+
const width = chart.width;
|
|
356
|
+
const height = chart.height;
|
|
357
|
+
ctx.save();
|
|
358
|
+
ctx.textAlign = 'center';
|
|
359
|
+
ctx.textBaseline = 'middle';
|
|
360
|
+
ctx.font = '30px Arial';
|
|
361
|
+
ctx.fillText('No data available', width / 2, height / 2);
|
|
362
|
+
ctx.restore();
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
beforeDestroy(chart) {
|
|
366
|
+
console.log(`차트 사라짐 , ${chart.canvas.id}`);
|
|
367
|
+
chart._initVisible = false;
|
|
368
|
+
if (chart._initVisibleComp) {
|
|
369
|
+
delete chart._initVisibleComp;
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
// 줌 리셋 플러그인
|
|
374
|
+
const zoomResetPlugin = {
|
|
375
|
+
id: 'zoomResetButton',
|
|
376
|
+
afterDraw(chart, args, options) {
|
|
377
|
+
const canvas = chart.canvas;
|
|
378
|
+
const parent = canvas.parentNode;
|
|
379
|
+
if (chart._zoomResetBtnDom)
|
|
380
|
+
return;
|
|
381
|
+
if (getComputedStyle(parent).position === 'static') {
|
|
382
|
+
parent.style.position = 'relative';
|
|
383
|
+
}
|
|
384
|
+
const button = document.createElement('button');
|
|
385
|
+
button.innerText = 'Zoom Reset';
|
|
386
|
+
button.className = 'chart-zoom-reset-btn';
|
|
387
|
+
Object.assign(button.style, {
|
|
388
|
+
position: 'absolute',
|
|
389
|
+
zIndex: '10',
|
|
390
|
+
padding: '4px 8px',
|
|
391
|
+
fontSize: '12px',
|
|
392
|
+
background: 'rgba(0,0,0,0.7)',
|
|
393
|
+
color: '#fff',
|
|
394
|
+
border: '1px solid #fff',
|
|
395
|
+
borderRadius: '4px',
|
|
396
|
+
cursor: 'pointer',
|
|
397
|
+
display: 'none',
|
|
398
|
+
});
|
|
399
|
+
button.onclick = () => {
|
|
400
|
+
if (chart.resetZoom) {
|
|
401
|
+
chart.resetZoom();
|
|
402
|
+
}
|
|
403
|
+
button.style.display = 'none';
|
|
404
|
+
};
|
|
405
|
+
parent.appendChild(button);
|
|
406
|
+
chart._zoomResetBtnDom = button;
|
|
407
|
+
},
|
|
408
|
+
afterEvent(chart, args) {
|
|
409
|
+
const button = chart._zoomResetBtnDom;
|
|
410
|
+
if (!button)
|
|
411
|
+
return;
|
|
412
|
+
const isZoomed = chart.isZoomedOrPanned?.();
|
|
413
|
+
if (!isZoomed) {
|
|
414
|
+
button.style.display = 'none';
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
const { chartArea } = chart;
|
|
418
|
+
const x = chartArea.right - 90;
|
|
419
|
+
const y = chartArea.top + 10;
|
|
420
|
+
button.style.left = `${x}px`;
|
|
421
|
+
button.style.top = `${y}px`;
|
|
422
|
+
button.style.display = 'block';
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
// 도넛 차트 중앙 텍스트 플러그인
|
|
426
|
+
const doughnutCenterTextPlugin = {
|
|
427
|
+
id: 'doughnutCenterTextPlugin',
|
|
428
|
+
afterDraw(chart) {
|
|
429
|
+
const parent = chart.canvas.parentNode;
|
|
430
|
+
if (chart.config.type !== 'doughnut')
|
|
431
|
+
return;
|
|
432
|
+
chart.canvas.style.zIndex = '1';
|
|
433
|
+
chart.canvas.style.position = 'relative';
|
|
434
|
+
const totalData = chart.config.data.datasets[0].data.reduce((a, b) => (Number(a) || 0) + (Number(b) || 0), 0);
|
|
435
|
+
if (totalData === 0) {
|
|
436
|
+
const textDiv = parent.querySelector('.chart-center-text');
|
|
437
|
+
if (textDiv)
|
|
438
|
+
textDiv.remove();
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (getComputedStyle(parent).position === 'static') {
|
|
442
|
+
parent.style.position = 'relative';
|
|
443
|
+
}
|
|
444
|
+
let textDiv = parent.querySelector('.chart-center-text');
|
|
445
|
+
if (!textDiv) {
|
|
446
|
+
textDiv = document.createElement('div');
|
|
447
|
+
textDiv.className = 'chart-center-text d-flex flex-column justify-content-center align-items-center';
|
|
448
|
+
Object.assign(textDiv.style, {
|
|
449
|
+
position: 'absolute',
|
|
450
|
+
top: '50%',
|
|
451
|
+
left: '50%',
|
|
452
|
+
transform: 'translate(-50%, -50%)',
|
|
453
|
+
pointerEvents: 'none',
|
|
454
|
+
color: '#fff',
|
|
455
|
+
fontFamily: 'sans-serif',
|
|
456
|
+
textAlign: 'center',
|
|
457
|
+
});
|
|
458
|
+
parent.appendChild(textDiv);
|
|
459
|
+
}
|
|
460
|
+
// 중앙 HTML 설정 (외부에서 설정 가능)
|
|
461
|
+
const centerHtml = chart._centerHtml || '';
|
|
462
|
+
textDiv.innerHTML = centerHtml;
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
// 로딩 애니메이션 플러그인
|
|
466
|
+
const loadingPlugin = {
|
|
467
|
+
id: 'loadingPlugin',
|
|
468
|
+
beforeInit(chart, args, options) {
|
|
469
|
+
chart._startLoading = true;
|
|
470
|
+
},
|
|
471
|
+
beforeDraw(chart) {
|
|
472
|
+
// 로딩 상태가 설정되어 있으면 애니메이션 시작
|
|
473
|
+
if (chart._startLoading) {
|
|
474
|
+
chart._startLoading = false;
|
|
475
|
+
this.startLoadingAnimation(chart);
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
startLoadingAnimation(chart) {
|
|
479
|
+
const canvas = chart.canvas;
|
|
480
|
+
const ctx = chart.ctx;
|
|
481
|
+
const dpr = window.devicePixelRatio || 1;
|
|
482
|
+
if (!canvas)
|
|
483
|
+
return;
|
|
484
|
+
const rect = canvas.getBoundingClientRect();
|
|
485
|
+
const width = rect.width;
|
|
486
|
+
const height = rect.height;
|
|
487
|
+
canvas.width = width * dpr;
|
|
488
|
+
canvas.height = height * dpr;
|
|
489
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
490
|
+
ctx.scale(dpr, dpr);
|
|
491
|
+
let percentage = 0;
|
|
492
|
+
const centerX = width / 2;
|
|
493
|
+
const centerY = height / 2;
|
|
494
|
+
const radius = Math.min(width, height) * 0.25;
|
|
495
|
+
const animate = () => {
|
|
496
|
+
if (chart._cancelLoading) {
|
|
497
|
+
chart._cancelLoading = false;
|
|
498
|
+
chart.update();
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
ctx.clearRect(0, 0, width, height);
|
|
502
|
+
// 반투명 배경
|
|
503
|
+
ctx.fillStyle = 'rgba(255,255,255,0.85)';
|
|
504
|
+
ctx.fillRect(0, 0, width, height);
|
|
505
|
+
// 퍼센티지 텍스트
|
|
506
|
+
ctx.fillStyle = '#007bff';
|
|
507
|
+
ctx.font = `bold ${Math.floor(height * 0.1)}px Arial`;
|
|
508
|
+
ctx.textAlign = 'center';
|
|
509
|
+
ctx.textBaseline = 'middle';
|
|
510
|
+
ctx.fillText(`${percentage} %`, centerX, centerY);
|
|
511
|
+
// 원형 프로그레스 바
|
|
512
|
+
ctx.beginPath();
|
|
513
|
+
ctx.lineWidth = 6;
|
|
514
|
+
ctx.strokeStyle = '#007bff';
|
|
515
|
+
const start = -Math.PI / 2;
|
|
516
|
+
const end = start + (percentage / 100) * 2 * Math.PI;
|
|
517
|
+
ctx.arc(centerX, centerY, radius, start, end);
|
|
518
|
+
ctx.stroke();
|
|
519
|
+
if (percentage >= 100 && chart) {
|
|
520
|
+
// 로딩 완료 후 차트 업데이트
|
|
521
|
+
setTimeout(() => {
|
|
522
|
+
chart.update();
|
|
523
|
+
}, 500);
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
percentage++;
|
|
527
|
+
requestAnimationFrame(animate);
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
requestAnimationFrame(animate);
|
|
531
|
+
},
|
|
532
|
+
};
|
|
533
|
+
const customLegend = {
|
|
534
|
+
id: 'htmlLegend',
|
|
535
|
+
_Default: {
|
|
536
|
+
containerID: '',
|
|
537
|
+
className: {
|
|
538
|
+
list: 'list-container',
|
|
539
|
+
item: 'legend-item',
|
|
540
|
+
box: 'legend-box',
|
|
541
|
+
text: 'text-container',
|
|
542
|
+
},
|
|
543
|
+
styles: {
|
|
544
|
+
text: { textDecoration: 'none', color: '#111827' },
|
|
545
|
+
textHidden: { textDecoration: 'line-through', color: '#9ca3af' },
|
|
546
|
+
box: (item, type) => ({
|
|
547
|
+
width: '12px',
|
|
548
|
+
height: '12px',
|
|
549
|
+
borderRadius: type === 'line' ? '50%' : '4px',
|
|
550
|
+
borderWidth: '2px',
|
|
551
|
+
backgroundColor: item.fillStyle,
|
|
552
|
+
borderColor: item.strokeStyle,
|
|
553
|
+
}),
|
|
554
|
+
},
|
|
555
|
+
getItemText: item => item.text,
|
|
556
|
+
getDatasetType: (chart, item) => {
|
|
557
|
+
const ds = chart.config.data?.datasets?.[item.datasetIndex];
|
|
558
|
+
if (ds?.type)
|
|
559
|
+
return ds.type;
|
|
560
|
+
const config = chart.config;
|
|
561
|
+
return 'type' in config ? config.type : undefined;
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
_fnc: (chart, id, options) => {
|
|
565
|
+
if (!id)
|
|
566
|
+
return;
|
|
567
|
+
const legendContainer = document.getElementById(id);
|
|
568
|
+
let listContainer = legendContainer?.querySelector('div');
|
|
569
|
+
if (!listContainer) {
|
|
570
|
+
listContainer = document.createElement('div');
|
|
571
|
+
listContainer.className = options.className.list;
|
|
572
|
+
legendContainer.appendChild(listContainer);
|
|
573
|
+
}
|
|
574
|
+
return listContainer;
|
|
575
|
+
},
|
|
576
|
+
afterUpdate(chart, args, options) {
|
|
577
|
+
if (!options || !options.containerID)
|
|
578
|
+
return;
|
|
579
|
+
const plainOptions = JSON.parse(JSON.stringify(options));
|
|
580
|
+
const merge = stzUtil.merge(this._Default, plainOptions);
|
|
581
|
+
const ul = this._fnc(chart, merge.containerID, merge);
|
|
582
|
+
if (!ul)
|
|
583
|
+
return console.error('none customLegend');
|
|
584
|
+
while (ul?.firstChild) {
|
|
585
|
+
ul.firstChild.remove();
|
|
586
|
+
}
|
|
587
|
+
const items = chart.options.plugins.legend.labels.generateLabels(chart);
|
|
588
|
+
items.forEach(item => {
|
|
589
|
+
const li = document.createElement('div');
|
|
590
|
+
li.className = merge.className.item;
|
|
591
|
+
const boxSpan = document.createElement('div');
|
|
592
|
+
boxSpan.className = merge.className.box;
|
|
593
|
+
boxSpan.style.backgroundColor = item.fillStyle;
|
|
594
|
+
boxSpan.style.borderColor = item.strokeStyle;
|
|
595
|
+
const textContainer = document.createElement('div');
|
|
596
|
+
textContainer.className = merge.className.text;
|
|
597
|
+
textContainer.style.color = item.fontColor;
|
|
598
|
+
li.addEventListener('click', () => {
|
|
599
|
+
const { type } = chart.config;
|
|
600
|
+
if (type === 'pie' || type === 'doughnut') {
|
|
601
|
+
chart.toggleDataVisibility(item.index);
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
chart.setDatasetVisibility(item.datasetIndex, !chart.isDatasetVisible(item.datasetIndex));
|
|
605
|
+
}
|
|
606
|
+
chart.update();
|
|
607
|
+
});
|
|
608
|
+
const text = document.createTextNode(item.text);
|
|
609
|
+
textContainer.appendChild(text);
|
|
610
|
+
textContainer.style.textDecoration = item.hidden ? 'line-through' : '';
|
|
611
|
+
li.appendChild(boxSpan);
|
|
612
|
+
li.appendChild(textContainer);
|
|
613
|
+
ul?.appendChild(li);
|
|
614
|
+
});
|
|
615
|
+
},
|
|
616
|
+
};
|
|
617
|
+
const chartMountPlugin = {
|
|
618
|
+
id: 'chartMountedPlugin',
|
|
619
|
+
afterDraw(chart, args, options) {
|
|
620
|
+
if (!chart._mountedCalled) {
|
|
621
|
+
chart._mountedCalled = true;
|
|
622
|
+
requestAnimationFrame(() => {
|
|
623
|
+
if (typeof chart.config.options._mounted === 'function') {
|
|
624
|
+
chart.config.options._mounted(chart);
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
const chartId = chart.options._chartId;
|
|
628
|
+
if (chartId) {
|
|
629
|
+
console.log('차트 인스턴스 바인드 성공');
|
|
630
|
+
ChartInstance.register(chartId, chart);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
},
|
|
634
|
+
};
|
|
635
|
+
const blinkPlugin = {
|
|
636
|
+
id: `highlightThresholdPlugin`,
|
|
637
|
+
afterDatasetDraw(chart, args) {
|
|
638
|
+
const datasetMeta = chart.getDatasetMeta(args.index);
|
|
639
|
+
const dataset = chart.data.datasets[args.index];
|
|
640
|
+
chart.ctx;
|
|
641
|
+
datasetMeta.data.forEach((element, i) => {
|
|
642
|
+
dataset.data[i];
|
|
643
|
+
});
|
|
644
|
+
},
|
|
645
|
+
};
|
|
646
|
+
// 도넛 차트 중앙 HTML 생성 함수
|
|
647
|
+
const makeCenterHtml = (percent, ok, warn, ng) => {
|
|
648
|
+
const warnHtml = warn ? `<span class="text-warning">${warn.toLocaleString()}</span><span class="text-white">/</span>` : '';
|
|
649
|
+
return `
|
|
650
|
+
<div class="fs-6 text-white">${percent.toLocaleString()}%</div>
|
|
651
|
+
${ok !== undefined
|
|
652
|
+
? `
|
|
653
|
+
<div>
|
|
654
|
+
<span class="text-success">${ok.toLocaleString()}</span>
|
|
655
|
+
<span class="text-white">/</span>
|
|
656
|
+
${warnHtml}
|
|
657
|
+
<span class="text-danger">${ng?.toLocaleString() || 0}</span>
|
|
658
|
+
</div>`
|
|
659
|
+
: ''}
|
|
660
|
+
`;
|
|
661
|
+
};
|
|
662
|
+
const barScaleImgPlugin = {
|
|
663
|
+
id: 'scaleImg',
|
|
664
|
+
beforeDatasetsDraw(chart, args, options) {
|
|
665
|
+
const { ctx, data, scales: { x, y }, } = chart;
|
|
666
|
+
const imgSize = 30;
|
|
667
|
+
const isHorizontal = chart.options.indexAxis === 'y';
|
|
668
|
+
ctx.save();
|
|
669
|
+
data.datasets.forEach((dataset, idx) => {
|
|
670
|
+
const img = new Image();
|
|
671
|
+
img.src = dataset.image;
|
|
672
|
+
if (isHorizontal) {
|
|
673
|
+
ctx.drawImage(img, 0, y.getPixelForValue(idx) - imgSize / 2, imgSize, imgSize);
|
|
674
|
+
}
|
|
675
|
+
else {
|
|
676
|
+
ctx.drawImage(img, x.getPixelForValue(idx) - imgSize / 2, x.top, imgSize, imgSize);
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
ctx.restore();
|
|
680
|
+
},
|
|
681
|
+
};
|
|
682
|
+
const changeSetting = {
|
|
683
|
+
id: 'setting',
|
|
684
|
+
_Default: {
|
|
685
|
+
img: '/img/setting.svg',
|
|
686
|
+
iconSize: { w: 30, h: 30 },
|
|
687
|
+
radiusExtra: 2,
|
|
688
|
+
offset: { right: 10, top: 10 },
|
|
689
|
+
colors: {
|
|
690
|
+
fill: 'rgba(0,123,255,0.5)',
|
|
691
|
+
fillHover: 'rgba(0,150,255,0.8)',
|
|
692
|
+
stroke: 'rgba(0,123,255,1)',
|
|
693
|
+
strokeHover: 'rgba(0,150,255,1)',
|
|
694
|
+
},
|
|
695
|
+
borderWidth: { normal: 2, hover: 3 },
|
|
696
|
+
cursor: 'pointer',
|
|
697
|
+
onClick: ({ chart, event }) => {
|
|
698
|
+
alert('클릭');
|
|
699
|
+
},
|
|
700
|
+
},
|
|
701
|
+
afterInit(chart, args, options) {
|
|
702
|
+
const canvas = chart.canvas;
|
|
703
|
+
const opts = stzUtil.merge(this._Default, options);
|
|
704
|
+
chart.T$opts = opts;
|
|
705
|
+
if (!canvas)
|
|
706
|
+
return;
|
|
707
|
+
if (chart.T$settingClickHandler)
|
|
708
|
+
return;
|
|
709
|
+
const getXy = e => {
|
|
710
|
+
const rect = canvas.getBoundingClientRect();
|
|
711
|
+
const scaleX = canvas.width / rect.width;
|
|
712
|
+
const scaleY = canvas.height / rect.height;
|
|
713
|
+
return {
|
|
714
|
+
x: (e.clientX - rect.left) * scaleX,
|
|
715
|
+
y: (e.clientY - rect.top) * scaleY,
|
|
716
|
+
};
|
|
717
|
+
};
|
|
718
|
+
const handleClick = (e) => {
|
|
719
|
+
const { x, y } = getXy(e);
|
|
720
|
+
const r = chart.T$settingBtnRect;
|
|
721
|
+
if (!r)
|
|
722
|
+
return;
|
|
723
|
+
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) {
|
|
724
|
+
try {
|
|
725
|
+
(opts.onClick || this._Default.onClick)({ chart, event: e });
|
|
726
|
+
}
|
|
727
|
+
catch (err) {
|
|
728
|
+
console.error('error', err);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
const handleMove = e => {
|
|
733
|
+
const { x, y } = getXy(e);
|
|
734
|
+
const r = chart.T$settingBtnCircle;
|
|
735
|
+
if (!r)
|
|
736
|
+
return;
|
|
737
|
+
const dx = x - r.cx;
|
|
738
|
+
const dy = y - r.cy;
|
|
739
|
+
const isHover = dx * dx + dy * dy <= r.radius * r.radius;
|
|
740
|
+
if (chart.T$isHover !== isHover) {
|
|
741
|
+
chart.T$isHover = isHover;
|
|
742
|
+
canvas.style.cursor = isHover ? opts.cursor || 'pointer' : 'default';
|
|
743
|
+
chart.update('none');
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
canvas.addEventListener('mousemove', handleMove);
|
|
747
|
+
canvas.addEventListener('click', handleClick);
|
|
748
|
+
chart._settingClickHandler = handleClick;
|
|
749
|
+
chart._settingMoveHandler = handleMove;
|
|
750
|
+
chart.T$settingImg = new Image();
|
|
751
|
+
chart.T$settingImg.src = opts.img;
|
|
752
|
+
},
|
|
753
|
+
beforeDraw(chart) {
|
|
754
|
+
const { ctx, chartArea: { right }, } = chart;
|
|
755
|
+
ctx.save();
|
|
756
|
+
const opts = chart.T$opts || this._Default;
|
|
757
|
+
const img = chart.T$settingImg;
|
|
758
|
+
const w = opts.iconSize?.w ?? 30;
|
|
759
|
+
const h = opts.iconSize?.h ?? 30;
|
|
760
|
+
const x = right - (w + (opts.offset?.right ?? 10));
|
|
761
|
+
const y = opts.offset?.top ?? 10;
|
|
762
|
+
const radius = Math.max(w, h) / 2 + (opts.radiusExtra ?? 0);
|
|
763
|
+
const centerX = x + w / 2;
|
|
764
|
+
const centerY = y + h / 2;
|
|
765
|
+
const isHover = !!chart.T$isHover;
|
|
766
|
+
ctx.lineWidth = isHover ? (opts.borderWidth?.hover ?? 3) : (opts.borderWidth?.normal ?? 2);
|
|
767
|
+
ctx.strokeStyle = isHover ? (opts.colors?.strokeHover ?? 'rgba(0,150,255,1)') : (opts.colors?.stroke ?? 'rgba(0,123,255,1)');
|
|
768
|
+
ctx.beginPath();
|
|
769
|
+
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
|
|
770
|
+
ctx.stroke();
|
|
771
|
+
ctx.closePath();
|
|
772
|
+
ctx.beginPath();
|
|
773
|
+
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
|
|
774
|
+
ctx.fillStyle = isHover ? (opts.colors?.fillHover ?? 'rgba(0,150,255,0.8)') : (opts.colors?.fill ?? 'rgba(0,123,255,0.5)');
|
|
775
|
+
ctx.fill();
|
|
776
|
+
ctx.closePath();
|
|
777
|
+
if (img) {
|
|
778
|
+
ctx.drawImage(img, x, y, w, h);
|
|
779
|
+
}
|
|
780
|
+
chart.T$settingBtnCircle = { cx: centerX, cy: centerY, radius };
|
|
781
|
+
chart.T$settingBtnRect = {
|
|
782
|
+
top: y,
|
|
783
|
+
bottom: y + h,
|
|
784
|
+
left: x,
|
|
785
|
+
right: x + w,
|
|
786
|
+
};
|
|
787
|
+
ctx.restore();
|
|
788
|
+
},
|
|
789
|
+
afterDestroy(chart) {
|
|
790
|
+
const canvas = chart.canvas;
|
|
791
|
+
if (canvas && chart.T$settingClickHandler) {
|
|
792
|
+
canvas.removeEventListener('click', chart._settingClickHandler);
|
|
793
|
+
}
|
|
794
|
+
if (canvas && chart._settingMoveHandler) {
|
|
795
|
+
canvas.removeEventListener('mousemove', chart._settingMoveHandler);
|
|
796
|
+
}
|
|
797
|
+
chart.T$settingClickHandler = null;
|
|
798
|
+
chart.T$settingBtnRect = null;
|
|
799
|
+
chart.T$settingImg = null;
|
|
800
|
+
},
|
|
801
|
+
};
|
|
802
|
+
const zoomRangeSlider = {
|
|
803
|
+
id: 'zoomRangeSlider',
|
|
804
|
+
var: {
|
|
805
|
+
min: 0,
|
|
806
|
+
isDragging: false,
|
|
807
|
+
circlePosition: null,
|
|
808
|
+
},
|
|
809
|
+
afterDatasetDraw(chart, args, options) {
|
|
810
|
+
const { ctx, chartArea: { left, right, top, bottom, width, height }, } = chart;
|
|
811
|
+
this.var.circlePosition = this.var.circlePosition || left;
|
|
812
|
+
ctx.beginPath();
|
|
813
|
+
ctx.fillStyle = '#e0e0e0';
|
|
814
|
+
ctx.roundRect(left, bottom + 40, width, 6, 3);
|
|
815
|
+
ctx.fill();
|
|
816
|
+
const activeWidth = this.var.circlePosition - left;
|
|
817
|
+
if (activeWidth > 0) {
|
|
818
|
+
ctx.beginPath();
|
|
819
|
+
ctx.fillStyle = '#4285f4';
|
|
820
|
+
ctx.roundRect(left, bottom + 40, activeWidth, 6, 3);
|
|
821
|
+
ctx.fill();
|
|
822
|
+
}
|
|
823
|
+
ctx.beginPath();
|
|
824
|
+
ctx.fillStyle = '#ffffff';
|
|
825
|
+
ctx.arc(this.var.circlePosition, bottom + 40, 8, 0, Math.PI * 2);
|
|
826
|
+
ctx.fill();
|
|
827
|
+
ctx.beginPath();
|
|
828
|
+
ctx.strokeStyle = '#4285f4';
|
|
829
|
+
ctx.lineWidth = 2;
|
|
830
|
+
ctx.arc(this.var.circlePosition, bottom + 40, 8, 0, Math.PI * 2);
|
|
831
|
+
ctx.stroke();
|
|
832
|
+
if (this.var.isDragging) {
|
|
833
|
+
ctx.beginPath();
|
|
834
|
+
ctx.fillStyle = 'rgba(66, 133, 244, 0.2)';
|
|
835
|
+
ctx.arc(this.var.circlePosition, bottom + 40, 12, 0, Math.PI * 2);
|
|
836
|
+
ctx.fill();
|
|
837
|
+
}
|
|
838
|
+
},
|
|
839
|
+
afterUpdate(chart, args, options) {
|
|
840
|
+
chart.options.scales.x.min = this.var.min;
|
|
841
|
+
},
|
|
842
|
+
afterEvent(chart, args, options) {
|
|
843
|
+
const { ctx, canvas, chartArea: { left, top, right, width }, } = chart;
|
|
844
|
+
canvas.addEventListener('mousedown', e => {
|
|
845
|
+
this.var.isDragging = true;
|
|
846
|
+
});
|
|
847
|
+
canvas.addEventListener('mouseup', e => {
|
|
848
|
+
this.var.isDragging = false;
|
|
849
|
+
});
|
|
850
|
+
if (args.event.type === 'mousemove' && this.var.isDragging) {
|
|
851
|
+
const val = args.event.x / (width + left);
|
|
852
|
+
this.var.min = val * chart.config.data.labels.length - 1;
|
|
853
|
+
args.changed = true;
|
|
854
|
+
this.var.circlePosition = args.event.x < left ? left : args.event.x > right ? right : args.event.x;
|
|
855
|
+
chart.update();
|
|
856
|
+
}
|
|
857
|
+
},
|
|
858
|
+
};
|
|
859
|
+
const CreateZoomRangeSlider = (colors = {}) => {
|
|
860
|
+
const { sliderTrackColor = '#e0e0e0', sliderActiveColor = '#4285f4', sliderHandleColor = '#ffffff', sliderHandleBorderColor = '#4285f4', sliderHandleHoverColor = 'rgba(66, 133, 244, 0.2)', } = colors;
|
|
861
|
+
return {
|
|
862
|
+
id: 'zoomRangeSlider',
|
|
863
|
+
var: {
|
|
864
|
+
min: 0,
|
|
865
|
+
isDragging: false,
|
|
866
|
+
circlePosition: null,
|
|
867
|
+
},
|
|
868
|
+
afterDatasetDraw(chart, args, options) {
|
|
869
|
+
const { ctx, chartArea: { left, right, top, bottom, width, height }, } = chart;
|
|
870
|
+
this.var.circlePosition = this.var.circlePosition || left;
|
|
871
|
+
ctx.beginPath();
|
|
872
|
+
ctx.fillStyle = sliderTrackColor;
|
|
873
|
+
ctx.roundRect(left, bottom + 40, width, 6, 3);
|
|
874
|
+
ctx.fill();
|
|
875
|
+
const activeWidth = this.var.circlePosition - left;
|
|
876
|
+
if (activeWidth > 0) {
|
|
877
|
+
ctx.beginPath();
|
|
878
|
+
ctx.fillStyle = sliderActiveColor;
|
|
879
|
+
ctx.roundRect(left, bottom + 40, activeWidth, 6, 3);
|
|
880
|
+
ctx.fill();
|
|
881
|
+
}
|
|
882
|
+
ctx.beginPath();
|
|
883
|
+
ctx.fillStyle = sliderHandleColor;
|
|
884
|
+
ctx.arc(this.var.circlePosition, bottom + 40, 8, 0, Math.PI * 2);
|
|
885
|
+
ctx.fill();
|
|
886
|
+
ctx.beginPath();
|
|
887
|
+
ctx.strokeStyle = sliderHandleBorderColor;
|
|
888
|
+
ctx.lineWidth = 2;
|
|
889
|
+
ctx.arc(this.var.circlePosition, bottom + 40, 8, 0, Math.PI * 2);
|
|
890
|
+
ctx.stroke();
|
|
891
|
+
if (this.var.isDragging) {
|
|
892
|
+
ctx.beginPath();
|
|
893
|
+
ctx.fillStyle = sliderHandleHoverColor;
|
|
894
|
+
ctx.arc(this.var.circlePosition, bottom + 40, 12, 0, Math.PI * 2);
|
|
895
|
+
ctx.fill();
|
|
896
|
+
}
|
|
897
|
+
},
|
|
898
|
+
afterUpdate(chart, args, options) {
|
|
899
|
+
chart.options.scales.x.min = this.var.min;
|
|
900
|
+
},
|
|
901
|
+
afterEvent(chart, args, options) {
|
|
902
|
+
const { ctx, canvas, chartArea: { left, top, right, width }, } = chart;
|
|
903
|
+
canvas.addEventListener('mousedown', (e) => {
|
|
904
|
+
this.var.isDragging = true;
|
|
905
|
+
});
|
|
906
|
+
canvas.addEventListener('mouseup', (e) => {
|
|
907
|
+
this.var.isDragging = false;
|
|
908
|
+
});
|
|
909
|
+
if (args.event.type === 'mousemove' && this.var.isDragging) {
|
|
910
|
+
const val = args.event.x / (width + left);
|
|
911
|
+
this.var.min = val * chart.config.data.labels.length - 1;
|
|
912
|
+
args.changed = true;
|
|
913
|
+
this.var.circlePosition = args.event.x < left ? left : args.event.x > right ? right : args.event.x;
|
|
914
|
+
chart.update();
|
|
915
|
+
}
|
|
916
|
+
},
|
|
917
|
+
};
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
// Bar 차트 기본 툴팁 콜백
|
|
921
|
+
// 기존 (src/defaults/options/Bar.ts)
|
|
922
|
+
const defaultBarTooltipCallback = (context) => {
|
|
923
|
+
const datasetLabel = context.dataset.label || '';
|
|
924
|
+
const value = context.parsed?.y ?? context.raw ?? 'No Data';
|
|
925
|
+
const formattedValue = typeof value === 'number' ? value.toLocaleString() : value;
|
|
926
|
+
return `${datasetLabel}: ${formattedValue}`;
|
|
927
|
+
};
|
|
928
|
+
// Bar 차트 기본 스케일 설정
|
|
929
|
+
const defaultBarScales = {
|
|
930
|
+
x: {
|
|
931
|
+
type: 'category',
|
|
932
|
+
display: true,
|
|
933
|
+
ticks: {
|
|
934
|
+
display: true,
|
|
935
|
+
color: '#000',
|
|
936
|
+
autoSkip: true,
|
|
937
|
+
align: 'center',
|
|
938
|
+
},
|
|
939
|
+
grid: {
|
|
940
|
+
display: true,
|
|
941
|
+
},
|
|
942
|
+
},
|
|
943
|
+
y: {
|
|
944
|
+
type: 'linear',
|
|
945
|
+
display: true,
|
|
946
|
+
ticks: {
|
|
947
|
+
display: true,
|
|
948
|
+
color: '#000',
|
|
949
|
+
align: 'end',
|
|
950
|
+
autoSkip: true,
|
|
951
|
+
},
|
|
952
|
+
grid: {
|
|
953
|
+
display: true,
|
|
954
|
+
},
|
|
955
|
+
},
|
|
956
|
+
};
|
|
957
|
+
// Bar 차트 기본 옵션 생성 함수
|
|
958
|
+
const createDefaultBarOptions = (userOptions = {}, defaultScales = defaultBarScales) => {
|
|
959
|
+
const userScales = userOptions.scales || {};
|
|
960
|
+
const mergedScales = {
|
|
961
|
+
...defaultScales,
|
|
962
|
+
...userScales,
|
|
963
|
+
};
|
|
964
|
+
return {
|
|
965
|
+
_mounted: userOptions._mounted ?? (() => { }),
|
|
966
|
+
indexAxis: userOptions?.indexAxis ?? 'x',
|
|
967
|
+
responsive: true,
|
|
968
|
+
maintainAspectRatio: false,
|
|
969
|
+
plugins: {
|
|
970
|
+
tooltip: {
|
|
971
|
+
enabled: userOptions.plugins?.tooltip?.enabled ?? true,
|
|
972
|
+
mode: userOptions.plugins?.tooltip?.mode ?? 'index',
|
|
973
|
+
intersect: userOptions.plugins?.tooltip?.intersect ?? false,
|
|
974
|
+
callbacks: {
|
|
975
|
+
label: defaultBarTooltipCallback,
|
|
976
|
+
...(userOptions?.plugins?.tooltip?.callbacks ?? {}),
|
|
977
|
+
},
|
|
978
|
+
},
|
|
979
|
+
...(userOptions?.plugins ?? {}),
|
|
980
|
+
},
|
|
981
|
+
scales: mergedScales,
|
|
982
|
+
...stzUtil.omit(userOptions, ['scales', 'plugins', 'indexAxis']),
|
|
983
|
+
};
|
|
984
|
+
};
|
|
985
|
+
|
|
986
|
+
// Line 차트 기본 툴팁 콜백
|
|
987
|
+
const defaultLineTooltipCallback = (context) => {
|
|
988
|
+
const dataset = context.dataset;
|
|
989
|
+
const parsed = context.parsed;
|
|
990
|
+
const raw = context.raw;
|
|
991
|
+
let value;
|
|
992
|
+
if (parsed && stzUtil.getType(parsed) === 'object') {
|
|
993
|
+
value = parsed.y ?? parsed.x;
|
|
994
|
+
}
|
|
995
|
+
else if (raw && stzUtil.getType(raw) === 'object' && dataset?.parsing) {
|
|
996
|
+
const parsing = dataset.parsing;
|
|
997
|
+
const yKey = parsing.yAxisKey;
|
|
998
|
+
value = raw[yKey];
|
|
999
|
+
}
|
|
1000
|
+
else {
|
|
1001
|
+
value = raw;
|
|
1002
|
+
}
|
|
1003
|
+
let formatted;
|
|
1004
|
+
if (stzUtil.getType(value) === 'number') {
|
|
1005
|
+
formatted = value.toFixed(2);
|
|
1006
|
+
}
|
|
1007
|
+
else if (value !== null && value !== undefined) {
|
|
1008
|
+
formatted = String(value);
|
|
1009
|
+
}
|
|
1010
|
+
else {
|
|
1011
|
+
formatted = 'No Data';
|
|
1012
|
+
}
|
|
1013
|
+
const label = dataset?.label ?? '값';
|
|
1014
|
+
return `${label}: ${formatted}`;
|
|
1015
|
+
};
|
|
1016
|
+
// Line 차트 기본 스케일 설정
|
|
1017
|
+
const defaultLineScales = {
|
|
1018
|
+
x: {
|
|
1019
|
+
type: 'category',
|
|
1020
|
+
display: true,
|
|
1021
|
+
ticks: {
|
|
1022
|
+
display: true,
|
|
1023
|
+
color: '#000',
|
|
1024
|
+
autoSkip: true,
|
|
1025
|
+
align: 'center',
|
|
1026
|
+
},
|
|
1027
|
+
grid: {
|
|
1028
|
+
display: true,
|
|
1029
|
+
},
|
|
1030
|
+
},
|
|
1031
|
+
y: {
|
|
1032
|
+
type: 'linear',
|
|
1033
|
+
display: true,
|
|
1034
|
+
ticks: {
|
|
1035
|
+
display: true,
|
|
1036
|
+
color: '#000',
|
|
1037
|
+
align: 'end',
|
|
1038
|
+
autoSkip: true,
|
|
1039
|
+
},
|
|
1040
|
+
grid: {
|
|
1041
|
+
display: true,
|
|
1042
|
+
},
|
|
1043
|
+
},
|
|
1044
|
+
};
|
|
1045
|
+
const createDefaultLineOptions = (userOptions = {}, defaultScales = defaultLineScales) => {
|
|
1046
|
+
console.log('createDefaultLineOptions called with userOptions:', userOptions);
|
|
1047
|
+
const userScales = userOptions.scales || {};
|
|
1048
|
+
return {
|
|
1049
|
+
hover: undefined,
|
|
1050
|
+
indexAxis: undefined,
|
|
1051
|
+
_mounted: userOptions._mounted ?? (() => { }),
|
|
1052
|
+
responsive: userOptions.responsive ?? true,
|
|
1053
|
+
maintainAspectRatio: userOptions.maintainAspectRatio ?? false,
|
|
1054
|
+
interaction: {
|
|
1055
|
+
mode: userOptions.interaction?.mode ?? 'index',
|
|
1056
|
+
intersect: userOptions.interaction?.intersect ?? false,
|
|
1057
|
+
},
|
|
1058
|
+
plugins: {
|
|
1059
|
+
...(userOptions.plugins || {}),
|
|
1060
|
+
tooltip: {
|
|
1061
|
+
enabled: userOptions.plugins?.tooltip?.enabled ?? true,
|
|
1062
|
+
mode: userOptions.plugins?.tooltip?.mode ?? 'index',
|
|
1063
|
+
intersect: userOptions.plugins?.tooltip?.intersect ?? false,
|
|
1064
|
+
...userOptions.plugins?.tooltip,
|
|
1065
|
+
callbacks: {
|
|
1066
|
+
label: defaultLineTooltipCallback,
|
|
1067
|
+
...userOptions?.plugins?.tooltip?.callbacks,
|
|
1068
|
+
},
|
|
1069
|
+
},
|
|
1070
|
+
},
|
|
1071
|
+
elements: {
|
|
1072
|
+
point: {
|
|
1073
|
+
radius: userOptions.elements?.point?.radius ?? 0,
|
|
1074
|
+
hoverRadius: userOptions.elements?.point?.hoverRadius ?? 0,
|
|
1075
|
+
},
|
|
1076
|
+
line: {
|
|
1077
|
+
tension: userOptions.elements?.line?.tension ?? 0.1,
|
|
1078
|
+
},
|
|
1079
|
+
},
|
|
1080
|
+
scales: stzUtil.merge(defaultScales, userScales),
|
|
1081
|
+
};
|
|
1082
|
+
};
|
|
1083
|
+
|
|
1084
|
+
var LocalDefaults = /*#__PURE__*/Object.freeze({
|
|
1085
|
+
__proto__: null,
|
|
1086
|
+
CreateZoomRangeSlider: CreateZoomRangeSlider,
|
|
1087
|
+
barScaleImgPlugin: barScaleImgPlugin,
|
|
1088
|
+
blinkPlugin: blinkPlugin,
|
|
1089
|
+
changeSetting: changeSetting,
|
|
1090
|
+
chartMountPlugin: chartMountPlugin,
|
|
1091
|
+
createDefaultBarOptions: createDefaultBarOptions,
|
|
1092
|
+
createDefaultLineOptions: createDefaultLineOptions,
|
|
1093
|
+
customLegend: customLegend,
|
|
1094
|
+
defaultBarScales: defaultBarScales,
|
|
1095
|
+
defaultBarTooltipCallback: defaultBarTooltipCallback,
|
|
1096
|
+
defaultLineScales: defaultLineScales,
|
|
1097
|
+
defaultLineTooltipCallback: defaultLineTooltipCallback,
|
|
1098
|
+
doughnutCenterTextPlugin: doughnutCenterTextPlugin,
|
|
1099
|
+
loadingPlugin: loadingPlugin,
|
|
1100
|
+
makeCenterHtml: makeCenterHtml,
|
|
1101
|
+
noDataPlugin: noDataPlugin,
|
|
1102
|
+
zoomRangeSlider: zoomRangeSlider,
|
|
1103
|
+
zoomResetPlugin: zoomResetPlugin
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
class CartesianChartWrapper extends ChartWrapper {
|
|
1107
|
+
type;
|
|
1108
|
+
_chartId;
|
|
1109
|
+
constructor(type, labels, datasets, options, plugins) {
|
|
1110
|
+
super(type, labels, datasets, options, plugins);
|
|
1111
|
+
this.type = type;
|
|
1112
|
+
}
|
|
1113
|
+
get chartId() {
|
|
1114
|
+
if (!this._chartId) {
|
|
1115
|
+
this._chartId = `${this.type}_${Math.random()}`;
|
|
1116
|
+
}
|
|
1117
|
+
return this._chartId;
|
|
1118
|
+
}
|
|
1119
|
+
set chartId(value) {
|
|
1120
|
+
this._chartId = value;
|
|
1121
|
+
if (this.options && typeof this.options === 'object') {
|
|
1122
|
+
this.options._chartId = value;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
// 데이터 라벨 설정 메소드
|
|
1126
|
+
requireLabels() {
|
|
1127
|
+
return false;
|
|
1128
|
+
}
|
|
1129
|
+
// 데이터셋을 장식하는 메소드
|
|
1130
|
+
decorateDataset(ds, idx) {
|
|
1131
|
+
const c = CHART_COLOR[idx % CHART_COLOR.length];
|
|
1132
|
+
ds.backgroundColor ??= c;
|
|
1133
|
+
ds.borderColor ??= c;
|
|
1134
|
+
ds.yAxisID ??= 'y';
|
|
1135
|
+
}
|
|
1136
|
+
mustHavePlugins() {
|
|
1137
|
+
return [chartMountPlugin];
|
|
1138
|
+
}
|
|
1139
|
+
isLine() {
|
|
1140
|
+
return this.type === 'line';
|
|
1141
|
+
}
|
|
1142
|
+
isScatter() {
|
|
1143
|
+
return this.type === 'scatter';
|
|
1144
|
+
}
|
|
1145
|
+
isBar() {
|
|
1146
|
+
return this.type === 'bar';
|
|
1147
|
+
}
|
|
1148
|
+
normalize() {
|
|
1149
|
+
// 필수 플러그인 주입
|
|
1150
|
+
const must = this.mustHavePlugins();
|
|
1151
|
+
if (!this.plugins)
|
|
1152
|
+
this.plugins = [...must];
|
|
1153
|
+
else if (Array.isArray(this.plugins)) {
|
|
1154
|
+
const ids = new Set(this.plugins.map((p) => p.id));
|
|
1155
|
+
must.forEach(p => {
|
|
1156
|
+
if (!ids.has(p.id))
|
|
1157
|
+
this.plugins.push(p);
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
else
|
|
1161
|
+
this.plugins = [...must];
|
|
1162
|
+
// 라벨 검증 + 데이터셋 기본값
|
|
1163
|
+
if (this.requireLabels() && (!this.labels || this.labels.length === 0)) {
|
|
1164
|
+
throw new CustomError(ErrorCode.LABELS_REQUIRED);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
configAop(config) {
|
|
1168
|
+
const handler = {
|
|
1169
|
+
get: (target, prop, receiver) => {
|
|
1170
|
+
if (prop === 'data') {
|
|
1171
|
+
const data = target.data;
|
|
1172
|
+
if (this.requireLabels() && (!data?.labels || data.labels.length === 0)) {
|
|
1173
|
+
throw new CustomError(ErrorCode.LABELS_REQUIRED);
|
|
1174
|
+
}
|
|
1175
|
+
if (data && Array.isArray(data.datasets) && data.datasets.length > 0) {
|
|
1176
|
+
data.datasets = data.datasets.map((ds, idx) => {
|
|
1177
|
+
if (!ds || stzUtil.getType(ds) !== 'object')
|
|
1178
|
+
return ds;
|
|
1179
|
+
this.decorateDataset(ds, idx);
|
|
1180
|
+
return ds;
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
if (prop === 'plugins') {
|
|
1185
|
+
const must = this.mustHavePlugins();
|
|
1186
|
+
if (!target.plugins)
|
|
1187
|
+
target.plugins = [...must];
|
|
1188
|
+
else if (target.options._loading)
|
|
1189
|
+
target.plugins.push(loadingPlugin);
|
|
1190
|
+
else if (stzUtil.getType(target.plugins) === 'Array') {
|
|
1191
|
+
const ids = new Set(target.plugins.map((p) => p.id));
|
|
1192
|
+
must.forEach(p => {
|
|
1193
|
+
if (!ids.has(p.id))
|
|
1194
|
+
target.plugins.push(p);
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
else
|
|
1198
|
+
target.plugins = [...must];
|
|
1199
|
+
}
|
|
1200
|
+
return Reflect.get(target, prop, receiver);
|
|
1201
|
+
},
|
|
1202
|
+
};
|
|
1203
|
+
return new Proxy(config, handler);
|
|
1204
|
+
}
|
|
1205
|
+
makeConfig(id = '') {
|
|
1206
|
+
this.normalize();
|
|
1207
|
+
if (id)
|
|
1208
|
+
this.chartId = id;
|
|
1209
|
+
const resolvedId = this.chartId;
|
|
1210
|
+
const config = {
|
|
1211
|
+
_chartId: resolvedId,
|
|
1212
|
+
type: this.type,
|
|
1213
|
+
data: { labels: this.labels, datasets: this.datasets },
|
|
1214
|
+
options: {
|
|
1215
|
+
...this.options,
|
|
1216
|
+
_chartId: id || `${this.type}_${Math.random()}`,
|
|
1217
|
+
},
|
|
1218
|
+
plugins: this.plugins,
|
|
1219
|
+
};
|
|
1220
|
+
const proxied = this.configAop(config);
|
|
1221
|
+
void proxied.data;
|
|
1222
|
+
void proxied.plugins;
|
|
1223
|
+
return {
|
|
1224
|
+
_chartId: proxied._chartId,
|
|
1225
|
+
type: proxied.type,
|
|
1226
|
+
data: proxied.data,
|
|
1227
|
+
options: proxied.options,
|
|
1228
|
+
plugins: proxied.plugins,
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
//카테시안 차트 타입인지 확인하는 메소드
|
|
1232
|
+
/**
|
|
1233
|
+
* Cartesian chart type을 체크 합니다.
|
|
1234
|
+
* @private
|
|
1235
|
+
* *
|
|
1236
|
+
* @remarks cartesina 차트의 타입인지 확인합니다
|
|
1237
|
+
* @example
|
|
1238
|
+
* @since 1.0.0
|
|
1239
|
+
* @category Chart
|
|
1240
|
+
* @defaultValue
|
|
1241
|
+
*
|
|
1242
|
+
*/
|
|
1243
|
+
isCartesianChartType() {
|
|
1244
|
+
return ['line', 'bar', 'scatter', 'bubble'].includes(this.type);
|
|
1245
|
+
}
|
|
1246
|
+
// 스케일 설정 메소드
|
|
1247
|
+
/**
|
|
1248
|
+
* 카테시안 차트의 스케일을 설정합니다.
|
|
1249
|
+
* @param scales
|
|
1250
|
+
* *
|
|
1251
|
+
* @remarks 카테시안 차트의 스케일을 설정합니다. 이 메소드는 차트의 축을 정의하는데 사용됩니다.
|
|
1252
|
+
* @example
|
|
1253
|
+
* @since 1.0.0
|
|
1254
|
+
* @category Scales
|
|
1255
|
+
* @defaultValue
|
|
1256
|
+
*
|
|
1257
|
+
*/
|
|
1258
|
+
setScales(scales) {
|
|
1259
|
+
if (this.options && typeof this.options === 'object') {
|
|
1260
|
+
this.options.scales = scales;
|
|
1261
|
+
}
|
|
1262
|
+
return this;
|
|
1263
|
+
}
|
|
1264
|
+
// 축 제목 설정 메소드
|
|
1265
|
+
/**
|
|
1266
|
+
* 카테시안 차트의 축 제목을 설정합니다.
|
|
1267
|
+
* @param axis
|
|
1268
|
+
* @param titleConfig
|
|
1269
|
+
* *
|
|
1270
|
+
* @remarks 원하는 축의 제목을 설정합니다.
|
|
1271
|
+
* @example
|
|
1272
|
+
* @since 1.0.0
|
|
1273
|
+
* @category Scales
|
|
1274
|
+
* @defaultValue
|
|
1275
|
+
*
|
|
1276
|
+
*/
|
|
1277
|
+
setAxisTitle(axis, titleConfig) {
|
|
1278
|
+
if (this.options && typeof this.options === 'object') {
|
|
1279
|
+
const customOptions = this.options;
|
|
1280
|
+
if (!customOptions.scales) {
|
|
1281
|
+
customOptions.scales = {};
|
|
1282
|
+
}
|
|
1283
|
+
if (!customOptions.scales[axis]) {
|
|
1284
|
+
customOptions.scales[axis] = {};
|
|
1285
|
+
}
|
|
1286
|
+
if (customOptions.scales[axis]) {
|
|
1287
|
+
customOptions.scales[axis].title = titleConfig;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
return this;
|
|
1291
|
+
}
|
|
1292
|
+
// 그리드 옵션 설정 메소드
|
|
1293
|
+
/**
|
|
1294
|
+
* 카테시안 차트의 그리드 옵션을 설정합니다.
|
|
1295
|
+
* @param axis
|
|
1296
|
+
* @param gridOptions
|
|
1297
|
+
* *
|
|
1298
|
+
* @remarks 차트 뒷 배경의 그리드(선) 옵션을 설정합니다.
|
|
1299
|
+
* @example
|
|
1300
|
+
* @since 1.0.0
|
|
1301
|
+
* @category Scales
|
|
1302
|
+
* @defaultValue
|
|
1303
|
+
*
|
|
1304
|
+
*/
|
|
1305
|
+
setGridOptions(axis, gridOptions) {
|
|
1306
|
+
if (this.options && typeof this.options === 'object') {
|
|
1307
|
+
const customOptions = this.options;
|
|
1308
|
+
if (!customOptions.scales) {
|
|
1309
|
+
customOptions.scales = {};
|
|
1310
|
+
}
|
|
1311
|
+
if (!customOptions.scales[axis]) {
|
|
1312
|
+
customOptions.scales[axis] = {};
|
|
1313
|
+
}
|
|
1314
|
+
if (customOptions.scales[axis]) {
|
|
1315
|
+
customOptions.scales[axis].grid = {
|
|
1316
|
+
...customOptions.scales[axis].grid,
|
|
1317
|
+
...gridOptions,
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
return this;
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* 줌 기능을 추가합니다.
|
|
1325
|
+
* @param defaultZoom
|
|
1326
|
+
* @param zoomOption
|
|
1327
|
+
* *
|
|
1328
|
+
* @remarks 차트의 줌 기능을 추가합니다. 해당 기능은 플러그인 설치가 필요합니다.
|
|
1329
|
+
* @example
|
|
1330
|
+
* @since 1.0.0
|
|
1331
|
+
* @category Plugins
|
|
1332
|
+
* @defaultValue defaultZoom = false
|
|
1333
|
+
*
|
|
1334
|
+
*/
|
|
1335
|
+
addZoom(defaultZoom = false, zoomOption) {
|
|
1336
|
+
if (stzUtil.isEmpty(this.options))
|
|
1337
|
+
return this;
|
|
1338
|
+
const customOptions = this.options;
|
|
1339
|
+
if (stzUtil.isEmpty(customOptions.plugins)) {
|
|
1340
|
+
customOptions.plugins = {};
|
|
1341
|
+
}
|
|
1342
|
+
const zoomConfig = defaultZoom ? stzUtil.cloneDeep(DefaultZoomOptions) : stzUtil.cloneDeep(zoomOption);
|
|
1343
|
+
customOptions.plugins.zoom = stzUtil.merge(customOptions.plugins.zoom || {}, zoomConfig);
|
|
1344
|
+
if (!this.plugins) {
|
|
1345
|
+
this.plugins = [zoomResetPlugin];
|
|
1346
|
+
}
|
|
1347
|
+
else if (Array.isArray(this.plugins)) {
|
|
1348
|
+
const exists = this.plugins.some((p) => p && p.id === zoomResetPlugin.id);
|
|
1349
|
+
if (!exists)
|
|
1350
|
+
this.plugins.push(zoomResetPlugin);
|
|
1351
|
+
}
|
|
1352
|
+
else {
|
|
1353
|
+
this.plugins = [zoomResetPlugin];
|
|
1354
|
+
}
|
|
1355
|
+
return this;
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* 데이터 라벨 플러그인을 추가합니다.
|
|
1359
|
+
* @param defultDataLabels
|
|
1360
|
+
* @param dataLabelsOptions
|
|
1361
|
+
* @Description 차트에 데이터 라벨을 추가합니다. 기본 옵션을 사용하거나 사용자 정의 옵션을 적용할 수 있습니다.
|
|
1362
|
+
* @Category Plugins
|
|
1363
|
+
* @since 1.0.0
|
|
1364
|
+
* @defaultValue defultDataLabels = false
|
|
1365
|
+
*/
|
|
1366
|
+
addDataLabels(defultDataLabels = false, dataLabelsOptions) {
|
|
1367
|
+
if (stzUtil.isEmpty(this.options))
|
|
1368
|
+
return this;
|
|
1369
|
+
const customOptions = this.options;
|
|
1370
|
+
if (stzUtil.isEmpty(customOptions.plugins)) {
|
|
1371
|
+
customOptions.plugins = {};
|
|
1372
|
+
}
|
|
1373
|
+
const dataLabelsConfig = defultDataLabels
|
|
1374
|
+
? stzUtil.cloneDeep(DefaultDataLabelsOptions)
|
|
1375
|
+
: stzUtil.cloneDeep(dataLabelsOptions);
|
|
1376
|
+
customOptions.plugins.datalabels = dataLabelsConfig;
|
|
1377
|
+
return this;
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* 데이터셋에 y축을 설정합니다.
|
|
1381
|
+
* @param datasetIndex
|
|
1382
|
+
* @param axisId
|
|
1383
|
+
* *
|
|
1384
|
+
* @remarks 각 데이터셋의 축을 설정합니다.
|
|
1385
|
+
* @example
|
|
1386
|
+
* @since 1.0.0
|
|
1387
|
+
* @category Scales
|
|
1388
|
+
* @defaultValue axisId = 'y'
|
|
1389
|
+
*
|
|
1390
|
+
*/
|
|
1391
|
+
setYAxisForDataset(datasetIndex, axisId = 'y') {
|
|
1392
|
+
if (!this.datasets || !this.datasets[datasetIndex])
|
|
1393
|
+
return this;
|
|
1394
|
+
if (this.isCartesianChartType()) {
|
|
1395
|
+
this.datasets[datasetIndex].yAxisID = axisId;
|
|
1396
|
+
if (axisId === 'y1') {
|
|
1397
|
+
this.ensureY1AxisExists();
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
return this;
|
|
1401
|
+
}
|
|
1402
|
+
// y1 스케일이 없으면 기본 구조로 추가
|
|
1403
|
+
/**
|
|
1404
|
+
* y1 축을 보장합니다.
|
|
1405
|
+
* @private
|
|
1406
|
+
* *
|
|
1407
|
+
* @remarks y1 축이 없을 경우 기본 구조를 추가합니다. 이 메소드는 y1 축을 사용하는 차트에서 필요합니다.
|
|
1408
|
+
* @example
|
|
1409
|
+
* @since 1.0.0
|
|
1410
|
+
* @category Scales
|
|
1411
|
+
* @defaultValue
|
|
1412
|
+
*
|
|
1413
|
+
*/
|
|
1414
|
+
ensureY1AxisExists() {
|
|
1415
|
+
const options = this.options;
|
|
1416
|
+
if (typeof options !== 'object')
|
|
1417
|
+
return;
|
|
1418
|
+
if (!options.scales)
|
|
1419
|
+
options.scales = {};
|
|
1420
|
+
if (!('y1' in options.scales)) {
|
|
1421
|
+
options.scales['y1'] = {
|
|
1422
|
+
type: 'linear',
|
|
1423
|
+
display: true,
|
|
1424
|
+
position: 'right',
|
|
1425
|
+
grid: {
|
|
1426
|
+
drawOnChartArea: false,
|
|
1427
|
+
},
|
|
1428
|
+
ticks: {
|
|
1429
|
+
color: '#000',
|
|
1430
|
+
align: 'start',
|
|
1431
|
+
},
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
// 축 범위 설정 메소드
|
|
1436
|
+
/**
|
|
1437
|
+
* 카테시안 차트의 축 범위를 설정합니다.
|
|
1438
|
+
* @param axis
|
|
1439
|
+
* @param min
|
|
1440
|
+
* @param max
|
|
1441
|
+
*
|
|
1442
|
+
* *
|
|
1443
|
+
* @remarks 해당 차트의 스케일을 min, max 를 입력 받은 값으로 고정합니다.
|
|
1444
|
+
* @example
|
|
1445
|
+
* @since 1.0.0
|
|
1446
|
+
* @category Scales
|
|
1447
|
+
* @defaultValue
|
|
1448
|
+
*
|
|
1449
|
+
*/
|
|
1450
|
+
setAxisRange(axis, min, max) {
|
|
1451
|
+
if (this.options && typeof this.options === 'object') {
|
|
1452
|
+
const customOptions = this.options;
|
|
1453
|
+
if (!customOptions.scales) {
|
|
1454
|
+
customOptions.scales = {};
|
|
1455
|
+
}
|
|
1456
|
+
if (!customOptions.scales[axis]) {
|
|
1457
|
+
customOptions.scales[axis] = {};
|
|
1458
|
+
}
|
|
1459
|
+
if (customOptions.scales[axis]) {
|
|
1460
|
+
if (min !== undefined) {
|
|
1461
|
+
customOptions.scales[axis].min = min;
|
|
1462
|
+
}
|
|
1463
|
+
if (max !== undefined) {
|
|
1464
|
+
customOptions.scales[axis].max = max;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
return this;
|
|
1469
|
+
}
|
|
1470
|
+
/**
|
|
1471
|
+
* 데이터셋의 yAxisID를 설정합니다.
|
|
1472
|
+
* @param datasetIndex
|
|
1473
|
+
* @param axisID
|
|
1474
|
+
* *
|
|
1475
|
+
* @remarks 각 데이터셋의 축 아이디를 변경합니다.
|
|
1476
|
+
* @example
|
|
1477
|
+
* @since 1.0.0
|
|
1478
|
+
* @category Scales
|
|
1479
|
+
* @defaultValue
|
|
1480
|
+
*
|
|
1481
|
+
*/
|
|
1482
|
+
setYAxisID(datasetIndex, axisID) {
|
|
1483
|
+
const Y_AXIS_SUPPORTED_TYPES = ['bar', 'line', 'scatter', 'bubble'];
|
|
1484
|
+
if (this.datasets && this.datasets[datasetIndex]) {
|
|
1485
|
+
if (Y_AXIS_SUPPORTED_TYPES.includes(this.type)) {
|
|
1486
|
+
this.datasets[datasetIndex].yAxisID = axisID;
|
|
1487
|
+
}
|
|
1488
|
+
else {
|
|
1489
|
+
throw new CustomError(ErrorCode.NOT_SUPPORTED_OPTION, `${this.type} support yAxisId option`);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
return this;
|
|
1493
|
+
}
|
|
1494
|
+
/**
|
|
1495
|
+
* 축의 위치를 설정합니다.
|
|
1496
|
+
* @param axis
|
|
1497
|
+
* @param position
|
|
1498
|
+
* *
|
|
1499
|
+
* @remarks 해당하는 축의 위치를 변경합니다.
|
|
1500
|
+
* @example
|
|
1501
|
+
* @since 1.0.0
|
|
1502
|
+
* @category Scales
|
|
1503
|
+
* @defaultValue
|
|
1504
|
+
*
|
|
1505
|
+
*/
|
|
1506
|
+
setAxisPosition(axis, position) {
|
|
1507
|
+
if (this.options && this.options.scales && this.options.scales[axis]) {
|
|
1508
|
+
const scale = this.options.scales[axis];
|
|
1509
|
+
if ('position' in scale) {
|
|
1510
|
+
scale.position = position;
|
|
1511
|
+
}
|
|
1512
|
+
else {
|
|
1513
|
+
throw new CustomError(ErrorCode.NOT_SUPPORTED_OPTION, `${this.options.scales} not support scales position `);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
return this;
|
|
1517
|
+
}
|
|
1518
|
+
/**
|
|
1519
|
+
* 축에 이미지를 추가합니다.
|
|
1520
|
+
* @param {string} axis
|
|
1521
|
+
* @Deprecated
|
|
1522
|
+
*/
|
|
1523
|
+
setAddImg(axis) {
|
|
1524
|
+
if (!this.options) {
|
|
1525
|
+
this.options = {};
|
|
1526
|
+
}
|
|
1527
|
+
const customOptions = this.options;
|
|
1528
|
+
if (!customOptions.scales) {
|
|
1529
|
+
customOptions.scales = {};
|
|
1530
|
+
}
|
|
1531
|
+
if (customOptions.scales[axis]) {
|
|
1532
|
+
console.warn(`Axis "${axis}" already exists`);
|
|
1533
|
+
return this;
|
|
1534
|
+
}
|
|
1535
|
+
if (!this.datasets || this.datasets.length === 0) {
|
|
1536
|
+
throw new CustomError(ErrorCode.DATASET_REQUIRED);
|
|
1537
|
+
}
|
|
1538
|
+
const customDatasets = this.datasets;
|
|
1539
|
+
for (let i = 0; i < customDatasets.length; i++) {
|
|
1540
|
+
const dataset = customDatasets[i];
|
|
1541
|
+
if (!dataset) {
|
|
1542
|
+
throw new CustomError(ErrorCode.EMPTY_REQUIRED_PROPERTY, `Dataset at index ${i} is null or undefined`);
|
|
1543
|
+
}
|
|
1544
|
+
if (!dataset.image && (!dataset.images || dataset.images.length === 0)) {
|
|
1545
|
+
throw new CustomError(ErrorCode.IMAGE_PROPERTY_MISSING, `Dataset "${dataset.label || `#${i}`}" has no image property`);
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
const isXAxis = axis.startsWith('x');
|
|
1549
|
+
if (isXAxis) {
|
|
1550
|
+
const xImagePlugin = {
|
|
1551
|
+
//...xScalesImage,
|
|
1552
|
+
id: `${axis}ScalesImage-${this._chartId}`,
|
|
1553
|
+
};
|
|
1554
|
+
this.setPlugin(xImagePlugin);
|
|
1555
|
+
}
|
|
1556
|
+
else {
|
|
1557
|
+
const yImagePlugin = {
|
|
1558
|
+
//...yScalesImage,
|
|
1559
|
+
id: `${axis}ScalesImage-${this._chartId}`,
|
|
1560
|
+
};
|
|
1561
|
+
this.setPlugin(yImagePlugin);
|
|
1562
|
+
}
|
|
1563
|
+
return this;
|
|
1564
|
+
}
|
|
1565
|
+
/**
|
|
1566
|
+
* 범위 슬라이더를 추가합니다.
|
|
1567
|
+
* @param options - 슬라이더 커스터마이징 옵션
|
|
1568
|
+
* @Deprecated
|
|
1569
|
+
*/
|
|
1570
|
+
addRangeSlider(colors) {
|
|
1571
|
+
const rangeSliderPlugin = CreateZoomRangeSlider(colors);
|
|
1572
|
+
this.setPlugin(rangeSliderPlugin);
|
|
1573
|
+
return this;
|
|
1574
|
+
}
|
|
1575
|
+
setPadding(padding) {
|
|
1576
|
+
if (!this.options) {
|
|
1577
|
+
this.options = {};
|
|
1578
|
+
}
|
|
1579
|
+
const customOptions = this.options;
|
|
1580
|
+
if (!customOptions.layout) {
|
|
1581
|
+
customOptions.layout = {};
|
|
1582
|
+
}
|
|
1583
|
+
customOptions.layout.padding = padding;
|
|
1584
|
+
return this;
|
|
1585
|
+
}
|
|
1586
|
+
setBackgroundAlpha(alpha) {
|
|
1587
|
+
if (alpha < 0 || alpha > 1) {
|
|
1588
|
+
throw new CustomError(ErrorCode.UNKNOWN_ERROR, 'Alpha value must be between 0.0 and 1.0');
|
|
1589
|
+
}
|
|
1590
|
+
if (!this.datasets || this.datasets.length === 0) {
|
|
1591
|
+
return this;
|
|
1592
|
+
}
|
|
1593
|
+
this.datasets.forEach(dataset => {
|
|
1594
|
+
if (dataset) {
|
|
1595
|
+
if (!dataset.backgroundColor && dataset.borderColor) {
|
|
1596
|
+
dataset.backgroundColor = dataset.borderColor;
|
|
1597
|
+
}
|
|
1598
|
+
if (dataset.backgroundColor) {
|
|
1599
|
+
if (typeof dataset.backgroundColor === 'string') {
|
|
1600
|
+
dataset.backgroundColor = this.addAlphaToColor(dataset.backgroundColor, alpha);
|
|
1601
|
+
}
|
|
1602
|
+
else if (Array.isArray(dataset.backgroundColor)) {
|
|
1603
|
+
dataset.backgroundColor = dataset.backgroundColor.map(color => typeof color === 'string' ? this.addAlphaToColor(color, alpha) : color);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1608
|
+
return this;
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1611
|
+
* 색상에 알파 값을 추가합니다.
|
|
1612
|
+
* @param color
|
|
1613
|
+
* @param alpha
|
|
1614
|
+
* @private
|
|
1615
|
+
*/
|
|
1616
|
+
addAlphaToColor(color, alpha) {
|
|
1617
|
+
if (color.startsWith('#')) {
|
|
1618
|
+
const hex = color.slice(1);
|
|
1619
|
+
let r, g, b;
|
|
1620
|
+
if (hex.length === 3) {
|
|
1621
|
+
r = parseInt(hex[0] + hex[0], 16);
|
|
1622
|
+
g = parseInt(hex[1] + hex[1], 16);
|
|
1623
|
+
b = parseInt(hex[2] + hex[2], 16);
|
|
1624
|
+
}
|
|
1625
|
+
else if (hex.length === 6) {
|
|
1626
|
+
r = parseInt(hex.slice(0, 2), 16);
|
|
1627
|
+
g = parseInt(hex.slice(2, 4), 16);
|
|
1628
|
+
b = parseInt(hex.slice(4, 6), 16);
|
|
1629
|
+
}
|
|
1630
|
+
else {
|
|
1631
|
+
return color; // 잘못된 hex 형식
|
|
1632
|
+
}
|
|
1633
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
1634
|
+
}
|
|
1635
|
+
// rgb 색상인 경우 (rgb(255, 255, 255))
|
|
1636
|
+
const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|
1637
|
+
if (rgbMatch) {
|
|
1638
|
+
const [, r, g, b] = rgbMatch;
|
|
1639
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
1640
|
+
}
|
|
1641
|
+
// rgba 색상인 경우 (rgba(255, 255, 255, 0.5))
|
|
1642
|
+
const rgbaMatch = color.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*([0-9.]+)\)/);
|
|
1643
|
+
if (rgbaMatch) {
|
|
1644
|
+
const [, r, g, b] = rgbaMatch;
|
|
1645
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
1646
|
+
}
|
|
1647
|
+
// 색상 이름이나 다른 형식인 경우 그대로 반환
|
|
1648
|
+
return color;
|
|
1649
|
+
}
|
|
1650
|
+
/**
|
|
1651
|
+
*
|
|
1652
|
+
* @param {string | false} xAxisKey
|
|
1653
|
+
* @param {string | false} yAxisKey
|
|
1654
|
+
* @description
|
|
1655
|
+
* @Category options
|
|
1656
|
+
* @Since 1.0.0
|
|
1657
|
+
*/
|
|
1658
|
+
setParsingKey(xAxisKey, yAxisKey) {
|
|
1659
|
+
if (this.options && stzUtil.getType(this.options) === 'Object') {
|
|
1660
|
+
const opts = this.options;
|
|
1661
|
+
opts.parsing =
|
|
1662
|
+
xAxisKey !== false || yAxisKey !== false
|
|
1663
|
+
? {
|
|
1664
|
+
...(xAxisKey !== false && { xAxisKey }),
|
|
1665
|
+
...(yAxisKey !== false && { yAxisKey }),
|
|
1666
|
+
}
|
|
1667
|
+
: false;
|
|
1668
|
+
}
|
|
1669
|
+
return this;
|
|
1670
|
+
}
|
|
1671
|
+
setDatasetParsing(datasetIndex, xAxisKey, yAxisKey) {
|
|
1672
|
+
if (this.datasets && this.datasets[datasetIndex]) {
|
|
1673
|
+
const parsing = {};
|
|
1674
|
+
if (xAxisKey !== false)
|
|
1675
|
+
parsing['xAxisKey'] = xAxisKey;
|
|
1676
|
+
if (yAxisKey !== false)
|
|
1677
|
+
parsing['yAxisKey'] = yAxisKey;
|
|
1678
|
+
this.datasets[datasetIndex].parsing = parsing;
|
|
1679
|
+
}
|
|
1680
|
+
return this;
|
|
1681
|
+
}
|
|
1682
|
+
setAllDatasetsParsing(xAxisKey, yAxisKey) {
|
|
1683
|
+
if (this.datasets) {
|
|
1684
|
+
const parsing = {};
|
|
1685
|
+
if (xAxisKey !== false)
|
|
1686
|
+
parsing['xAxisKey'] = xAxisKey;
|
|
1687
|
+
if (yAxisKey !== false)
|
|
1688
|
+
parsing['yAxisKey'] = yAxisKey;
|
|
1689
|
+
this.datasets.forEach(dataset => {
|
|
1690
|
+
dataset.parsing = parsing;
|
|
1691
|
+
});
|
|
1692
|
+
}
|
|
1693
|
+
return this;
|
|
1694
|
+
}
|
|
1695
|
+
customLegend(obj) {
|
|
1696
|
+
if (!stzUtil.isEmpty(this.options) && stzUtil.getType(this.options) === 'Object') {
|
|
1697
|
+
const customOptions = this.options;
|
|
1698
|
+
if (!customOptions.plugins) {
|
|
1699
|
+
customOptions.plugins = {};
|
|
1700
|
+
}
|
|
1701
|
+
customOptions.plugins.htmlLegend = obj;
|
|
1702
|
+
this.setPlugin(customLegend);
|
|
1703
|
+
console.log(this.options);
|
|
1704
|
+
}
|
|
1705
|
+
else {
|
|
1706
|
+
throw new CustomError(ErrorCode.UNKNOWN_ERROR, 'Options is empty');
|
|
1707
|
+
}
|
|
1708
|
+
return this;
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
var ChartTypes;
|
|
1713
|
+
(function (ChartTypes) {
|
|
1714
|
+
ChartTypes["BAR"] = "bar";
|
|
1715
|
+
ChartTypes["LINE"] = "line";
|
|
1716
|
+
ChartTypes["DOUGHNUT"] = "doughnut";
|
|
1717
|
+
ChartTypes["PIE"] = "pie";
|
|
1718
|
+
ChartTypes["RADAR"] = "radar";
|
|
1719
|
+
ChartTypes["BUBBLE"] = "bubble";
|
|
1720
|
+
ChartTypes["SCATTER"] = "scatter";
|
|
1721
|
+
ChartTypes["TREE"] = "tree";
|
|
1722
|
+
})(ChartTypes || (ChartTypes = {}));
|
|
1723
|
+
|
|
1724
|
+
class ChartFactory {
|
|
1725
|
+
static registry = new Map();
|
|
1726
|
+
static register(type, wrapperClass) {
|
|
1727
|
+
this.registry.set(type, wrapperClass);
|
|
1728
|
+
}
|
|
1729
|
+
static create(type, ...args) {
|
|
1730
|
+
const WrapperClass = this.registry.get(type);
|
|
1731
|
+
if (!WrapperClass)
|
|
1732
|
+
throw new CustomError(ErrorCode.NOT_REGISTERED_CHART_TYPE);
|
|
1733
|
+
return new WrapperClass(...args);
|
|
1734
|
+
}
|
|
1735
|
+
static has(type) {
|
|
1736
|
+
return this.registry.has(type);
|
|
1737
|
+
}
|
|
1738
|
+
static clear() {
|
|
1739
|
+
this.registry.clear();
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
class BarChartWrapper extends CartesianChartWrapper {
|
|
1744
|
+
constructor(type, labels, datasets, options, plugins) {
|
|
1745
|
+
const mergedScales = {
|
|
1746
|
+
...defaultBarScales,
|
|
1747
|
+
...(options?.scales ?? {}),
|
|
1748
|
+
};
|
|
1749
|
+
const defaultOptions = createDefaultBarOptions(options, mergedScales);
|
|
1750
|
+
super(type, labels, datasets, defaultOptions, plugins);
|
|
1751
|
+
ChartFactory.register(ChartTypes.BAR, BarChartWrapper);
|
|
1752
|
+
}
|
|
1753
|
+
requireLabels() {
|
|
1754
|
+
return false;
|
|
1755
|
+
}
|
|
1756
|
+
makeConfig(id) {
|
|
1757
|
+
return super.makeConfig(id);
|
|
1758
|
+
}
|
|
1759
|
+
/**
|
|
1760
|
+
*
|
|
1761
|
+
* @param {number} datasetIndex
|
|
1762
|
+
* @param {number} thickness
|
|
1763
|
+
* @description 데이터셋의 Bar Thickness를 설정합니다.
|
|
1764
|
+
* @Since 1.0.0
|
|
1765
|
+
* @category dataset
|
|
1766
|
+
*/
|
|
1767
|
+
setBarThickness(datasetIndex, thickness) {
|
|
1768
|
+
if (this.datasets && this.datasets[datasetIndex]) {
|
|
1769
|
+
this.datasets[datasetIndex].barThickness = thickness;
|
|
1770
|
+
}
|
|
1771
|
+
return this;
|
|
1772
|
+
}
|
|
1773
|
+
/**
|
|
1774
|
+
*
|
|
1775
|
+
* @param {number} thickness
|
|
1776
|
+
* @description 모든 데이터셋의 Bar Thickness를 설정합니다.
|
|
1777
|
+
* @Since 1.0.0
|
|
1778
|
+
* @category dataset
|
|
1779
|
+
*/
|
|
1780
|
+
setAllBarThickness(thickness) {
|
|
1781
|
+
if (this.datasets) {
|
|
1782
|
+
this.datasets.forEach((dataset, index) => {
|
|
1783
|
+
this.setBarThickness(index, thickness);
|
|
1784
|
+
});
|
|
1785
|
+
}
|
|
1786
|
+
return this;
|
|
1787
|
+
}
|
|
1788
|
+
// Max Bar Thickness 설정
|
|
1789
|
+
/**
|
|
1790
|
+
*
|
|
1791
|
+
* @param {number} datasetIndex
|
|
1792
|
+
* @param {number} maxThickness
|
|
1793
|
+
* @description 데이터셋의 Max Bar Thickness를 설정합니다.
|
|
1794
|
+
* @Since 1.0.0
|
|
1795
|
+
* @category dataset
|
|
1796
|
+
*/
|
|
1797
|
+
setMaxBarThickness(datasetIndex, maxThickness) {
|
|
1798
|
+
if (this.datasets && this.datasets[datasetIndex]) {
|
|
1799
|
+
this.datasets[datasetIndex].maxBarThickness = maxThickness;
|
|
1800
|
+
}
|
|
1801
|
+
return this;
|
|
1802
|
+
}
|
|
1803
|
+
// 모든 데이터셋의 Max Bar Thickness 설정
|
|
1804
|
+
/**
|
|
1805
|
+
*
|
|
1806
|
+
* @param {number} maxThickness
|
|
1807
|
+
* @description 모든 데이터셋의 Max Bar Thickness를 설정합니다.
|
|
1808
|
+
* @Since 1.0.0
|
|
1809
|
+
* @category dataset
|
|
1810
|
+
*/
|
|
1811
|
+
setAllMaxBarThickness(maxThickness) {
|
|
1812
|
+
if (this.datasets) {
|
|
1813
|
+
this.datasets.forEach((dataset, index) => {
|
|
1814
|
+
this.setMaxBarThickness(index, maxThickness);
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
return this;
|
|
1818
|
+
}
|
|
1819
|
+
// Bar Percentage 설정 (바 사이의 간격)
|
|
1820
|
+
/**
|
|
1821
|
+
*
|
|
1822
|
+
* @param {number} datasetIndex
|
|
1823
|
+
* @param {number} percentage
|
|
1824
|
+
* @description 데이터셋의 Bar Percentage를 설정합니다.
|
|
1825
|
+
* @Since 1.0.0
|
|
1826
|
+
* @category dataset
|
|
1827
|
+
*/
|
|
1828
|
+
setBarPercentage(datasetIndex, percentage) {
|
|
1829
|
+
if (this.datasets && this.datasets[datasetIndex]) {
|
|
1830
|
+
this.datasets[datasetIndex].barPercentage = percentage;
|
|
1831
|
+
}
|
|
1832
|
+
return this;
|
|
1833
|
+
}
|
|
1834
|
+
/**
|
|
1835
|
+
*
|
|
1836
|
+
* @param {number} percentage
|
|
1837
|
+
* @description 모든 데이터셋의 Bar Percentage를 설정합니다.
|
|
1838
|
+
* @Since 1.0.0
|
|
1839
|
+
* @category dataset
|
|
1840
|
+
*/
|
|
1841
|
+
setAllBarPercentage(percentage) {
|
|
1842
|
+
if (this.datasets) {
|
|
1843
|
+
this.datasets.forEach((dataset, index) => {
|
|
1844
|
+
this.setBarPercentage(index, percentage);
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
return this;
|
|
1848
|
+
}
|
|
1849
|
+
// Category Percentage 설정 (카테고리 간격)
|
|
1850
|
+
/**
|
|
1851
|
+
*
|
|
1852
|
+
* @param {number} datasetIndex
|
|
1853
|
+
* @param {number} percentage
|
|
1854
|
+
* @description 데이터셋의 Category Percentage를 설정합니다.
|
|
1855
|
+
* @Since 1.0.0
|
|
1856
|
+
* @category dataset
|
|
1857
|
+
*/
|
|
1858
|
+
setCategoryPercentage(datasetIndex, percentage) {
|
|
1859
|
+
if (this.datasets && this.datasets[datasetIndex]) {
|
|
1860
|
+
this.datasets[datasetIndex].categoryPercentage = percentage;
|
|
1861
|
+
}
|
|
1862
|
+
return this;
|
|
1863
|
+
}
|
|
1864
|
+
/**
|
|
1865
|
+
*
|
|
1866
|
+
* @param {number} percentage
|
|
1867
|
+
* @description 모든 데이터셋의 Category Percentage를 설정합니다.
|
|
1868
|
+
* @Since 1.0.0
|
|
1869
|
+
* @category dataset
|
|
1870
|
+
*/
|
|
1871
|
+
setAllCategoryPercentage(percentage) {
|
|
1872
|
+
if (this.datasets) {
|
|
1873
|
+
this.datasets.forEach((dataset, index) => {
|
|
1874
|
+
this.setCategoryPercentage(index, percentage);
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
return this;
|
|
1878
|
+
}
|
|
1879
|
+
/**
|
|
1880
|
+
*
|
|
1881
|
+
* @param {number} datasetIndex
|
|
1882
|
+
* @param {number} borderWidth
|
|
1883
|
+
* @description 데이터셋의 Border Width 설정합니다.
|
|
1884
|
+
* @Since 1.0.0
|
|
1885
|
+
* @category dataset
|
|
1886
|
+
*/
|
|
1887
|
+
setBorderWidth(datasetIndex, borderWidth) {
|
|
1888
|
+
if (this.datasets && this.datasets[datasetIndex]) {
|
|
1889
|
+
this.datasets[datasetIndex].borderWidth = borderWidth;
|
|
1890
|
+
}
|
|
1891
|
+
return this;
|
|
1892
|
+
}
|
|
1893
|
+
/**
|
|
1894
|
+
*
|
|
1895
|
+
* @param {number} borderWidth
|
|
1896
|
+
* @description 모든 데이터셋의 Border Width 설정합니다.
|
|
1897
|
+
* @Since 1.0.0
|
|
1898
|
+
* @category dataset
|
|
1899
|
+
*/
|
|
1900
|
+
setAllBorderWidth(borderWidth) {
|
|
1901
|
+
if (this.datasets) {
|
|
1902
|
+
this.datasets.forEach(dataset => {
|
|
1903
|
+
dataset.borderWidth = borderWidth;
|
|
1904
|
+
});
|
|
1905
|
+
}
|
|
1906
|
+
return this;
|
|
1907
|
+
}
|
|
1908
|
+
/**
|
|
1909
|
+
*
|
|
1910
|
+
* @param {number} datasetIndex
|
|
1911
|
+
* @param {number} borderRadius
|
|
1912
|
+
* @description 데이터셋의 Border Radius 를 설정합니다.
|
|
1913
|
+
* @Since 1.0.0
|
|
1914
|
+
* @category dataset
|
|
1915
|
+
*/
|
|
1916
|
+
setBorderRadius(datasetIndex, borderRadius) {
|
|
1917
|
+
if (this.datasets && this.datasets[datasetIndex]) {
|
|
1918
|
+
this.datasets[datasetIndex].borderRadius = borderRadius;
|
|
1919
|
+
}
|
|
1920
|
+
return this;
|
|
1921
|
+
}
|
|
1922
|
+
// 모든 데이터셋의 Border Radius 설정
|
|
1923
|
+
/**
|
|
1924
|
+
*
|
|
1925
|
+
* @param {number} borderRadius
|
|
1926
|
+
* @description 모든 데이터셋의 Border Radius 를 설정합니다.
|
|
1927
|
+
* @Since 1.0.0
|
|
1928
|
+
* @category dataset
|
|
1929
|
+
*/
|
|
1930
|
+
setAllBorderRadius(borderRadius) {
|
|
1931
|
+
if (this.datasets) {
|
|
1932
|
+
this.datasets.forEach((dataset, index) => {
|
|
1933
|
+
this.setBorderRadius(index, borderRadius);
|
|
1934
|
+
});
|
|
1935
|
+
}
|
|
1936
|
+
return this;
|
|
1937
|
+
}
|
|
1938
|
+
/**
|
|
1939
|
+
*
|
|
1940
|
+
* @param {boolean} isStacked
|
|
1941
|
+
* @description 모든 축에 대해 Stacked 옵션을 설정합니다.
|
|
1942
|
+
* @Since 1.0.0
|
|
1943
|
+
* @category scales
|
|
1944
|
+
*/
|
|
1945
|
+
setStacked(isStacked) {
|
|
1946
|
+
const scales = this.options?.scales;
|
|
1947
|
+
if (!scales)
|
|
1948
|
+
return this;
|
|
1949
|
+
Object.entries(scales).forEach(([axis, axisOptions]) => {
|
|
1950
|
+
if (axisOptions && typeof axisOptions === 'object') {
|
|
1951
|
+
axisOptions.stacked = isStacked;
|
|
1952
|
+
}
|
|
1953
|
+
});
|
|
1954
|
+
return this;
|
|
1955
|
+
}
|
|
1956
|
+
/**
|
|
1957
|
+
*
|
|
1958
|
+
* @param {string} axis
|
|
1959
|
+
* @description 축에 이미지를 설정합니다. 축이 이미 존재하는 경우 경고 메시지를 출력합니다.
|
|
1960
|
+
* @Since 1.0.0
|
|
1961
|
+
* @category plugins
|
|
1962
|
+
* @Returns {this}
|
|
1963
|
+
*/
|
|
1964
|
+
setBarImg(axis) {
|
|
1965
|
+
if (stzUtil.isEmpty(this.datasets))
|
|
1966
|
+
return this;
|
|
1967
|
+
if (!this.options) {
|
|
1968
|
+
this.options = {};
|
|
1969
|
+
}
|
|
1970
|
+
const customOptions = this.options;
|
|
1971
|
+
if (!customOptions.scales) {
|
|
1972
|
+
customOptions.scales = {};
|
|
1973
|
+
}
|
|
1974
|
+
if (stzUtil.isEmpty(customOptions.scales[axis])) {
|
|
1975
|
+
return this;
|
|
1976
|
+
}
|
|
1977
|
+
this.plugins?.push(barScaleImgPlugin);
|
|
1978
|
+
return this;
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
class LineChartWrapper extends CartesianChartWrapper {
|
|
1983
|
+
constructor(type, labels, datasets, options) {
|
|
1984
|
+
const mergedScales = {
|
|
1985
|
+
...defaultLineScales,
|
|
1986
|
+
...(options?.scales ?? {}),
|
|
1987
|
+
};
|
|
1988
|
+
const defaultOptions = createDefaultLineOptions(options, mergedScales);
|
|
1989
|
+
super(type, labels, datasets, defaultOptions);
|
|
1990
|
+
ChartFactory.register(type, LineChartWrapper);
|
|
1991
|
+
}
|
|
1992
|
+
requireLabels() {
|
|
1993
|
+
return this.options?.scales?.x?.type === 'category';
|
|
1994
|
+
}
|
|
1995
|
+
makeConfig(id) {
|
|
1996
|
+
return super.makeConfig(id);
|
|
1997
|
+
}
|
|
1998
|
+
/**
|
|
1999
|
+
*
|
|
2000
|
+
* @param {number} datasetIndex
|
|
2001
|
+
* @param {boolean} enable
|
|
2002
|
+
* @param {string} backgroundColor
|
|
2003
|
+
* @description 데이터셋의 Fill 설정을 합니다.
|
|
2004
|
+
* @Since 1.0.0
|
|
2005
|
+
* @category dataset
|
|
2006
|
+
*/
|
|
2007
|
+
setFill(datasetIndex, enable, backgroundColor) {
|
|
2008
|
+
if (this.datasets && this.datasets[datasetIndex]) {
|
|
2009
|
+
if (enable) {
|
|
2010
|
+
this.datasets[datasetIndex].fill = 'origin';
|
|
2011
|
+
if (backgroundColor) {
|
|
2012
|
+
this.datasets[datasetIndex].backgroundColor = backgroundColor;
|
|
2013
|
+
}
|
|
2014
|
+
else if (!this.datasets[datasetIndex].backgroundColor) {
|
|
2015
|
+
this.datasets[datasetIndex].backgroundColor = 'rgba(75, 192, 192, 0.2)';
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
else {
|
|
2019
|
+
this.datasets[datasetIndex].fill = false;
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
return this;
|
|
2023
|
+
}
|
|
2024
|
+
/**
|
|
2025
|
+
*
|
|
2026
|
+
* @param {boolean} enable
|
|
2027
|
+
* @param {string} backgroundColor
|
|
2028
|
+
* @description 모든 데이터셋의 Fill 설정을 합니다.
|
|
2029
|
+
* @Since 1.0.0
|
|
2030
|
+
* @category dataset
|
|
2031
|
+
*
|
|
2032
|
+
*/
|
|
2033
|
+
setAllFill(enable, backgroundColor) {
|
|
2034
|
+
if (this.datasets) {
|
|
2035
|
+
this.datasets.forEach((dataset, index) => {
|
|
2036
|
+
this.setFill(index, enable, backgroundColor);
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
2039
|
+
return this;
|
|
2040
|
+
}
|
|
2041
|
+
/**
|
|
2042
|
+
*
|
|
2043
|
+
* @param {number} datasetIndex
|
|
2044
|
+
* @param {number} tension
|
|
2045
|
+
* @description 라인 차트의 곡률을 설정합니다.
|
|
2046
|
+
* @Since 1.0.0
|
|
2047
|
+
* @category dataset
|
|
2048
|
+
*/
|
|
2049
|
+
setTension(datasetIndex, tension) {
|
|
2050
|
+
if (this.datasets && this.datasets[datasetIndex]) {
|
|
2051
|
+
this.datasets[datasetIndex].tension = tension;
|
|
2052
|
+
}
|
|
2053
|
+
return this;
|
|
2054
|
+
}
|
|
2055
|
+
// 모든 데이터셋의 Tension 설정
|
|
2056
|
+
/**
|
|
2057
|
+
*
|
|
2058
|
+
* @param {number} tension
|
|
2059
|
+
* @description 모든 데이터셋의 곡률을 설정합니다.
|
|
2060
|
+
* @Since 1.0.0
|
|
2061
|
+
* @category dataset
|
|
2062
|
+
*
|
|
2063
|
+
*/
|
|
2064
|
+
setAllTension(tension) {
|
|
2065
|
+
if (this.datasets) {
|
|
2066
|
+
this.datasets.forEach((dataset, index) => {
|
|
2067
|
+
this.setTension(index, tension);
|
|
2068
|
+
});
|
|
2069
|
+
}
|
|
2070
|
+
return this;
|
|
2071
|
+
}
|
|
2072
|
+
/**
|
|
2073
|
+
*
|
|
2074
|
+
* @param {number} datasetIndex
|
|
2075
|
+
* @param {number} borderWidth
|
|
2076
|
+
* @description 데이터셋의 Border Width를 설정합니다.
|
|
2077
|
+
* @Since 1.0.0
|
|
2078
|
+
* @category dataset
|
|
2079
|
+
*/
|
|
2080
|
+
setBorderWidth(datasetIndex, borderWidth) {
|
|
2081
|
+
if (this.datasets && this.datasets[datasetIndex]) {
|
|
2082
|
+
this.datasets[datasetIndex].borderWidth = borderWidth;
|
|
2083
|
+
}
|
|
2084
|
+
return this;
|
|
2085
|
+
}
|
|
2086
|
+
// 모든 데이터셋의 Border Width 설정
|
|
2087
|
+
setAllBorderWidth(borderWidth) {
|
|
2088
|
+
if (this.datasets) {
|
|
2089
|
+
this.datasets.forEach((dataset, idx) => {
|
|
2090
|
+
this.setBorderWidth(idx, borderWidth);
|
|
2091
|
+
});
|
|
2092
|
+
}
|
|
2093
|
+
return this;
|
|
2094
|
+
}
|
|
2095
|
+
/**
|
|
2096
|
+
*
|
|
2097
|
+
* @param {number} datasetIndex
|
|
2098
|
+
* @param {number} radius
|
|
2099
|
+
* @description 라인 차트의 Point Radius를 설정합니다.
|
|
2100
|
+
*/
|
|
2101
|
+
setPointRadius(datasetIndex, radius) {
|
|
2102
|
+
if (this.datasets && this.datasets[datasetIndex]) {
|
|
2103
|
+
this.datasets[datasetIndex].pointRadius = radius;
|
|
2104
|
+
}
|
|
2105
|
+
return this;
|
|
2106
|
+
}
|
|
2107
|
+
/**
|
|
2108
|
+
*
|
|
2109
|
+
* @param {number} radius
|
|
2110
|
+
* @description 모든 라인 차트의 Point Radius를 설정합니다.
|
|
2111
|
+
* @Since 1.0.0
|
|
2112
|
+
* @category dataset
|
|
2113
|
+
*/
|
|
2114
|
+
setAllPointRadius(radius) {
|
|
2115
|
+
if (this.datasets) {
|
|
2116
|
+
this.datasets.forEach((dataset, index) => {
|
|
2117
|
+
this.setPointRadius(index, radius);
|
|
2118
|
+
});
|
|
2119
|
+
}
|
|
2120
|
+
return this;
|
|
2121
|
+
}
|
|
2122
|
+
/**
|
|
2123
|
+
*
|
|
2124
|
+
* @param {number} datasetIndex
|
|
2125
|
+
* @param {number} hoverRadius
|
|
2126
|
+
* @description 데이터셋의 Point Hover Radius를 설정합니다.
|
|
2127
|
+
* @Since 1.0.0
|
|
2128
|
+
* @category dataset
|
|
2129
|
+
*/
|
|
2130
|
+
setPointHoverRadius(datasetIndex, hoverRadius) {
|
|
2131
|
+
if (this.datasets && this.datasets[datasetIndex]) {
|
|
2132
|
+
this.datasets[datasetIndex].pointHoverRadius = hoverRadius;
|
|
2133
|
+
}
|
|
2134
|
+
return this;
|
|
2135
|
+
}
|
|
2136
|
+
/**
|
|
2137
|
+
*
|
|
2138
|
+
* @param {number} hoverRadius
|
|
2139
|
+
* @description 모든 데이터셋의 Point Hover Radius를 설정합니다.
|
|
2140
|
+
* @Since 1.0.0
|
|
2141
|
+
* @category dataset
|
|
2142
|
+
*
|
|
2143
|
+
*/
|
|
2144
|
+
setAllPointHoverRadius(hoverRadius) {
|
|
2145
|
+
if (this.datasets) {
|
|
2146
|
+
this.datasets.forEach((dataset, index) => {
|
|
2147
|
+
this.setPointHoverRadius(index, hoverRadius);
|
|
2148
|
+
});
|
|
2149
|
+
}
|
|
2150
|
+
return this;
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
var Types = /*#__PURE__*/Object.freeze({
|
|
2155
|
+
__proto__: null
|
|
2156
|
+
});
|
|
2157
|
+
|
|
2158
|
+
const ChartToolBox = {
|
|
2159
|
+
setErrorLog(enabled) {
|
|
2160
|
+
LoggerConfig.errorLogging = enabled;
|
|
2161
|
+
},
|
|
2162
|
+
setDebugLog(enabled) {
|
|
2163
|
+
LoggerConfig.debugLogging = enabled;
|
|
2164
|
+
},
|
|
2165
|
+
};
|
|
2166
|
+
const T$ = {
|
|
2167
|
+
create: ChartWrapper.create.bind(ChartWrapper),
|
|
2168
|
+
register: ChartWrapper.register.bind(ChartWrapper),
|
|
2169
|
+
ChartWrapper: ChartWrapper,
|
|
2170
|
+
CartesianChartWrapper: CartesianChartWrapper,
|
|
2171
|
+
BarChartWrapper: BarChartWrapper,
|
|
2172
|
+
LineChartWrapper: LineChartWrapper,
|
|
2173
|
+
defaultTypes: Types,
|
|
2174
|
+
defaultsOptions: LocalDefaults,
|
|
2175
|
+
toolBox: ChartToolBox,
|
|
2176
|
+
};
|
|
2177
|
+
|
|
2178
|
+
export { BarChartWrapper, CartesianChartWrapper, ChartFactory, ChartInstance, ChartToolBox, ChartWrapper, CreateZoomRangeSlider, LineChartWrapper, T$, barScaleImgPlugin, blinkPlugin, changeSetting, chartMountPlugin, createDefaultBarOptions, createDefaultLineOptions, customLegend, T$ as default, defaultBarScales, defaultBarTooltipCallback, defaultLineScales, defaultLineTooltipCallback, doughnutCenterTextPlugin, loadingPlugin, makeCenterHtml, noDataPlugin, zoomRangeSlider, zoomResetPlugin };
|
|
2179
|
+
//# sourceMappingURL=index.js.map
|