@tradingaction/interactive 2.0.13 → 2.0.16

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.
@@ -0,0 +1,401 @@
1
+ import {
2
+ getStrokeDasharrayCanvas,
3
+ getMouseCanvas,
4
+ GenericChartComponent,
5
+ noop,
6
+ strokeDashTypes,
7
+ } from "@tradingaction/core";
8
+ import * as React from "react";
9
+
10
+ export interface RectangleProps {
11
+ readonly topLeftX: any;
12
+ readonly topRightX: any;
13
+ readonly topLeftY: any;
14
+ readonly topRightY: any;
15
+
16
+ readonly bottomLeftX: any;
17
+ readonly bottomLeftY: any;
18
+ readonly bottomRightX: any;
19
+ readonly bottomRightY: any;
20
+
21
+ readonly interactiveCursorClass?: string;
22
+ readonly strokeStyle: string;
23
+ readonly strokeWidth?: number;
24
+ readonly strokeDasharray?: strokeDashTypes;
25
+ readonly type:
26
+ | "XLINE" // extends from -Infinity to +Infinity
27
+ | "RAY" // extends to +/-Infinity in one direction
28
+ | "LINE"; // extends between the set bounds
29
+ readonly onEdge1Drag?: any; // func
30
+ readonly onEdge2Drag?: any; // func
31
+ readonly onDragStart?: (e: React.MouseEvent, moreProps: any) => void;
32
+ readonly onDrag?: (e: React.MouseEvent, moreProps: any) => void;
33
+ readonly onDragComplete?: (e: React.MouseEvent, moreProps: any) => void;
34
+ readonly onHover?: (e: React.MouseEvent, moreProps: any) => void;
35
+ readonly onUnHover?: (e: React.MouseEvent, moreProps: any) => void;
36
+ readonly onDoubleClickWhenHover?: (e: React.MouseEvent, moreProps: any) => void;
37
+ readonly onClickWhenHover?: (e: React.MouseEvent, moreProps: any) => void;
38
+ readonly onClickOutside?: (e: React.MouseEvent, moreProps: any) => void;
39
+ readonly onContextMenuWhenHover?: (e: React.MouseEvent, moreProps: any) => void;
40
+ readonly defaultClassName?: string;
41
+ readonly r?: number;
42
+ readonly edgeFill?: string;
43
+ readonly edgeStroke?: string;
44
+ readonly edgeStrokeWidth?: number;
45
+ readonly rectangleFill?: string;
46
+ readonly withEdge?: boolean;
47
+ readonly tolerance?: number;
48
+ readonly selected?: boolean;
49
+ }
50
+
51
+ export class InteractiveRectangle extends React.Component<RectangleProps> {
52
+ public static defaultProps = {
53
+ onEdge1Drag: noop,
54
+ onEdge2Drag: noop,
55
+ edgeStrokeWidth: 3,
56
+ edgeStroke: "#000000",
57
+ edgeFill: "#FFFFFF",
58
+ rectangleFill: 'transparent',
59
+ r: 10,
60
+ withEdge: false,
61
+ strokeWidth: 1,
62
+ strokeDasharray: "Solid",
63
+ children: noop,
64
+ tolerance: 7,
65
+ selected: false,
66
+ };
67
+
68
+ public render() {
69
+ const { selected, interactiveCursorClass } = this.props;
70
+ const {
71
+ onDragStart,
72
+ onDrag,
73
+ onDragComplete,
74
+ onHover,
75
+ onUnHover,
76
+ onDoubleClickWhenHover,
77
+ onClickWhenHover,
78
+ onClickOutside,
79
+ onContextMenuWhenHover,
80
+ } = this.props;
81
+
82
+ return (
83
+ <GenericChartComponent
84
+ isHover={this.isHover}
85
+ canvasToDraw={getMouseCanvas}
86
+ canvasDraw={this.drawOnCanvas}
87
+ interactiveCursorClass={interactiveCursorClass}
88
+ selected={selected}
89
+ onDragStart={onDragStart}
90
+ onDrag={onDrag}
91
+ onDragComplete={onDragComplete}
92
+ onHover={onHover}
93
+ onUnHover={onUnHover}
94
+ drawOn={["mousemove", "pan", "drag"]}
95
+ onDoubleClickWhenHover={onDoubleClickWhenHover}
96
+ onClickWhenHover={onClickWhenHover}
97
+ onClickOutside={onClickOutside}
98
+ onContextMenuWhenHover={onContextMenuWhenHover}
99
+ />
100
+ );
101
+ }
102
+
103
+ private readonly isHover = (moreProps: any) => {
104
+ const { tolerance, onHover } = this.props;
105
+
106
+ if (onHover !== undefined) {
107
+ const { topLeftX, topRightX, topLeftY, topRightY,
108
+ bottomLeftX, bottomLeftY, bottomRightX, bottomRightY,
109
+ type } = this.props;
110
+ const { mouseXY, xScale } = moreProps;
111
+ const {
112
+ chartConfig: { yScale },
113
+ } = moreProps;
114
+
115
+ const hovering = isHovering({
116
+ topLeftX,
117
+ topLeftY,
118
+ topRightX,
119
+ topRightY,
120
+ bottomLeftX, bottomLeftY, bottomRightX, bottomRightY,
121
+ mouseXY,
122
+ type,
123
+ tolerance,
124
+ xScale,
125
+ yScale,
126
+ });
127
+
128
+ return hovering;
129
+ }
130
+ return false;
131
+ };
132
+
133
+ private readonly drawOnCanvas = (ctx: CanvasRenderingContext2D, moreProps: any) => {
134
+ const {
135
+ strokeWidth = InteractiveRectangle.defaultProps.strokeWidth,
136
+ strokeDasharray,
137
+ strokeStyle,
138
+ rectangleFill
139
+ } = this.props;
140
+ const {
141
+ x1, y1, // top-left
142
+ x2, y2, // top-right
143
+ x1Bottom, y1Bottom, // bottom-left
144
+ x2Bottom, y2Bottom, // bottom-right
145
+ } = helper(this.props, moreProps);
146
+
147
+ // Set canvas styles
148
+ ctx.lineWidth = strokeWidth;
149
+ ctx.strokeStyle = strokeStyle || "transparent"; // Default to no border
150
+ ctx.fillStyle = rectangleFill || "rgba(0, 255, 0, 0.2)"; // Default fill color
151
+ ctx.setLineDash(getStrokeDasharrayCanvas(strokeDasharray));
152
+
153
+ // Begin rectangle path
154
+ ctx.beginPath();
155
+
156
+ // Start at top-left
157
+ ctx.moveTo(x1, y1);
158
+
159
+ // Draw to top-right
160
+ ctx.lineTo(x2, y2);
161
+
162
+ // Draw to bottom-right
163
+ ctx.lineTo(x2Bottom, y2Bottom);
164
+
165
+ // Draw to bottom-left
166
+ ctx.lineTo(x1Bottom, y1Bottom);
167
+
168
+ // Close the path back to top-left
169
+ ctx.closePath();
170
+
171
+ // ✅ Fill the rectangle
172
+ ctx.fill();
173
+
174
+ // ✅ Stroke the outline (optional)
175
+ if (strokeStyle !== "transparent" && strokeWidth > 0) {
176
+ ctx.stroke();
177
+ }
178
+
179
+ };
180
+ }
181
+
182
+ function isHovering({ topLeftX, topLeftY, topRightX, topRightY, bottomLeftX, bottomLeftY, bottomRightX, bottomRightY, mouseXY, type, tolerance, xScale, yScale }: any) {
183
+ const line = generateRectangle({
184
+ type,
185
+ start: [topLeftX, topLeftY],
186
+ end: [topRightX, topRightY],
187
+ startBottom: [bottomLeftX, bottomLeftY],
188
+ endBottom: [bottomRightX, bottomRightY],
189
+ xScale,
190
+ yScale,
191
+ });
192
+
193
+ const start = [xScale(line.x1), yScale(line.y1)];
194
+ const end = [xScale(line.x2), yScale(line.y2)];
195
+
196
+ const m = getSlope(start, end);
197
+ const [mouseX, mouseY] = mouseXY;
198
+
199
+ if (m !== undefined) {
200
+ const b = getYIntercept(m, end);
201
+ const y = m * mouseX + b;
202
+
203
+ return (
204
+ mouseY < y + tolerance &&
205
+ mouseY > y - tolerance &&
206
+ mouseX > Math.min(start[0], end[0]) - tolerance &&
207
+ mouseX < Math.max(start[0], end[0]) + tolerance
208
+ );
209
+ } else {
210
+ return (
211
+ mouseY >= Math.min(start[1], end[1]) &&
212
+ mouseY <= Math.max(start[1], end[1]) &&
213
+ mouseX < start[0] + tolerance &&
214
+ mouseX > start[0] - tolerance
215
+ );
216
+ }
217
+ }
218
+
219
+ function helper(props: any, moreProps: any) {
220
+ const { topLeftX, topRightX, topLeftY, topRightY, bottomLeftX, bottomLeftY, bottomRightX, bottomRightY, type } = props;
221
+
222
+ const {
223
+ xScale,
224
+ chartConfig: { yScale },
225
+ } = moreProps;
226
+
227
+ const modLine = generateRectangle({
228
+ type,
229
+ start: [topLeftX, topLeftY],
230
+ end: [topRightX, topRightY],
231
+ startBottom: [bottomLeftX, bottomLeftY],
232
+ endBottom: [bottomRightX, bottomRightY],
233
+ xScale,
234
+ yScale,
235
+ });
236
+
237
+ const x1 = xScale(modLine.x1);
238
+ const y1 = yScale(modLine.y1);
239
+ const x2 = xScale(modLine.x2);
240
+ const y2 = yScale(modLine.y2);
241
+
242
+ const x1Bottom = xScale(modLine.x1Bottom);
243
+ const y1Bottom = yScale(modLine.y1Bottom);
244
+ const x2Bottom = xScale(modLine.x2Bottom);
245
+ const y2Bottom = yScale(modLine.y2Bottom);
246
+
247
+ return {
248
+ x1,//Top (from Trendline cloning process)
249
+ y1,//Top (from Trendline cloning process)
250
+ x2,//Top (from Trendline cloning process)
251
+ y2,//Top (from Trendline cloning process)
252
+
253
+ x1Bottom,
254
+ y1Bottom,
255
+ x2Bottom,
256
+ y2Bottom,
257
+ };
258
+ }
259
+
260
+ function getSlope(start: any, end: any) {
261
+ const m /* slope */ = end[0] === start[0] ? undefined : (end[1] - start[1]) / (end[0] - start[0]);
262
+ return m;
263
+ }
264
+ function getYIntercept(m: any, end: any) {
265
+ const b /* y intercept */ = -1 * m * end[0] + end[1];
266
+ return b;
267
+ }
268
+
269
+ export function generateRectangle({ type, start, end, startBottom, endBottom, xScale, yScale }: any) {
270
+ const m /* slope */ = getSlope(start, end);
271
+ const b /* y intercept */ = getYIntercept(m, start);
272
+
273
+ switch (type) {
274
+ case "XLINE":
275
+ return getXLineCoordinates({
276
+ start,
277
+ end,
278
+ startBottom, endBottom,
279
+ xScale,
280
+ yScale,
281
+ m,
282
+ b,
283
+ });
284
+ case "RAY":
285
+ return getRayCoordinates({
286
+ start,
287
+ end,
288
+ startBottom, endBottom,
289
+ xScale,
290
+ yScale,
291
+ m,
292
+ b,
293
+ });
294
+ default:
295
+ case "LINE":
296
+ return getLineCoordinates({
297
+ start,
298
+ end,
299
+ startBottom, endBottom,
300
+ });
301
+ }
302
+ }
303
+
304
+ function getXLineCoordinates({ start, end, startBottom, endBottom, xScale, yScale, m, b }: any) {
305
+ const [xBegin, xFinish] = xScale.domain();
306
+ const [yBegin, yFinish] = yScale.domain();
307
+
308
+ if (end[0] === start[0]) {
309
+ return {
310
+ x1: end[0],
311
+ y1: yBegin,
312
+ x2: end[0],
313
+ y2: yFinish,
314
+
315
+ x1Bottom: 0,//Todo next
316
+ y1Bottom: 0,
317
+ x2Bottom: 0,
318
+ y2Bottom: 0,
319
+
320
+ };
321
+ }
322
+ const [x1, x2] = end[0] > start[0] ? [xBegin, xFinish] : [xFinish, xBegin];
323
+
324
+ return {
325
+ x1,
326
+ y1: m * x1 + b,
327
+ x2,
328
+ y2: m * x2 + b,
329
+
330
+ x1Bottom: 0,//Todo next
331
+ y1Bottom: 0,
332
+ x2Bottom: 0,
333
+ y2Bottom: 0,
334
+ };
335
+ }
336
+
337
+ function getRayCoordinates({ start, end, startBottom, endBottom, xScale, yScale, m, b }: any) {
338
+ const [xBegin, xFinish] = xScale.domain();
339
+ const [yBegin, yFinish] = yScale.domain();
340
+
341
+ const x1 = start[0];
342
+ if (end[0] === start[0]) {
343
+ return {
344
+ x1,
345
+ y1: start[1],
346
+ x2: x1,
347
+ y2: end[1] > start[1] ? yFinish : yBegin,
348
+
349
+ x1Bottom: 0,//Todo next
350
+ y1Bottom: 0,
351
+ x2Bottom: 0,
352
+ y2Bottom: 0,
353
+ };
354
+ }
355
+
356
+ const x2 = end[0] > start[0] ? xFinish : xBegin;
357
+
358
+ return {
359
+ x1,
360
+ y1: m * x1 + b,
361
+ x2,
362
+ y2: m * x2 + b,
363
+
364
+ x1Bottom: 0,//Todo next
365
+ y1Bottom: 0,
366
+ x2Bottom: 0,
367
+ y2Bottom: 0,
368
+ };
369
+ }
370
+
371
+ function getLineCoordinates({ start, end, startBottom, endBottom, }: any) {
372
+ const [x1, y1] = start;
373
+ const [x2, y2] = end;
374
+ const [x1Bottom, y1Bottom] = startBottom;
375
+ const [x2Bottom, y2Bottom] = endBottom;
376
+ if (end[0] === start[0] || endBottom[0] === startBottom[0]) {
377
+ return {
378
+ x1,
379
+ y1: start[1],
380
+ x2: x1,
381
+ y2: end[1],
382
+
383
+ x1Bottom,
384
+ y1Bottom: startBottom[1],
385
+ x2Bottom,
386
+ y2Bottom: endBottom[1],
387
+ };
388
+ }
389
+
390
+ return {
391
+ x1,
392
+ y1,
393
+ x2,
394
+ y2,
395
+
396
+ x1Bottom,
397
+ y1Bottom,
398
+ x2Bottom,
399
+ y2Bottom,
400
+ };
401
+ }
@@ -4,6 +4,7 @@ export * from "./ClickableShape";
4
4
  export * from "./GannFan";
5
5
  export * from "./HoverTextNearMouse";
6
6
  export * from "./InteractiveStraightLine";
7
+ export * from "./InteractiveRectangle";
7
8
  export * from "./InteractiveText";
8
9
  export * from "./InteractiveYCoordinate";
9
10
  export * from "./LinearRegressionChannelWithArea";
package/src/index.ts CHANGED
@@ -10,3 +10,4 @@ export { InteractiveYCoordinate } from "./InteractiveYCoordinate";
10
10
  export { DrawingObjectSelector } from "./DrawingObjectSelector";
11
11
  export { ZoomButtons } from "./ZoomButtons";
12
12
  export * from "./utils";
13
+ export { Rectangle } from "./Rectangle";