@qfo/qfchart 0.7.3 → 0.8.0

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.
@@ -1,263 +1,284 @@
1
- import { LayoutResult } from './LayoutManager';
2
- import { QFChartOptions } from '../types';
3
-
4
- export class GraphicBuilder {
5
- public static build(
6
- layout: LayoutResult,
7
- options: QFChartOptions,
8
- onToggle: (id: string, action?: 'collapse' | 'maximize' | 'fullscreen') => void,
9
- isMainCollapsed: boolean = false,
10
- maximizedPaneId: string | null = null
11
- ): any[] {
12
- const graphic: any[] = [];
13
- const pixelToPercent = layout.pixelToPercent;
14
- const mainPaneTop = layout.mainPaneTop;
15
-
16
- // Main Chart Title (Only if main chart is visible or maximized)
17
- // If maximizedPaneId is set and NOT main, main title should be hidden?
18
- // With current LayoutManager logic, if maximizedPaneId !== main, mainPaneHeight is 0.
19
- // We should check heights or IDs.
20
-
21
- const showMain = !maximizedPaneId || maximizedPaneId === 'main';
22
-
23
- if (showMain) {
24
- const titleTopMargin = 10 * pixelToPercent;
25
- graphic.push({
26
- type: 'text',
27
- left: '8.5%',
28
- top: mainPaneTop + titleTopMargin + '%',
29
- z: 10,
30
- style: {
31
- text: options.title || 'Market',
32
- fill: options.titleColor || '#fff',
33
- font: `bold 16px ${options.fontFamily || 'sans-serif'}`,
34
- textVerticalAlign: 'top',
35
- },
36
- });
37
-
38
- // Watermark
39
- if (options.watermark !== false) {
40
- const bottomY = layout.mainPaneTop + layout.mainPaneHeight;
41
- graphic.push({
42
- type: 'text',
43
- right: '11%',
44
- top: bottomY - 3 + '%', // Position 5% from bottom of main chart
45
- z: 10,
46
- style: {
47
- text: 'QFChart',
48
- fill: options.fontColor || '#cbd5e1',
49
- font: `bold 16px sans-serif`,
50
- opacity: 0.1,
51
- },
52
- cursor: 'pointer',
53
- onclick: () => {
54
- window.open('https://quantforge.org', '_blank');
55
- },
56
- });
57
- }
58
-
59
- // Main Controls Group
60
- const controls: any[] = [];
61
-
62
- // Collapse Button
63
- if (options.controls?.collapse) {
64
- controls.push({
65
- type: 'group',
66
- children: [
67
- {
68
- type: 'rect',
69
- shape: { width: 20, height: 20, r: 2 },
70
- style: { fill: '#334155', stroke: '#475569', lineWidth: 1 },
71
- onclick: () => onToggle('main', 'collapse'),
72
- },
73
- {
74
- type: 'text',
75
- style: {
76
- text: isMainCollapsed ? '+' : '−',
77
- fill: '#cbd5e1',
78
- font: `bold 14px ${options.fontFamily}`,
79
- x: 10,
80
- y: 10,
81
- textAlign: 'center',
82
- textVerticalAlign: 'middle',
83
- },
84
- silent: true,
85
- },
86
- ],
87
- });
88
- }
89
-
90
- // Maximize Button
91
- if (options.controls?.maximize) {
92
- const isMaximized = maximizedPaneId === 'main';
93
- // Shift x position if collapse button exists
94
- const xOffset = options.controls?.collapse ? 25 : 0;
95
-
96
- controls.push({
97
- type: 'group',
98
- x: xOffset,
99
- children: [
100
- {
101
- type: 'rect',
102
- shape: { width: 20, height: 20, r: 2 },
103
- style: { fill: '#334155', stroke: '#475569', lineWidth: 1 },
104
- onclick: () => onToggle('main', 'maximize'),
105
- },
106
- {
107
- type: 'text',
108
- style: {
109
- text: isMaximized ? '❐' : '□', // Simple chars for now
110
- fill: '#cbd5e1',
111
- font: `14px ${options.fontFamily}`,
112
- x: 10,
113
- y: 10,
114
- textAlign: 'center',
115
- textVerticalAlign: 'middle',
116
- },
117
- silent: true,
118
- },
119
- ],
120
- });
121
- }
122
-
123
- // Fullscreen Button
124
- if (options.controls?.fullscreen) {
125
- let xOffset = 0;
126
- if (options.controls?.collapse) xOffset += 25;
127
- if (options.controls?.maximize) xOffset += 25;
128
-
129
- controls.push({
130
- type: 'group',
131
- x: xOffset,
132
- children: [
133
- {
134
- type: 'rect',
135
- shape: { width: 20, height: 20, r: 2 },
136
- style: { fill: '#334155', stroke: '#475569', lineWidth: 1 },
137
- onclick: () => onToggle('main', 'fullscreen'),
138
- },
139
- {
140
- type: 'text',
141
- style: {
142
- text: '⛶',
143
- fill: '#cbd5e1',
144
- font: `14px ${options.fontFamily}`,
145
- x: 10,
146
- y: 10,
147
- textAlign: 'center',
148
- textVerticalAlign: 'middle',
149
- },
150
- silent: true,
151
- },
152
- ],
153
- });
154
- }
155
-
156
- if (controls.length > 0) {
157
- graphic.push({
158
- type: 'group',
159
- right: '10.5%',
160
- top: mainPaneTop + '%',
161
- children: controls,
162
- });
163
- }
164
- }
165
-
166
- // Indicator Panes
167
- layout.paneLayout.forEach((pane) => {
168
- // If maximizedPaneId is set, and this is NOT the maximized pane, skip rendering its controls
169
- if (maximizedPaneId && pane.indicatorId !== maximizedPaneId) {
170
- return;
171
- }
172
-
173
- // Title
174
- graphic.push({
175
- type: 'text',
176
- left: '8.5%',
177
- top: pane.top + 10 * pixelToPercent + '%',
178
- z: 10,
179
- style: {
180
- text: pane.indicatorId || '',
181
- fill: pane.titleColor || '#fff',
182
- font: `bold 12px ${options.fontFamily || 'sans-serif'}`,
183
- textVerticalAlign: 'top',
184
- },
185
- });
186
-
187
- // Controls
188
- const controls: any[] = [];
189
-
190
- // Collapse
191
- if (pane.controls?.collapse) {
192
- controls.push({
193
- type: 'group',
194
- children: [
195
- {
196
- type: 'rect',
197
- shape: { width: 20, height: 20, r: 2 },
198
- style: { fill: '#334155', stroke: '#475569', lineWidth: 1 },
199
- onclick: () => pane.indicatorId && onToggle(pane.indicatorId, 'collapse'),
200
- },
201
- {
202
- type: 'text',
203
- style: {
204
- text: pane.isCollapsed ? '+' : '−',
205
- fill: '#cbd5e1',
206
- font: `bold 14px ${options.fontFamily}`,
207
- x: 10,
208
- y: 10,
209
- textAlign: 'center',
210
- textVerticalAlign: 'middle',
211
- },
212
- silent: true,
213
- },
214
- ],
215
- });
216
- }
217
-
218
- // Maximize
219
- if (pane.controls?.maximize) {
220
- // Assuming we add maximize to Indicator controls
221
- const isMaximized = maximizedPaneId === pane.indicatorId;
222
- const xOffset = pane.controls?.collapse ? 25 : 0;
223
-
224
- controls.push({
225
- type: 'group',
226
- x: xOffset,
227
- children: [
228
- {
229
- type: 'rect',
230
- shape: { width: 20, height: 20, r: 2 },
231
- style: { fill: '#334155', stroke: '#475569', lineWidth: 1 },
232
- onclick: () => pane.indicatorId && onToggle(pane.indicatorId, 'maximize'),
233
- },
234
- {
235
- type: 'text',
236
- style: {
237
- text: isMaximized ? '❐' : '□',
238
- fill: '#cbd5e1',
239
- font: `14px ${options.fontFamily}`,
240
- x: 10,
241
- y: 10,
242
- textAlign: 'center',
243
- textVerticalAlign: 'middle',
244
- },
245
- silent: true,
246
- },
247
- ],
248
- });
249
- }
250
-
251
- if (controls.length > 0) {
252
- graphic.push({
253
- type: 'group',
254
- right: '10.5%',
255
- top: pane.top + '%',
256
- children: controls,
257
- });
258
- }
259
- });
260
-
261
- return graphic;
262
- }
263
- }
1
+ import { LayoutResult } from './LayoutManager';
2
+ import { QFChartOptions } from '../types';
3
+
4
+ export class GraphicBuilder {
5
+ public static build(
6
+ layout: LayoutResult,
7
+ options: QFChartOptions,
8
+ onToggle: (id: string, action?: 'collapse' | 'maximize' | 'fullscreen') => void,
9
+ isMainCollapsed: boolean = false,
10
+ maximizedPaneId: string | null = null,
11
+ overlayIndicators: { id: string; titleColor?: string }[] = []
12
+ ): any[] {
13
+ const graphic: any[] = [];
14
+ const pixelToPercent = layout.pixelToPercent;
15
+ const mainPaneTop = layout.mainPaneTop;
16
+
17
+ // Main Chart Title (Only if main chart is visible or maximized)
18
+ // If maximizedPaneId is set and NOT main, main title should be hidden?
19
+ // With current LayoutManager logic, if maximizedPaneId !== main, mainPaneHeight is 0.
20
+ // We should check heights or IDs.
21
+
22
+ const showMain = !maximizedPaneId || maximizedPaneId === 'main';
23
+
24
+ if (showMain) {
25
+ const titleTopMargin = 10 * pixelToPercent;
26
+ graphic.push({
27
+ type: 'text',
28
+ left: '8.5%',
29
+ top: mainPaneTop + titleTopMargin + '%',
30
+ z: 10,
31
+ style: {
32
+ text: options.title || 'Market',
33
+ fill: options.titleColor || '#fff',
34
+ font: `bold 16px ${options.fontFamily || 'sans-serif'}`,
35
+ textVerticalAlign: 'top',
36
+ },
37
+ });
38
+
39
+ // Overlay Indicator Titles (stacked below main chart title)
40
+ if (overlayIndicators.length > 0) {
41
+ const mainTitleHeight = 20 * pixelToPercent; // 16px font + padding
42
+ const overlayLineHeight = 16 * pixelToPercent; // 12px font + 4px gap
43
+ overlayIndicators.forEach((overlay, i) => {
44
+ graphic.push({
45
+ type: 'text',
46
+ left: '8.5%',
47
+ top: mainPaneTop + titleTopMargin + mainTitleHeight + i * overlayLineHeight + '%',
48
+ z: 10,
49
+ style: {
50
+ text: overlay.id,
51
+ fill: overlay.titleColor || '#9e9e9e',
52
+ font: `bold 12px ${options.fontFamily || 'sans-serif'}`,
53
+ textVerticalAlign: 'top',
54
+ },
55
+ });
56
+ });
57
+ }
58
+
59
+ // Watermark
60
+ if (options.watermark !== false) {
61
+ const bottomY = layout.mainPaneTop + layout.mainPaneHeight;
62
+ graphic.push({
63
+ type: 'text',
64
+ right: '11%',
65
+ top: bottomY - 3 + '%', // Position 5% from bottom of main chart
66
+ z: 10,
67
+ style: {
68
+ text: 'QFChart',
69
+ fill: options.fontColor || '#cbd5e1',
70
+ font: `bold 16px sans-serif`,
71
+ opacity: 0.1,
72
+ },
73
+ cursor: 'pointer',
74
+ onclick: () => {
75
+ window.open('https://quantforge.org', '_blank');
76
+ },
77
+ });
78
+ }
79
+
80
+ // Main Controls Group
81
+ const controls: any[] = [];
82
+
83
+ // Collapse Button
84
+ if (options.controls?.collapse) {
85
+ controls.push({
86
+ type: 'group',
87
+ children: [
88
+ {
89
+ type: 'rect',
90
+ shape: { width: 20, height: 20, r: 2 },
91
+ style: { fill: '#334155', stroke: '#475569', lineWidth: 1 },
92
+ onclick: () => onToggle('main', 'collapse'),
93
+ },
94
+ {
95
+ type: 'text',
96
+ style: {
97
+ text: isMainCollapsed ? '+' : '−',
98
+ fill: '#cbd5e1',
99
+ font: `bold 14px ${options.fontFamily}`,
100
+ x: 10,
101
+ y: 10,
102
+ textAlign: 'center',
103
+ textVerticalAlign: 'middle',
104
+ },
105
+ silent: true,
106
+ },
107
+ ],
108
+ });
109
+ }
110
+
111
+ // Maximize Button
112
+ if (options.controls?.maximize) {
113
+ const isMaximized = maximizedPaneId === 'main';
114
+ // Shift x position if collapse button exists
115
+ const xOffset = options.controls?.collapse ? 25 : 0;
116
+
117
+ controls.push({
118
+ type: 'group',
119
+ x: xOffset,
120
+ children: [
121
+ {
122
+ type: 'rect',
123
+ shape: { width: 20, height: 20, r: 2 },
124
+ style: { fill: '#334155', stroke: '#475569', lineWidth: 1 },
125
+ onclick: () => onToggle('main', 'maximize'),
126
+ },
127
+ {
128
+ type: 'text',
129
+ style: {
130
+ text: isMaximized ? '' : '□', // Simple chars for now
131
+ fill: '#cbd5e1',
132
+ font: `14px ${options.fontFamily}`,
133
+ x: 10,
134
+ y: 10,
135
+ textAlign: 'center',
136
+ textVerticalAlign: 'middle',
137
+ },
138
+ silent: true,
139
+ },
140
+ ],
141
+ });
142
+ }
143
+
144
+ // Fullscreen Button
145
+ if (options.controls?.fullscreen) {
146
+ let xOffset = 0;
147
+ if (options.controls?.collapse) xOffset += 25;
148
+ if (options.controls?.maximize) xOffset += 25;
149
+
150
+ controls.push({
151
+ type: 'group',
152
+ x: xOffset,
153
+ children: [
154
+ {
155
+ type: 'rect',
156
+ shape: { width: 20, height: 20, r: 2 },
157
+ style: { fill: '#334155', stroke: '#475569', lineWidth: 1 },
158
+ onclick: () => onToggle('main', 'fullscreen'),
159
+ },
160
+ {
161
+ type: 'text',
162
+ style: {
163
+ text: '⛶',
164
+ fill: '#cbd5e1',
165
+ font: `14px ${options.fontFamily}`,
166
+ x: 10,
167
+ y: 10,
168
+ textAlign: 'center',
169
+ textVerticalAlign: 'middle',
170
+ },
171
+ silent: true,
172
+ },
173
+ ],
174
+ });
175
+ }
176
+
177
+ if (controls.length > 0) {
178
+ graphic.push({
179
+ type: 'group',
180
+ right: '10.5%',
181
+ top: mainPaneTop + '%',
182
+ children: controls,
183
+ });
184
+ }
185
+ }
186
+
187
+ // Indicator Panes
188
+ layout.paneLayout.forEach((pane) => {
189
+ // If maximizedPaneId is set, and this is NOT the maximized pane, skip rendering its controls
190
+ if (maximizedPaneId && pane.indicatorId !== maximizedPaneId) {
191
+ return;
192
+ }
193
+
194
+ // Title
195
+ graphic.push({
196
+ type: 'text',
197
+ left: '8.5%',
198
+ top: pane.top + 10 * pixelToPercent + '%',
199
+ z: 10,
200
+ style: {
201
+ text: pane.indicatorId || '',
202
+ fill: pane.titleColor || '#fff',
203
+ font: `bold 12px ${options.fontFamily || 'sans-serif'}`,
204
+ textVerticalAlign: 'top',
205
+ },
206
+ });
207
+
208
+ // Controls
209
+ const controls: any[] = [];
210
+
211
+ // Collapse
212
+ if (pane.controls?.collapse) {
213
+ controls.push({
214
+ type: 'group',
215
+ children: [
216
+ {
217
+ type: 'rect',
218
+ shape: { width: 20, height: 20, r: 2 },
219
+ style: { fill: '#334155', stroke: '#475569', lineWidth: 1 },
220
+ onclick: () => pane.indicatorId && onToggle(pane.indicatorId, 'collapse'),
221
+ },
222
+ {
223
+ type: 'text',
224
+ style: {
225
+ text: pane.isCollapsed ? '+' : '−',
226
+ fill: '#cbd5e1',
227
+ font: `bold 14px ${options.fontFamily}`,
228
+ x: 10,
229
+ y: 10,
230
+ textAlign: 'center',
231
+ textVerticalAlign: 'middle',
232
+ },
233
+ silent: true,
234
+ },
235
+ ],
236
+ });
237
+ }
238
+
239
+ // Maximize
240
+ if (pane.controls?.maximize) {
241
+ // Assuming we add maximize to Indicator controls
242
+ const isMaximized = maximizedPaneId === pane.indicatorId;
243
+ const xOffset = pane.controls?.collapse ? 25 : 0;
244
+
245
+ controls.push({
246
+ type: 'group',
247
+ x: xOffset,
248
+ children: [
249
+ {
250
+ type: 'rect',
251
+ shape: { width: 20, height: 20, r: 2 },
252
+ style: { fill: '#334155', stroke: '#475569', lineWidth: 1 },
253
+ onclick: () => pane.indicatorId && onToggle(pane.indicatorId, 'maximize'),
254
+ },
255
+ {
256
+ type: 'text',
257
+ style: {
258
+ text: isMaximized ? '❐' : '□',
259
+ fill: '#cbd5e1',
260
+ font: `14px ${options.fontFamily}`,
261
+ x: 10,
262
+ y: 10,
263
+ textAlign: 'center',
264
+ textVerticalAlign: 'middle',
265
+ },
266
+ silent: true,
267
+ },
268
+ ],
269
+ });
270
+ }
271
+
272
+ if (controls.length > 0) {
273
+ graphic.push({
274
+ type: 'group',
275
+ right: '10.5%',
276
+ top: pane.top + '%',
277
+ children: controls,
278
+ });
279
+ }
280
+ });
281
+
282
+ return graphic;
283
+ }
284
+ }