@qfo/qfchart 0.7.3 → 0.8.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.
Files changed (59) hide show
  1. package/dist/index.d.ts +368 -14
  2. package/dist/qfchart.min.browser.js +34 -16
  3. package/dist/qfchart.min.es.js +34 -16
  4. package/package.json +1 -1
  5. package/src/QFChart.ts +460 -311
  6. package/src/components/AbstractPlugin.ts +234 -104
  7. package/src/components/DrawingEditor.ts +297 -248
  8. package/src/components/DrawingRendererRegistry.ts +13 -0
  9. package/src/components/GraphicBuilder.ts +284 -263
  10. package/src/components/LayoutManager.ts +72 -55
  11. package/src/components/SeriesBuilder.ts +110 -6
  12. package/src/components/TableCanvasRenderer.ts +467 -0
  13. package/src/components/TableOverlayRenderer.ts +38 -9
  14. package/src/components/TooltipFormatter.ts +97 -97
  15. package/src/components/renderers/BackgroundRenderer.ts +59 -47
  16. package/src/components/renderers/BoxRenderer.ts +113 -17
  17. package/src/components/renderers/FillRenderer.ts +118 -3
  18. package/src/components/renderers/LabelRenderer.ts +35 -9
  19. package/src/components/renderers/OHLCBarRenderer.ts +171 -161
  20. package/src/components/renderers/PolylineRenderer.ts +26 -19
  21. package/src/index.ts +17 -6
  22. package/src/plugins/ABCDPatternTool/ABCDPatternDrawingRenderer.ts +112 -0
  23. package/src/plugins/ABCDPatternTool/ABCDPatternTool.ts +136 -0
  24. package/src/plugins/ABCDPatternTool/index.ts +2 -0
  25. package/src/plugins/CypherPatternTool/CypherPatternDrawingRenderer.ts +80 -0
  26. package/src/plugins/CypherPatternTool/CypherPatternTool.ts +84 -0
  27. package/src/plugins/CypherPatternTool/index.ts +2 -0
  28. package/src/plugins/FibSpeedResistanceFanTool/FibSpeedResistanceFanDrawingRenderer.ts +163 -0
  29. package/src/plugins/FibSpeedResistanceFanTool/FibSpeedResistanceFanTool.ts +210 -0
  30. package/src/plugins/FibSpeedResistanceFanTool/index.ts +2 -0
  31. package/src/plugins/FibTrendExtensionTool/FibTrendExtensionDrawingRenderer.ts +141 -0
  32. package/src/plugins/FibTrendExtensionTool/FibTrendExtensionTool.ts +188 -0
  33. package/src/plugins/FibTrendExtensionTool/index.ts +2 -0
  34. package/src/plugins/FibonacciChannelTool/FibonacciChannelDrawingRenderer.ts +128 -0
  35. package/src/plugins/FibonacciChannelTool/FibonacciChannelTool.ts +231 -0
  36. package/src/plugins/FibonacciChannelTool/index.ts +2 -0
  37. package/src/plugins/FibonacciTool/FibonacciDrawingRenderer.ts +107 -0
  38. package/src/plugins/{FibonacciTool.ts → FibonacciTool/FibonacciTool.ts} +195 -192
  39. package/src/plugins/FibonacciTool/index.ts +2 -0
  40. package/src/plugins/HeadAndShouldersTool/HeadAndShouldersDrawingRenderer.ts +95 -0
  41. package/src/plugins/HeadAndShouldersTool/HeadAndShouldersTool.ts +97 -0
  42. package/src/plugins/HeadAndShouldersTool/index.ts +2 -0
  43. package/src/plugins/LineTool/LineDrawingRenderer.ts +49 -0
  44. package/src/plugins/{LineTool.ts → LineTool/LineTool.ts} +161 -190
  45. package/src/plugins/LineTool/index.ts +2 -0
  46. package/src/plugins/{MeasureTool.ts → MeasureTool/MeasureTool.ts} +324 -344
  47. package/src/plugins/MeasureTool/index.ts +1 -0
  48. package/src/plugins/ThreeDrivesPatternTool/ThreeDrivesPatternDrawingRenderer.ts +106 -0
  49. package/src/plugins/ThreeDrivesPatternTool/ThreeDrivesPatternTool.ts +98 -0
  50. package/src/plugins/ThreeDrivesPatternTool/index.ts +2 -0
  51. package/src/plugins/ToolGroup.ts +211 -0
  52. package/src/plugins/TrianglePatternTool/TrianglePatternDrawingRenderer.ts +107 -0
  53. package/src/plugins/TrianglePatternTool/TrianglePatternTool.ts +98 -0
  54. package/src/plugins/TrianglePatternTool/index.ts +2 -0
  55. package/src/plugins/XABCDPatternTool/XABCDPatternDrawingRenderer.ts +178 -0
  56. package/src/plugins/XABCDPatternTool/XABCDPatternTool.ts +213 -0
  57. package/src/plugins/XABCDPatternTool/index.ts +2 -0
  58. package/src/types.ts +39 -4
  59. package/src/utils/ColorUtils.ts +1 -1
@@ -0,0 +1,178 @@
1
+ import { DrawingRenderer, DrawingRenderContext } from '../../types';
2
+
3
+ const LABELS = ['X', 'A', 'B', 'C', 'D'];
4
+ const LEG_COLORS = ['#2196f3', '#ff9800', '#4caf50', '#f44336'];
5
+ const FILL_COLOR_1 = 'rgba(33, 150, 243, 0.08)';
6
+ const FILL_COLOR_2 = 'rgba(244, 67, 54, 0.08)';
7
+
8
+ export class XABCDPatternDrawingRenderer implements DrawingRenderer {
9
+ type = 'xabcd_pattern';
10
+
11
+ render(ctx: DrawingRenderContext): any {
12
+ const { drawing, pixelPoints, isSelected } = ctx;
13
+ const color = drawing.style?.color || '#3b82f6';
14
+
15
+ if (pixelPoints.length < 2) return;
16
+
17
+ const children: any[] = [];
18
+
19
+ // Fill triangles XAB and BCD
20
+ if (pixelPoints.length >= 3) {
21
+ children.push({
22
+ type: 'polygon',
23
+ name: 'line',
24
+ shape: {
25
+ points: pixelPoints.slice(0, 3).map(([x, y]) => [x, y]),
26
+ },
27
+ style: { fill: FILL_COLOR_1, opacity: 1 },
28
+ });
29
+ }
30
+ if (pixelPoints.length >= 5) {
31
+ children.push({
32
+ type: 'polygon',
33
+ name: 'line',
34
+ shape: {
35
+ points: pixelPoints.slice(2, 5).map(([x, y]) => [x, y]),
36
+ },
37
+ style: { fill: FILL_COLOR_2, opacity: 1 },
38
+ });
39
+ }
40
+
41
+ // Leg lines X→A→B→C→D
42
+ for (let i = 0; i < pixelPoints.length - 1; i++) {
43
+ const [x1, y1] = pixelPoints[i];
44
+ const [x2, y2] = pixelPoints[i + 1];
45
+ const legColor = LEG_COLORS[i % LEG_COLORS.length];
46
+
47
+ children.push({
48
+ type: 'line',
49
+ name: 'line',
50
+ shape: { x1, y1, x2, y2 },
51
+ style: { stroke: legColor, lineWidth: drawing.style?.lineWidth || 2 },
52
+ });
53
+ }
54
+
55
+ // Dashed connector lines: X→B, A→C, B→D (retrace references)
56
+ const connectors: [number, number][] = [[0, 2], [1, 3], [2, 4]];
57
+ for (const [from, to] of connectors) {
58
+ if (from < pixelPoints.length && to < pixelPoints.length) {
59
+ const [x1, y1] = pixelPoints[from];
60
+ const [x2, y2] = pixelPoints[to];
61
+ children.push({
62
+ type: 'line',
63
+ shape: { x1, y1, x2, y2 },
64
+ style: { stroke: '#555', lineWidth: 1, lineDash: [4, 4] },
65
+ silent: true,
66
+ });
67
+ }
68
+ }
69
+
70
+ // Fibonacci ratio labels on legs
71
+ if (drawing.points.length >= 3) {
72
+ // AB/XA ratio
73
+ const xa = Math.abs(drawing.points[1].value - drawing.points[0].value);
74
+ const ab = Math.abs(drawing.points[2].value - drawing.points[1].value);
75
+ if (xa !== 0) {
76
+ const ratio = (ab / xa).toFixed(3);
77
+ const mx = (pixelPoints[1][0] + pixelPoints[2][0]) / 2;
78
+ const my = (pixelPoints[1][1] + pixelPoints[2][1]) / 2;
79
+ children.push({
80
+ type: 'text',
81
+ style: { text: ratio, x: mx + 8, y: my, fill: '#ff9800', fontSize: 10 },
82
+ silent: true,
83
+ });
84
+ }
85
+ }
86
+ if (drawing.points.length >= 4) {
87
+ // BC/AB ratio
88
+ const ab = Math.abs(drawing.points[2].value - drawing.points[1].value);
89
+ const bc = Math.abs(drawing.points[3].value - drawing.points[2].value);
90
+ if (ab !== 0) {
91
+ const ratio = (bc / ab).toFixed(3);
92
+ const mx = (pixelPoints[2][0] + pixelPoints[3][0]) / 2;
93
+ const my = (pixelPoints[2][1] + pixelPoints[3][1]) / 2;
94
+ children.push({
95
+ type: 'text',
96
+ style: { text: ratio, x: mx + 8, y: my, fill: '#4caf50', fontSize: 10 },
97
+ silent: true,
98
+ });
99
+ }
100
+ }
101
+ if (drawing.points.length >= 5) {
102
+ // CD/BC ratio
103
+ const bc = Math.abs(drawing.points[3].value - drawing.points[2].value);
104
+ const cd = Math.abs(drawing.points[4].value - drawing.points[3].value);
105
+ if (bc !== 0) {
106
+ const ratio = (cd / bc).toFixed(3);
107
+ const mx = (pixelPoints[3][0] + pixelPoints[4][0]) / 2;
108
+ const my = (pixelPoints[3][1] + pixelPoints[4][1]) / 2;
109
+ children.push({
110
+ type: 'text',
111
+ style: { text: ratio, x: mx + 8, y: my, fill: '#f44336', fontSize: 10 },
112
+ silent: true,
113
+ });
114
+ }
115
+
116
+ // XA/AD ratio (overall retracement)
117
+ const xa = Math.abs(drawing.points[1].value - drawing.points[0].value);
118
+ const ad = Math.abs(drawing.points[4].value - drawing.points[1].value);
119
+ if (xa !== 0) {
120
+ const ratio = (ad / xa).toFixed(3);
121
+ // Place near D point
122
+ const [dx, dy] = pixelPoints[4];
123
+ children.push({
124
+ type: 'text',
125
+ style: { text: `AD/XA: ${ratio}`, x: dx + 10, y: dy + 14, fill: '#aaa', fontSize: 9 },
126
+ silent: true,
127
+ });
128
+ }
129
+ }
130
+
131
+ // Vertex labels (X, A, B, C, D)
132
+ for (let i = 0; i < pixelPoints.length && i < LABELS.length; i++) {
133
+ const [px, py] = pixelPoints[i];
134
+ // Place label above or below the point based on neighbors
135
+ const isLocalHigh =
136
+ (i === 0 || py <= pixelPoints[i - 1][1]) &&
137
+ (i === pixelPoints.length - 1 || py <= pixelPoints[i + 1]?.[1]);
138
+ const labelY = isLocalHigh ? py - 14 : py + 16;
139
+
140
+ children.push({
141
+ type: 'text',
142
+ style: {
143
+ text: LABELS[i],
144
+ x: px,
145
+ y: labelY,
146
+ fill: '#e2e8f0',
147
+ fontSize: 12,
148
+ fontWeight: 'bold',
149
+ align: 'center',
150
+ verticalAlign: 'middle',
151
+ },
152
+ silent: true,
153
+ });
154
+ }
155
+
156
+ // Control points
157
+ for (let i = 0; i < pixelPoints.length; i++) {
158
+ const [px, py] = pixelPoints[i];
159
+ children.push({
160
+ type: 'circle',
161
+ name: `point-${i}`,
162
+ shape: { cx: px, cy: py, r: 4 },
163
+ style: {
164
+ fill: '#fff',
165
+ stroke: color,
166
+ lineWidth: 1,
167
+ opacity: isSelected ? 1 : 0,
168
+ },
169
+ z: 100,
170
+ });
171
+ }
172
+
173
+ return {
174
+ type: 'group',
175
+ children,
176
+ };
177
+ }
178
+ }
@@ -0,0 +1,213 @@
1
+ import * as echarts from 'echarts';
2
+ import { AbstractPlugin } from '../../components/AbstractPlugin';
3
+ import { XABCDPatternDrawingRenderer } from './XABCDPatternDrawingRenderer';
4
+
5
+ const LABELS = ['X', 'A', 'B', 'C', 'D'];
6
+ const LEG_COLORS = ['#2196f3', '#ff9800', '#4caf50', '#f44336'];
7
+ const TOTAL_POINTS = 5;
8
+
9
+ export class XABCDPatternTool extends AbstractPlugin {
10
+ private points: number[][] = [];
11
+ private state: 'idle' | 'drawing' | 'finished' = 'idle';
12
+ private graphicGroup: any = null;
13
+
14
+ constructor(options: { name?: string; icon?: string } = {}) {
15
+ super({
16
+ id: 'xabcd-pattern-tool',
17
+ name: options.name || 'XABCD Pattern',
18
+ icon:
19
+ options.icon ||
20
+ `<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#e3e3e3" stroke-width="1.5"><polyline points="2,18 6,6 11,14 16,4 21,16"/><circle cx="2" cy="18" r="1.5" fill="#e3e3e3"/><circle cx="6" cy="6" r="1.5" fill="#e3e3e3"/><circle cx="11" cy="14" r="1.5" fill="#e3e3e3"/><circle cx="16" cy="4" r="1.5" fill="#e3e3e3"/><circle cx="21" cy="16" r="1.5" fill="#e3e3e3"/></svg>`,
21
+ });
22
+ }
23
+
24
+ protected onInit(): void {
25
+ this.context.registerDrawingRenderer(new XABCDPatternDrawingRenderer());
26
+ }
27
+
28
+ protected onActivate(): void {
29
+ this.state = 'idle';
30
+ this.points = [];
31
+ this.context.getChart().getZr().setCursorStyle('crosshair');
32
+ this.bindEvents();
33
+ }
34
+
35
+ protected onDeactivate(): void {
36
+ this.state = 'idle';
37
+ this.points = [];
38
+ this.removeGraphic();
39
+ this.unbindEvents();
40
+ this.context.getChart().getZr().setCursorStyle('default');
41
+ }
42
+
43
+ private bindEvents() {
44
+ const zr = this.context.getChart().getZr();
45
+ zr.on('click', this.onClick);
46
+ zr.on('mousemove', this.onMouseMove);
47
+ }
48
+
49
+ private unbindEvents() {
50
+ const zr = this.context.getChart().getZr();
51
+ zr.off('click', this.onClick);
52
+ zr.off('mousemove', this.onMouseMove);
53
+ }
54
+
55
+ private onClick = (params: any) => {
56
+ const pt = this.getPoint(params);
57
+
58
+ if (this.state === 'idle') {
59
+ this.state = 'drawing';
60
+ this.points = [pt, [...pt]]; // First point + cursor preview
61
+ this.initGraphic();
62
+ this.updateGraphic();
63
+ } else if (this.state === 'drawing') {
64
+ // Replace the preview point with the confirmed click
65
+ this.points[this.points.length - 1] = pt;
66
+
67
+ if (this.points.length >= TOTAL_POINTS) {
68
+ // All 5 points placed
69
+ this.state = 'finished';
70
+ this.updateGraphic();
71
+ this.saveDrawing();
72
+ this.removeGraphic();
73
+ this.context.disableTools();
74
+ } else {
75
+ // Add a new preview point for the next position
76
+ this.points.push([...pt]);
77
+ this.updateGraphic();
78
+ }
79
+ }
80
+ };
81
+
82
+ private onMouseMove = (params: any) => {
83
+ if (this.state !== 'drawing' || this.points.length < 2) return;
84
+ // Update the last (preview) point
85
+ this.points[this.points.length - 1] = this.getPoint(params);
86
+ this.updateGraphic();
87
+ };
88
+
89
+ private initGraphic() {
90
+ this.graphicGroup = new echarts.graphic.Group();
91
+ this.context.getChart().getZr().add(this.graphicGroup);
92
+ }
93
+
94
+ private removeGraphic() {
95
+ if (this.graphicGroup) {
96
+ this.context.getChart().getZr().remove(this.graphicGroup);
97
+ this.graphicGroup = null;
98
+ }
99
+ }
100
+
101
+ private updateGraphic() {
102
+ if (!this.graphicGroup) return;
103
+ this.graphicGroup.removeAll();
104
+
105
+ const pts = this.points;
106
+
107
+ // Fill triangles
108
+ if (pts.length >= 3) {
109
+ this.graphicGroup.add(
110
+ new echarts.graphic.Polygon({
111
+ shape: { points: pts.slice(0, 3) },
112
+ style: { fill: 'rgba(33, 150, 243, 0.08)' },
113
+ silent: true,
114
+ }),
115
+ );
116
+ }
117
+ if (pts.length >= 5) {
118
+ this.graphicGroup.add(
119
+ new echarts.graphic.Polygon({
120
+ shape: { points: pts.slice(2, 5) },
121
+ style: { fill: 'rgba(244, 67, 54, 0.08)' },
122
+ silent: true,
123
+ }),
124
+ );
125
+ }
126
+
127
+ // Leg lines
128
+ for (let i = 0; i < pts.length - 1; i++) {
129
+ const [x1, y1] = pts[i];
130
+ const [x2, y2] = pts[i + 1];
131
+ this.graphicGroup.add(
132
+ new echarts.graphic.Line({
133
+ shape: { x1, y1, x2, y2 },
134
+ style: { stroke: LEG_COLORS[i % LEG_COLORS.length], lineWidth: 2 },
135
+ silent: true,
136
+ }),
137
+ );
138
+ }
139
+
140
+ // Dashed connectors X→B, A→C, B→D
141
+ const connectors: [number, number][] = [[0, 2], [1, 3], [2, 4]];
142
+ for (const [from, to] of connectors) {
143
+ if (from < pts.length && to < pts.length) {
144
+ const [x1, y1] = pts[from];
145
+ const [x2, y2] = pts[to];
146
+ this.graphicGroup.add(
147
+ new echarts.graphic.Line({
148
+ shape: { x1, y1, x2, y2 },
149
+ style: { stroke: '#555', lineWidth: 1, lineDash: [4, 4] },
150
+ silent: true,
151
+ }),
152
+ );
153
+ }
154
+ }
155
+
156
+ // Vertex labels
157
+ for (let i = 0; i < pts.length && i < LABELS.length; i++) {
158
+ const [px, py] = pts[i];
159
+ const isLocalHigh =
160
+ (i === 0 || py <= pts[i - 1][1]) &&
161
+ (i === pts.length - 1 || py <= pts[i + 1]?.[1]);
162
+ const labelY = isLocalHigh ? py - 14 : py + 16;
163
+
164
+ this.graphicGroup.add(
165
+ new echarts.graphic.Text({
166
+ style: {
167
+ text: LABELS[i],
168
+ x: px,
169
+ y: labelY,
170
+ fill: '#e2e8f0',
171
+ fontSize: 12,
172
+ fontWeight: 'bold',
173
+ align: 'center',
174
+ verticalAlign: 'middle',
175
+ },
176
+ silent: true,
177
+ }),
178
+ );
179
+ }
180
+
181
+ // Point circles
182
+ for (let i = 0; i < pts.length; i++) {
183
+ const [px, py] = pts[i];
184
+ this.graphicGroup.add(
185
+ new echarts.graphic.Circle({
186
+ shape: { cx: px, cy: py, r: 4 },
187
+ style: { fill: '#fff', stroke: '#3b82f6', lineWidth: 1.5 },
188
+ z: 101,
189
+ silent: true,
190
+ }),
191
+ );
192
+ }
193
+ }
194
+
195
+ private saveDrawing() {
196
+ const dataPoints = this.points.map((pt) =>
197
+ this.context.coordinateConversion.pixelToData({ x: pt[0], y: pt[1] }),
198
+ );
199
+
200
+ if (dataPoints.every((p) => p !== null)) {
201
+ this.context.addDrawing({
202
+ id: `xabcd-${Date.now()}`,
203
+ type: 'xabcd_pattern',
204
+ points: dataPoints as any[],
205
+ paneIndex: dataPoints[0]!.paneIndex || 0,
206
+ style: {
207
+ color: '#3b82f6',
208
+ lineWidth: 2,
209
+ },
210
+ });
211
+ }
212
+ }
213
+ }
@@ -0,0 +1,2 @@
1
+ export { XABCDPatternTool } from './XABCDPatternTool';
2
+ export { XABCDPatternDrawingRenderer } from './XABCDPatternDrawingRenderer';
package/src/types.ts CHANGED
@@ -106,6 +106,7 @@ export interface QFChartOptions {
106
106
  };
107
107
  dataZoom?: {
108
108
  visible?: boolean;
109
+ pannable?: boolean; // Keep pan/drag when visible=false (default true)
109
110
  position?: 'top' | 'bottom';
110
111
  height?: number; // height in %, default 6
111
112
  start?: number; // 0-100, default 50
@@ -116,9 +117,18 @@ export interface QFChartOptions {
116
117
  position: 'floating' | 'left' | 'right';
117
118
  triggerOn?: 'mousemove' | 'click' | 'none'; // When to show tooltip/crosshair, default 'mousemove'
118
119
  };
120
+ grid?: {
121
+ show?: boolean; // Show/hide split lines (default true)
122
+ lineColor?: string; // Split line color (default '#334155')
123
+ lineOpacity?: number; // Split line opacity (default 0.5 main, 0.3 indicator panes)
124
+ borderColor?: string; // Axis line color (default '#334155')
125
+ borderShow?: boolean; // Show/hide axis border lines (default true)
126
+ };
119
127
  layout?: {
120
- mainPaneHeight: string; // e.g. "60%"
121
- gap: number; // e.g. 5 (percent)
128
+ mainPaneHeight?: string; // e.g. "60%"
129
+ gap?: number; // Gap between panes in % (default ~5)
130
+ left?: string; // Grid left margin (default '10%')
131
+ right?: string; // Grid right margin (default '10%')
122
132
  };
123
133
  watermark?: boolean; // Default true
124
134
  }
@@ -167,14 +177,20 @@ export interface ChartContext {
167
177
  // Interaction Locking
168
178
  lockChart(): void;
169
179
  unlockChart(): void;
180
+
181
+ // Drawing Renderer Registration
182
+ registerDrawingRenderer(renderer: DrawingRenderer): void;
183
+
184
+ // Snap to nearest candle OHLC value
185
+ snapToCandle(point: Coordinate): Coordinate;
170
186
  }
171
187
 
172
- export type DrawingType = 'line' | 'fibonacci';
188
+ export type DrawingType = string;
173
189
 
174
190
  export interface DrawingElement {
175
191
  id: string;
176
192
  type: DrawingType;
177
- points: DataCoordinate[]; // [start, end]
193
+ points: DataCoordinate[];
178
194
  paneIndex?: number; // Pane where this drawing belongs (default 0)
179
195
  style?: {
180
196
  color?: string;
@@ -182,6 +198,25 @@ export interface DrawingElement {
182
198
  };
183
199
  }
184
200
 
201
+ // Drawing Renderer System
202
+
203
+ export interface DrawingRenderContext {
204
+ drawing: DrawingElement;
205
+ /** Pixel coords for each point, in the same order as drawing.points */
206
+ pixelPoints: [number, number][];
207
+ /** Whether this drawing is currently selected */
208
+ isSelected: boolean;
209
+ /** The ECharts custom series api object */
210
+ api: any;
211
+ }
212
+
213
+ export interface DrawingRenderer {
214
+ /** The drawing type this renderer handles */
215
+ type: string;
216
+ /** Return an ECharts custom series renderItem group element */
217
+ render(ctx: DrawingRenderContext): any;
218
+ }
219
+
185
220
  export interface PluginConfig {
186
221
  id: string;
187
222
  name?: string;
@@ -4,7 +4,7 @@ export class ColorUtils {
4
4
  * Supports: hex (#RRGGBB, #RRGGBBAA), named colors (green, red), rgba(r,g,b,a), rgb(r,g,b)
5
5
  */
6
6
  public static parseColor(colorStr: string): { color: string; opacity: number } {
7
- if (!colorStr) {
7
+ if (!colorStr || typeof colorStr !== 'string') {
8
8
  return { color: '#888888', opacity: 0.2 };
9
9
  }
10
10