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/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