@qfo/qfchart 0.5.7 → 0.6.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.d.ts +23 -2
- package/dist/qfchart.min.browser.js +14 -13
- package/dist/qfchart.min.es.js +15 -14
- package/package.json +1 -1
- package/src/QFChart.ts +226 -14
- package/src/components/LayoutManager.ts +210 -11
- package/src/components/PluginManager.ts +205 -210
- package/src/components/SeriesBuilder.ts +271 -18
- package/src/types.ts +34 -3
|
@@ -1,234 +1,229 @@
|
|
|
1
|
-
import { ChartContext, Plugin } from
|
|
1
|
+
import { ChartContext, Plugin } from '../types';
|
|
2
2
|
// We need to import AbstractPlugin if we check instanceof, or just treat all as Plugin interface
|
|
3
3
|
|
|
4
4
|
export class PluginManager {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
private createTooltip() {
|
|
20
|
-
this.tooltipElement = document.createElement("div");
|
|
21
|
-
Object.assign(this.tooltipElement.style, {
|
|
22
|
-
position: "fixed",
|
|
23
|
-
display: "none",
|
|
24
|
-
backgroundColor: "#1e293b",
|
|
25
|
-
color: "#e2e8f0",
|
|
26
|
-
padding: "6px 10px",
|
|
27
|
-
borderRadius: "6px",
|
|
28
|
-
fontSize: "13px",
|
|
29
|
-
lineHeight: "1.4",
|
|
30
|
-
fontWeight: "500",
|
|
31
|
-
border: "1px solid #334155",
|
|
32
|
-
zIndex: "9999",
|
|
33
|
-
pointerEvents: "none",
|
|
34
|
-
whiteSpace: "nowrap",
|
|
35
|
-
boxShadow:
|
|
36
|
-
"0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.15)",
|
|
37
|
-
fontFamily: this.context.getOptions().fontFamily || "sans-serif",
|
|
38
|
-
transition: "opacity 0.15s ease-in-out, transform 0.15s ease-in-out",
|
|
39
|
-
opacity: "0",
|
|
40
|
-
transform: "translateX(-5px)",
|
|
41
|
-
});
|
|
42
|
-
document.body.appendChild(this.tooltipElement);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
public destroy() {
|
|
46
|
-
if (this.tooltipElement && this.tooltipElement.parentNode) {
|
|
47
|
-
this.tooltipElement.parentNode.removeChild(this.tooltipElement);
|
|
5
|
+
private plugins: Map<string, Plugin> = new Map();
|
|
6
|
+
private activePluginId: string | null = null;
|
|
7
|
+
private context: ChartContext;
|
|
8
|
+
private toolbarContainer: HTMLElement;
|
|
9
|
+
private tooltipElement: HTMLElement | null = null;
|
|
10
|
+
private hideTimeout: any = null;
|
|
11
|
+
|
|
12
|
+
constructor(context: ChartContext, toolbarContainer: HTMLElement) {
|
|
13
|
+
this.context = context;
|
|
14
|
+
this.toolbarContainer = toolbarContainer;
|
|
15
|
+
this.createTooltip();
|
|
16
|
+
this.renderToolbar();
|
|
48
17
|
}
|
|
49
|
-
this.tooltipElement = null;
|
|
50
|
-
}
|
|
51
18
|
|
|
52
|
-
|
|
53
|
-
|
|
19
|
+
private createTooltip() {
|
|
20
|
+
this.tooltipElement = document.createElement('div');
|
|
21
|
+
Object.assign(this.tooltipElement.style, {
|
|
22
|
+
position: 'fixed',
|
|
23
|
+
display: 'none',
|
|
24
|
+
backgroundColor: '#1e293b',
|
|
25
|
+
color: '#e2e8f0',
|
|
26
|
+
padding: '6px 10px',
|
|
27
|
+
borderRadius: '6px',
|
|
28
|
+
fontSize: '13px',
|
|
29
|
+
lineHeight: '1.4',
|
|
30
|
+
fontWeight: '500',
|
|
31
|
+
border: '1px solid #334155',
|
|
32
|
+
zIndex: '9999',
|
|
33
|
+
pointerEvents: 'none',
|
|
34
|
+
whiteSpace: 'nowrap',
|
|
35
|
+
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.15)',
|
|
36
|
+
fontFamily: this.context.getOptions().fontFamily || 'sans-serif',
|
|
37
|
+
transition: 'opacity 0.15s ease-in-out, transform 0.15s ease-in-out',
|
|
38
|
+
opacity: '0',
|
|
39
|
+
transform: 'translateX(-5px)',
|
|
40
|
+
});
|
|
41
|
+
document.body.appendChild(this.tooltipElement);
|
|
42
|
+
}
|
|
54
43
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
44
|
+
public destroy() {
|
|
45
|
+
if (this.tooltipElement && this.tooltipElement.parentNode) {
|
|
46
|
+
this.tooltipElement.parentNode.removeChild(this.tooltipElement);
|
|
47
|
+
}
|
|
48
|
+
this.tooltipElement = null;
|
|
59
49
|
}
|
|
60
50
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
51
|
+
private showTooltip(target: HTMLElement, text: string) {
|
|
52
|
+
if (!this.tooltipElement) return;
|
|
53
|
+
|
|
54
|
+
// Clear any pending hide to prevent race conditions
|
|
55
|
+
if (this.hideTimeout) {
|
|
56
|
+
clearTimeout(this.hideTimeout);
|
|
57
|
+
this.hideTimeout = null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const rect = target.getBoundingClientRect();
|
|
61
|
+
this.tooltipElement.textContent = text;
|
|
62
|
+
this.tooltipElement.style.display = 'block';
|
|
63
|
+
|
|
64
|
+
// Position to the right of the button, centered vertically
|
|
65
|
+
const tooltipRect = this.tooltipElement.getBoundingClientRect();
|
|
66
|
+
const top = rect.top + (rect.height - tooltipRect.height) / 2;
|
|
67
|
+
const left = rect.right + 10; // 10px gap
|
|
68
|
+
|
|
69
|
+
this.tooltipElement.style.top = `${top}px`;
|
|
70
|
+
this.tooltipElement.style.left = `${left}px`;
|
|
71
|
+
|
|
72
|
+
// Trigger animation
|
|
73
|
+
requestAnimationFrame(() => {
|
|
74
|
+
if (this.tooltipElement) {
|
|
75
|
+
this.tooltipElement.style.opacity = '1';
|
|
76
|
+
this.tooltipElement.style.transform = 'translateX(0)';
|
|
77
|
+
}
|
|
78
|
+
});
|
|
89
79
|
}
|
|
90
80
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
this.tooltipElement.style.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
81
|
+
private hideTooltip() {
|
|
82
|
+
if (!this.tooltipElement) return;
|
|
83
|
+
this.tooltipElement.style.opacity = '0';
|
|
84
|
+
this.tooltipElement.style.transform = 'translateX(-5px)';
|
|
85
|
+
|
|
86
|
+
if (this.hideTimeout) {
|
|
87
|
+
clearTimeout(this.hideTimeout);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Wait for transition to finish before hiding
|
|
91
|
+
this.hideTimeout = setTimeout(() => {
|
|
92
|
+
if (this.tooltipElement) {
|
|
93
|
+
this.tooltipElement.style.display = 'none';
|
|
94
|
+
}
|
|
95
|
+
this.hideTimeout = null;
|
|
96
|
+
}, 150);
|
|
104
97
|
}
|
|
105
|
-
|
|
106
|
-
plugin
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
this.deactivatePlugin();
|
|
115
|
-
}
|
|
116
|
-
plugin.destroy?.();
|
|
117
|
-
this.plugins.delete(pluginId);
|
|
118
|
-
this.removeButton(pluginId);
|
|
98
|
+
|
|
99
|
+
public register(plugin: Plugin): void {
|
|
100
|
+
if (this.plugins.has(plugin.id)) {
|
|
101
|
+
console.warn(`Plugin with id ${plugin.id} is already registered.`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
this.plugins.set(plugin.id, plugin);
|
|
105
|
+
plugin.init(this.context);
|
|
106
|
+
this.addButton(plugin);
|
|
119
107
|
}
|
|
120
|
-
}
|
|
121
108
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
109
|
+
public unregister(pluginId: string): void {
|
|
110
|
+
const plugin = this.plugins.get(pluginId);
|
|
111
|
+
if (plugin) {
|
|
112
|
+
if (this.activePluginId === pluginId) {
|
|
113
|
+
this.deactivatePlugin();
|
|
114
|
+
}
|
|
115
|
+
plugin.destroy?.();
|
|
116
|
+
this.plugins.delete(pluginId);
|
|
117
|
+
this.removeButton(pluginId);
|
|
118
|
+
}
|
|
127
119
|
}
|
|
128
120
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
121
|
+
public activatePlugin(pluginId: string): void {
|
|
122
|
+
// If same plugin is clicked, deactivate it (toggle)
|
|
123
|
+
if (this.activePluginId === pluginId) {
|
|
124
|
+
this.deactivatePlugin();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Deactivate current active plugin
|
|
129
|
+
if (this.activePluginId) {
|
|
130
|
+
this.deactivatePlugin();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const plugin = this.plugins.get(pluginId);
|
|
134
|
+
if (plugin) {
|
|
135
|
+
this.activePluginId = pluginId;
|
|
136
|
+
this.setButtonActive(pluginId, true);
|
|
137
|
+
plugin.activate?.();
|
|
138
|
+
}
|
|
132
139
|
}
|
|
133
140
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
public deactivatePlugin(): void {
|
|
142
|
+
if (this.activePluginId) {
|
|
143
|
+
const plugin = this.plugins.get(this.activePluginId);
|
|
144
|
+
plugin?.deactivate?.();
|
|
145
|
+
this.setButtonActive(this.activePluginId, false);
|
|
146
|
+
this.activePluginId = null;
|
|
147
|
+
}
|
|
139
148
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
149
|
+
|
|
150
|
+
// --- UI Handling ---
|
|
151
|
+
|
|
152
|
+
private renderToolbar(): void {
|
|
153
|
+
this.toolbarContainer.innerHTML = '';
|
|
154
|
+
this.toolbarContainer.classList.add('qfchart-toolbar');
|
|
155
|
+
this.toolbarContainer.style.display = 'flex';
|
|
156
|
+
this.toolbarContainer.style.flexDirection = 'column';
|
|
157
|
+
this.toolbarContainer.style.width = '40px';
|
|
158
|
+
this.toolbarContainer.style.backgroundColor = this.context.getOptions().backgroundColor || '#1e293b';
|
|
159
|
+
this.toolbarContainer.style.borderRight = '1px solid #334155';
|
|
160
|
+
this.toolbarContainer.style.padding = '5px';
|
|
161
|
+
this.toolbarContainer.style.boxSizing = 'border-box';
|
|
162
|
+
this.toolbarContainer.style.gap = '5px';
|
|
163
|
+
this.toolbarContainer.style.flexShrink = '0';
|
|
148
164
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
165
|
+
|
|
166
|
+
private addButton(plugin: Plugin): void {
|
|
167
|
+
const btn = document.createElement('button');
|
|
168
|
+
btn.id = `qfchart-plugin-btn-${plugin.id}`;
|
|
169
|
+
// Removed native title to use custom tooltip
|
|
170
|
+
// btn.title = plugin.name || plugin.id;
|
|
171
|
+
btn.style.width = '30px';
|
|
172
|
+
btn.style.height = '30px';
|
|
173
|
+
btn.style.padding = '4px';
|
|
174
|
+
btn.style.border = '1px solid transparent';
|
|
175
|
+
btn.style.borderRadius = '4px';
|
|
176
|
+
btn.style.backgroundColor = 'transparent';
|
|
177
|
+
btn.style.cursor = 'pointer';
|
|
178
|
+
btn.style.color = this.context.getOptions().fontColor || '#cbd5e1';
|
|
179
|
+
btn.style.display = 'flex';
|
|
180
|
+
btn.style.alignItems = 'center';
|
|
181
|
+
btn.style.justifyContent = 'center';
|
|
182
|
+
|
|
183
|
+
// Icon
|
|
184
|
+
if (plugin.icon) {
|
|
185
|
+
btn.innerHTML = plugin.icon;
|
|
186
|
+
} else {
|
|
187
|
+
btn.innerText = (plugin.name || plugin.id).substring(0, 2).toUpperCase();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Hover effects and Tooltip
|
|
191
|
+
btn.addEventListener('mouseenter', () => {
|
|
192
|
+
if (this.activePluginId !== plugin.id) {
|
|
193
|
+
btn.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
|
|
194
|
+
}
|
|
195
|
+
this.showTooltip(btn, plugin.name || plugin.id);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
btn.addEventListener('mouseleave', () => {
|
|
199
|
+
if (this.activePluginId !== plugin.id) {
|
|
200
|
+
btn.style.backgroundColor = 'transparent';
|
|
201
|
+
}
|
|
202
|
+
this.hideTooltip();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
btn.onclick = () => this.activatePlugin(plugin.id);
|
|
206
|
+
|
|
207
|
+
this.toolbarContainer.appendChild(btn);
|
|
189
208
|
}
|
|
190
209
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
this.showTooltip(btn, plugin.name || plugin.id);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
btn.addEventListener("mouseleave", () => {
|
|
200
|
-
if (this.activePluginId !== plugin.id) {
|
|
201
|
-
btn.style.backgroundColor = "transparent";
|
|
202
|
-
}
|
|
203
|
-
this.hideTooltip();
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
btn.onclick = () => this.activatePlugin(plugin.id);
|
|
207
|
-
|
|
208
|
-
this.toolbarContainer.appendChild(btn);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
private removeButton(pluginId: string): void {
|
|
212
|
-
const btn = this.toolbarContainer.querySelector(
|
|
213
|
-
`#qfchart-plugin-btn-${pluginId}`
|
|
214
|
-
);
|
|
215
|
-
if (btn) {
|
|
216
|
-
btn.remove();
|
|
210
|
+
private removeButton(pluginId: string): void {
|
|
211
|
+
const btn = this.toolbarContainer.querySelector(`#qfchart-plugin-btn-${pluginId}`);
|
|
212
|
+
if (btn) {
|
|
213
|
+
btn.remove();
|
|
214
|
+
}
|
|
217
215
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
btn.style.color = this.context.getOptions().fontColor || "#cbd5e1";
|
|
231
|
-
}
|
|
216
|
+
|
|
217
|
+
private setButtonActive(pluginId: string, active: boolean): void {
|
|
218
|
+
const btn = this.toolbarContainer.querySelector(`#qfchart-plugin-btn-${pluginId}`) as HTMLElement;
|
|
219
|
+
if (btn) {
|
|
220
|
+
if (active) {
|
|
221
|
+
btn.style.backgroundColor = '#2563eb'; // Blue highlight
|
|
222
|
+
btn.style.color = '#ffffff';
|
|
223
|
+
} else {
|
|
224
|
+
btn.style.backgroundColor = 'transparent';
|
|
225
|
+
btn.style.color = this.context.getOptions().fontColor || '#cbd5e1';
|
|
226
|
+
}
|
|
227
|
+
}
|
|
232
228
|
}
|
|
233
|
-
}
|
|
234
229
|
}
|