@tradingaction/interactive 2.0.13
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/LICENSE +24 -0
- package/README.md +5 -0
- package/lib/Brush.d.ts +35 -0
- package/lib/Brush.js +103 -0
- package/lib/Brush.js.map +1 -0
- package/lib/ClickCallback.d.ts +18 -0
- package/lib/ClickCallback.js +12 -0
- package/lib/ClickCallback.js.map +1 -0
- package/lib/DrawingObjectSelector.d.ts +18 -0
- package/lib/DrawingObjectSelector.js +73 -0
- package/lib/DrawingObjectSelector.js.map +1 -0
- package/lib/EquidistantChannel.d.ts +74 -0
- package/lib/EquidistantChannel.js +149 -0
- package/lib/EquidistantChannel.js.map +1 -0
- package/lib/FibonacciRetracement.d.ts +89 -0
- package/lib/FibonacciRetracement.js +184 -0
- package/lib/FibonacciRetracement.js.map +1 -0
- package/lib/GannFan.d.ts +78 -0
- package/lib/GannFan.js +128 -0
- package/lib/GannFan.js.map +1 -0
- package/lib/InteractiveText.d.ts +65 -0
- package/lib/InteractiveText.js +83 -0
- package/lib/InteractiveText.js.map +1 -0
- package/lib/InteractiveYCoordinate.d.ts +122 -0
- package/lib/InteractiveYCoordinate.js +100 -0
- package/lib/InteractiveYCoordinate.js.map +1 -0
- package/lib/StandardDeviationChannel.d.ts +74 -0
- package/lib/StandardDeviationChannel.js +125 -0
- package/lib/StandardDeviationChannel.js.map +1 -0
- package/lib/TrendLine.d.ts +84 -0
- package/lib/TrendLine.js +128 -0
- package/lib/TrendLine.js.map +1 -0
- package/lib/ZoomButtons.d.ts +30 -0
- package/lib/ZoomButtons.js +78 -0
- package/lib/ZoomButtons.js.map +1 -0
- package/lib/components/ChannelWithArea.d.ts +30 -0
- package/lib/components/ChannelWithArea.js +126 -0
- package/lib/components/ChannelWithArea.js.map +1 -0
- package/lib/components/ClickableCircle.d.ts +26 -0
- package/lib/components/ClickableCircle.js +48 -0
- package/lib/components/ClickableCircle.js.map +1 -0
- package/lib/components/ClickableShape.d.ts +33 -0
- package/lib/components/ClickableShape.js +64 -0
- package/lib/components/ClickableShape.js.map +1 -0
- package/lib/components/GannFan.d.ts +32 -0
- package/lib/components/GannFan.js +134 -0
- package/lib/components/GannFan.js.map +1 -0
- package/lib/components/HoverTextNearMouse.d.ts +35 -0
- package/lib/components/HoverTextNearMouse.js +113 -0
- package/lib/components/HoverTextNearMouse.js.map +1 -0
- package/lib/components/InteractiveStraightLine.d.ts +61 -0
- package/lib/components/InteractiveStraightLine.js +220 -0
- package/lib/components/InteractiveStraightLine.js.map +1 -0
- package/lib/components/InteractiveText.d.ts +38 -0
- package/lib/components/InteractiveText.js +84 -0
- package/lib/components/InteractiveText.js.map +1 -0
- package/lib/components/InteractiveYCoordinate.d.ts +46 -0
- package/lib/components/InteractiveYCoordinate.js +109 -0
- package/lib/components/InteractiveYCoordinate.js.map +1 -0
- package/lib/components/LinearRegressionChannelWithArea.d.ts +31 -0
- package/lib/components/LinearRegressionChannelWithArea.js +104 -0
- package/lib/components/LinearRegressionChannelWithArea.js.map +1 -0
- package/lib/components/MouseLocationIndicator.d.ts +34 -0
- package/lib/components/MouseLocationIndicator.js +79 -0
- package/lib/components/MouseLocationIndicator.js.map +1 -0
- package/lib/components/Text.d.ts +17 -0
- package/lib/components/Text.js +26 -0
- package/lib/components/Text.js.map +1 -0
- package/lib/components/index.d.ts +11 -0
- package/lib/components/index.js +12 -0
- package/lib/components/index.js.map +1 -0
- package/lib/index.d.ts +12 -0
- package/lib/index.js +13 -0
- package/lib/index.js.map +1 -0
- package/lib/utils.d.ts +7 -0
- package/lib/utils.js +84 -0
- package/lib/utils.js.map +1 -0
- package/lib/wrapper/EachEquidistantChannel.d.ts +59 -0
- package/lib/wrapper/EachEquidistantChannel.js +172 -0
- package/lib/wrapper/EachEquidistantChannel.js.map +1 -0
- package/lib/wrapper/EachFibRetracement.d.ts +81 -0
- package/lib/wrapper/EachFibRetracement.js +202 -0
- package/lib/wrapper/EachFibRetracement.js.map +1 -0
- package/lib/wrapper/EachGannFan.d.ts +81 -0
- package/lib/wrapper/EachGannFan.js +161 -0
- package/lib/wrapper/EachGannFan.js.map +1 -0
- package/lib/wrapper/EachInteractiveYCoordinate.d.ts +50 -0
- package/lib/wrapper/EachInteractiveYCoordinate.js +80 -0
- package/lib/wrapper/EachInteractiveYCoordinate.js.map +1 -0
- package/lib/wrapper/EachLinearRegressionChannel.d.ts +75 -0
- package/lib/wrapper/EachLinearRegressionChannel.js +89 -0
- package/lib/wrapper/EachLinearRegressionChannel.js.map +1 -0
- package/lib/wrapper/EachText.d.ts +59 -0
- package/lib/wrapper/EachText.js +83 -0
- package/lib/wrapper/EachText.js.map +1 -0
- package/lib/wrapper/EachTrendLine.d.ts +85 -0
- package/lib/wrapper/EachTrendLine.js +165 -0
- package/lib/wrapper/EachTrendLine.js.map +1 -0
- package/lib/wrapper/index.d.ts +7 -0
- package/lib/wrapper/index.js +8 -0
- package/lib/wrapper/index.js.map +1 -0
- package/package.json +53 -0
- package/src/Brush.tsx +172 -0
- package/src/ClickCallback.tsx +37 -0
- package/src/DrawingObjectSelector.tsx +97 -0
- package/src/EquidistantChannel.tsx +268 -0
- package/src/FibonacciRetracement.tsx +328 -0
- package/src/GannFan.tsx +233 -0
- package/src/InteractiveText.tsx +182 -0
- package/src/InteractiveYCoordinate.tsx +199 -0
- package/src/StandardDeviationChannel.tsx +269 -0
- package/src/TrendLine.tsx +294 -0
- package/src/ZoomButtons.tsx +165 -0
- package/src/components/ChannelWithArea.tsx +199 -0
- package/src/components/ClickableCircle.tsx +91 -0
- package/src/components/ClickableShape.tsx +114 -0
- package/src/components/GannFan.tsx +189 -0
- package/src/components/HoverTextNearMouse.tsx +174 -0
- package/src/components/InteractiveStraightLine.tsx +335 -0
- package/src/components/InteractiveText.tsx +146 -0
- package/src/components/InteractiveYCoordinate.tsx +200 -0
- package/src/components/LinearRegressionChannelWithArea.tsx +169 -0
- package/src/components/MouseLocationIndicator.tsx +128 -0
- package/src/components/Text.tsx +47 -0
- package/src/components/index.ts +11 -0
- package/src/index.ts +12 -0
- package/src/utils.ts +101 -0
- package/src/wrapper/EachEquidistantChannel.tsx +302 -0
- package/src/wrapper/EachFibRetracement.tsx +359 -0
- package/src/wrapper/EachGannFan.tsx +289 -0
- package/src/wrapper/EachInteractiveYCoordinate.tsx +198 -0
- package/src/wrapper/EachLinearRegressionChannel.tsx +202 -0
- package/src/wrapper/EachText.tsx +190 -0
- package/src/wrapper/EachTrendLine.tsx +331 -0
- package/src/wrapper/index.ts +7 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { pairs } from "d3-array";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { generateLine, isHovering2 } from "./InteractiveStraightLine";
|
|
4
|
+
import { isDefined, isNotDefined, getMouseCanvas, GenericChartComponent } from "@tradingaction/core";
|
|
5
|
+
|
|
6
|
+
export interface GannFanProps {
|
|
7
|
+
readonly startXY: number[];
|
|
8
|
+
readonly defaultClassName?: string;
|
|
9
|
+
readonly endXY: number[];
|
|
10
|
+
readonly fillStyle: Array<string>;
|
|
11
|
+
readonly fontFamily: string;
|
|
12
|
+
readonly fontSize: number;
|
|
13
|
+
readonly fontFill: string;
|
|
14
|
+
readonly interactiveCursorClass?: string;
|
|
15
|
+
readonly onDragStart?: (e: React.MouseEvent, moreProps: any) => void;
|
|
16
|
+
readonly onDrag?: (e: React.MouseEvent, moreProps: any) => void;
|
|
17
|
+
readonly onDragComplete?: (e: React.MouseEvent, moreProps: any) => void;
|
|
18
|
+
readonly onHover?: (e: React.MouseEvent, moreProps: any) => void;
|
|
19
|
+
readonly onUnHover?: (e: React.MouseEvent, moreProps: any) => void;
|
|
20
|
+
readonly selected: boolean;
|
|
21
|
+
readonly strokeStyle: string;
|
|
22
|
+
readonly strokeWidth: number;
|
|
23
|
+
readonly tolerance: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class GannFan extends React.Component<GannFanProps> {
|
|
27
|
+
public static defaultProps = {
|
|
28
|
+
strokeWidth: 1,
|
|
29
|
+
tolerance: 4,
|
|
30
|
+
selected: false,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
public render() {
|
|
34
|
+
const { selected, interactiveCursorClass } = this.props;
|
|
35
|
+
const { onDragStart, onDrag, onDragComplete, onHover, onUnHover } = this.props;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<GenericChartComponent
|
|
39
|
+
isHover={this.isHover}
|
|
40
|
+
canvasToDraw={getMouseCanvas}
|
|
41
|
+
canvasDraw={this.drawOnCanvas}
|
|
42
|
+
interactiveCursorClass={interactiveCursorClass}
|
|
43
|
+
selected={selected}
|
|
44
|
+
onDragStart={onDragStart}
|
|
45
|
+
onDrag={onDrag}
|
|
46
|
+
onDragComplete={onDragComplete}
|
|
47
|
+
onHover={onHover}
|
|
48
|
+
onUnHover={onUnHover}
|
|
49
|
+
drawOn={["mousemove", "mouseleave", "pan", "drag"]}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private readonly drawOnCanvas = (ctx: CanvasRenderingContext2D, moreProps: any) => {
|
|
55
|
+
const { strokeStyle, strokeWidth, fillStyle, fontFamily, fontSize, fontFill } = this.props;
|
|
56
|
+
|
|
57
|
+
const lines = this.helper(this.props, moreProps);
|
|
58
|
+
|
|
59
|
+
ctx.lineWidth = strokeWidth;
|
|
60
|
+
ctx.strokeStyle = strokeStyle;
|
|
61
|
+
ctx.font = `${fontSize}px ${fontFamily}`;
|
|
62
|
+
ctx.fillStyle = fontFill;
|
|
63
|
+
|
|
64
|
+
lines.forEach((line) => {
|
|
65
|
+
const { x1, y1, x2, y2, label } = line;
|
|
66
|
+
|
|
67
|
+
ctx.beginPath();
|
|
68
|
+
ctx.moveTo(x1, y1);
|
|
69
|
+
ctx.lineTo(x2, y2);
|
|
70
|
+
ctx.stroke();
|
|
71
|
+
ctx.beginPath();
|
|
72
|
+
ctx.fillText(label.text, label.x, label.y);
|
|
73
|
+
});
|
|
74
|
+
const pairsOfLines = pairs(lines);
|
|
75
|
+
|
|
76
|
+
pairsOfLines.forEach(([line1, line2], idx) => {
|
|
77
|
+
ctx.fillStyle = fillStyle[idx];
|
|
78
|
+
|
|
79
|
+
ctx.beginPath();
|
|
80
|
+
ctx.moveTo(line1.x1, line1.y1);
|
|
81
|
+
ctx.lineTo(line1.x2, line1.y2);
|
|
82
|
+
ctx.lineTo(line2.x2, line2.y2);
|
|
83
|
+
ctx.closePath();
|
|
84
|
+
ctx.fill();
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
private readonly isHover = (moreProps: any) => {
|
|
89
|
+
const { tolerance, onHover } = this.props;
|
|
90
|
+
const { mouseXY } = moreProps;
|
|
91
|
+
const [mouseX, mouseY] = mouseXY;
|
|
92
|
+
|
|
93
|
+
let hovering = false;
|
|
94
|
+
if (isDefined(onHover)) {
|
|
95
|
+
const lines = this.helper(this.props, moreProps);
|
|
96
|
+
|
|
97
|
+
// tslint:disable-next-line: prefer-for-of
|
|
98
|
+
for (let i = 0; i < lines.length; i++) {
|
|
99
|
+
const line1 = lines[i];
|
|
100
|
+
|
|
101
|
+
const left = Math.min(line1.x1, line1.x2);
|
|
102
|
+
const right = Math.max(line1.x1, line1.x2);
|
|
103
|
+
const top = Math.min(line1.y1, line1.y2);
|
|
104
|
+
const bottom = Math.max(line1.y1, line1.y2);
|
|
105
|
+
|
|
106
|
+
const isWithinLineBounds = mouseX >= left && mouseX <= right && mouseY >= top && mouseY <= bottom;
|
|
107
|
+
|
|
108
|
+
hovering =
|
|
109
|
+
isWithinLineBounds && isHovering2([line1.x1, line1.y1], [line1.x2, line1.y2], mouseXY, tolerance);
|
|
110
|
+
|
|
111
|
+
if (hovering) {
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return hovering;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
private readonly getLineCoordinates = (start: number[], endX: number, endY: number, text: string) => {
|
|
120
|
+
const end = [endX, endY];
|
|
121
|
+
return {
|
|
122
|
+
start,
|
|
123
|
+
end,
|
|
124
|
+
text,
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
private readonly helper = (props: GannFanProps, moreProps: any) => {
|
|
129
|
+
const { startXY, endXY } = props;
|
|
130
|
+
|
|
131
|
+
const {
|
|
132
|
+
xScale,
|
|
133
|
+
chartConfig: { yScale },
|
|
134
|
+
} = moreProps;
|
|
135
|
+
if (isNotDefined(startXY) || isNotDefined(endXY)) {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
const [x1, y1] = startXY;
|
|
139
|
+
const [x2, y2] = endXY;
|
|
140
|
+
|
|
141
|
+
const dx = x2 - x1;
|
|
142
|
+
const dy = y2 - y1;
|
|
143
|
+
|
|
144
|
+
if (dx !== 0 && dy !== 0) {
|
|
145
|
+
const halfY = this.getLineCoordinates(startXY, x2, y1 + dy / 2, "2/1");
|
|
146
|
+
const oneThirdY = this.getLineCoordinates(startXY, x2, y1 + dy / 3, "3/1");
|
|
147
|
+
const oneFourthY = this.getLineCoordinates(startXY, x2, y1 + dy / 4, "4/1");
|
|
148
|
+
const oneEighthY = this.getLineCoordinates(startXY, x2, y1 + dy / 8, "8/1");
|
|
149
|
+
const halfX = this.getLineCoordinates(startXY, x1 + dx / 2, y2, "1/2");
|
|
150
|
+
const oneThirdX = this.getLineCoordinates(startXY, x1 + dx / 3, y2, "1/3");
|
|
151
|
+
const oneFourthX = this.getLineCoordinates(startXY, x1 + dx / 4, y2, "1/4");
|
|
152
|
+
const oneEighthX = this.getLineCoordinates(startXY, x1 + dx / 8, y2, "1/8");
|
|
153
|
+
const lines = [
|
|
154
|
+
oneEighthX,
|
|
155
|
+
oneFourthX,
|
|
156
|
+
oneThirdX,
|
|
157
|
+
halfX,
|
|
158
|
+
{ start: startXY, end: endXY, text: "1/1" },
|
|
159
|
+
halfY,
|
|
160
|
+
oneThirdY,
|
|
161
|
+
oneFourthY,
|
|
162
|
+
oneEighthY,
|
|
163
|
+
];
|
|
164
|
+
const lineCoods = lines.map((line) => {
|
|
165
|
+
// tslint:disable-next-line: no-shadowed-variable
|
|
166
|
+
const { x1, y1, x2, y2 } = generateLine({
|
|
167
|
+
type: "RAY",
|
|
168
|
+
start: line.start,
|
|
169
|
+
end: line.end,
|
|
170
|
+
xScale,
|
|
171
|
+
yScale,
|
|
172
|
+
});
|
|
173
|
+
return {
|
|
174
|
+
x1: xScale(x1),
|
|
175
|
+
y1: yScale(y1),
|
|
176
|
+
x2: xScale(x2),
|
|
177
|
+
y2: yScale(y2),
|
|
178
|
+
label: {
|
|
179
|
+
x: xScale(line.end[0]),
|
|
180
|
+
y: yScale(line.end[1]),
|
|
181
|
+
text: line.text,
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
return lineCoods;
|
|
186
|
+
}
|
|
187
|
+
return [];
|
|
188
|
+
};
|
|
189
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { isDefined, GenericChartComponent } from "@tradingaction/core";
|
|
3
|
+
|
|
4
|
+
const PADDING = 10;
|
|
5
|
+
const MIN_WIDTH = PADDING;
|
|
6
|
+
|
|
7
|
+
export interface HoverTextNearMouseProps {
|
|
8
|
+
readonly fontFamily?: string;
|
|
9
|
+
readonly fontSize?: number;
|
|
10
|
+
readonly fill?: string;
|
|
11
|
+
readonly text?: string;
|
|
12
|
+
readonly bgFill?: string;
|
|
13
|
+
readonly bgOpacity?: number;
|
|
14
|
+
readonly bgWidth: number | string;
|
|
15
|
+
readonly bgHeight: number | string;
|
|
16
|
+
readonly show: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface HoverTextNearMouseState {
|
|
20
|
+
textHeight?: number;
|
|
21
|
+
textWidth?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class HoverTextNearMouse extends React.Component<HoverTextNearMouseProps, HoverTextNearMouseState> {
|
|
25
|
+
public static defaultProps = {
|
|
26
|
+
fontFamily: "-apple-system, system-ui, Roboto, 'Helvetica Neue', Ubuntu, sans-serif",
|
|
27
|
+
fontSize: 12,
|
|
28
|
+
fill: "#000000",
|
|
29
|
+
bgFill: "#FA9325",
|
|
30
|
+
bgOpacity: 0.5,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
private readonly textNode = React.createRef<SVGTextElement>();
|
|
34
|
+
|
|
35
|
+
public constructor(props: HoverTextNearMouseProps) {
|
|
36
|
+
super(props);
|
|
37
|
+
|
|
38
|
+
this.state = {
|
|
39
|
+
textWidth: undefined,
|
|
40
|
+
textHeight: undefined,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public componentDidMount() {
|
|
45
|
+
this.updateTextSize();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public componentDidUpdate() {
|
|
49
|
+
this.updateTextSize();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public render() {
|
|
53
|
+
const { text } = this.props;
|
|
54
|
+
if (text) {
|
|
55
|
+
return <GenericChartComponent svgDraw={this.renderSVG} drawOn={["mousemove"]} />;
|
|
56
|
+
} else {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private readonly renderSVG = (moreProps: any) => {
|
|
62
|
+
const { fontFamily, fontSize, fill, bgFill, bgOpacity } = this.props;
|
|
63
|
+
|
|
64
|
+
const textMetaData = helper(
|
|
65
|
+
{
|
|
66
|
+
...this.props,
|
|
67
|
+
bgWidth: this.getBgWidth(),
|
|
68
|
+
bgHeight: this.getBgHeight(),
|
|
69
|
+
},
|
|
70
|
+
moreProps,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if (textMetaData !== undefined && isDefined(textMetaData)) {
|
|
74
|
+
const { rect, text } = textMetaData;
|
|
75
|
+
return (
|
|
76
|
+
<g>
|
|
77
|
+
<rect fill={bgFill} fillOpacity={bgOpacity} stroke={bgFill} {...rect} />
|
|
78
|
+
<text
|
|
79
|
+
ref={this.textNode}
|
|
80
|
+
fontSize={fontSize}
|
|
81
|
+
fontFamily={fontFamily}
|
|
82
|
+
textAnchor="start"
|
|
83
|
+
alignmentBaseline={"central"}
|
|
84
|
+
fill={fill}
|
|
85
|
+
x={text.x}
|
|
86
|
+
y={text.y}
|
|
87
|
+
>
|
|
88
|
+
{text.text}
|
|
89
|
+
</text>
|
|
90
|
+
</g>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return null;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
private readonly getBgHeight = () => {
|
|
98
|
+
const { bgHeight } = this.props;
|
|
99
|
+
const { textHeight } = this.state;
|
|
100
|
+
|
|
101
|
+
if (bgHeight !== "auto") {
|
|
102
|
+
return bgHeight;
|
|
103
|
+
} else if (textHeight !== undefined) {
|
|
104
|
+
return textHeight + PADDING;
|
|
105
|
+
} else {
|
|
106
|
+
return MIN_WIDTH;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
private readonly getBgWidth = () => {
|
|
111
|
+
const { bgWidth } = this.props;
|
|
112
|
+
const { textWidth } = this.state;
|
|
113
|
+
|
|
114
|
+
if (bgWidth !== "auto") {
|
|
115
|
+
return bgWidth;
|
|
116
|
+
} else if (textWidth !== undefined) {
|
|
117
|
+
return textWidth + PADDING;
|
|
118
|
+
} else {
|
|
119
|
+
return MIN_WIDTH;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
private readonly updateTextSize = () => {
|
|
124
|
+
const { bgWidth, bgHeight } = this.props;
|
|
125
|
+
if (bgWidth === "auto" || bgHeight === "auto") {
|
|
126
|
+
const textNode = this.textNode.current;
|
|
127
|
+
if (textNode !== null) {
|
|
128
|
+
const { width, height } = textNode.getBBox();
|
|
129
|
+
if (this.state.textWidth !== width || this.state.textHeight !== height) {
|
|
130
|
+
this.setState({
|
|
131
|
+
textWidth: width,
|
|
132
|
+
textHeight: height,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function helper(props: any, moreProps: any) {
|
|
141
|
+
const { show, bgWidth, bgHeight } = props;
|
|
142
|
+
|
|
143
|
+
const {
|
|
144
|
+
mouseXY,
|
|
145
|
+
chartConfig: { height, width },
|
|
146
|
+
show: mouseInsideCanvas,
|
|
147
|
+
} = moreProps;
|
|
148
|
+
|
|
149
|
+
if (show && mouseInsideCanvas) {
|
|
150
|
+
const [x, y] = mouseXY;
|
|
151
|
+
|
|
152
|
+
const cx = x < width / 2 ? x + PADDING : x - bgWidth - PADDING;
|
|
153
|
+
|
|
154
|
+
const cy = y < height / 2 ? y + PADDING : y - bgHeight - PADDING;
|
|
155
|
+
|
|
156
|
+
const rect = {
|
|
157
|
+
x: cx,
|
|
158
|
+
y: cy,
|
|
159
|
+
width: bgWidth,
|
|
160
|
+
height: bgHeight,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const text = {
|
|
164
|
+
text: props.text,
|
|
165
|
+
x: cx + PADDING / 2,
|
|
166
|
+
y: cy + bgHeight / 2,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
rect,
|
|
171
|
+
text,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,335 @@
|
|
|
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 StraightLineProps {
|
|
11
|
+
readonly x1Value: any;
|
|
12
|
+
readonly x2Value: any;
|
|
13
|
+
readonly y1Value: any;
|
|
14
|
+
readonly y2Value: any;
|
|
15
|
+
readonly interactiveCursorClass?: string;
|
|
16
|
+
readonly strokeStyle: string;
|
|
17
|
+
readonly strokeWidth?: number;
|
|
18
|
+
readonly strokeDasharray?: strokeDashTypes;
|
|
19
|
+
readonly type:
|
|
20
|
+
| "XLINE" // extends from -Infinity to +Infinity
|
|
21
|
+
| "RAY" // extends to +/-Infinity in one direction
|
|
22
|
+
| "LINE"; // extends between the set bounds
|
|
23
|
+
readonly onEdge1Drag?: any; // func
|
|
24
|
+
readonly onEdge2Drag?: any; // func
|
|
25
|
+
readonly onDragStart?: (e: React.MouseEvent, moreProps: any) => void;
|
|
26
|
+
readonly onDrag?: (e: React.MouseEvent, moreProps: any) => void;
|
|
27
|
+
readonly onDragComplete?: (e: React.MouseEvent, moreProps: any) => void;
|
|
28
|
+
readonly onHover?: (e: React.MouseEvent, moreProps: any) => void;
|
|
29
|
+
readonly onUnHover?: (e: React.MouseEvent, moreProps: any) => void;
|
|
30
|
+
readonly onDoubleClickWhenHover?: (e: React.MouseEvent, moreProps: any) => void;
|
|
31
|
+
readonly onClickWhenHover?: (e: React.MouseEvent, moreProps: any) => void;
|
|
32
|
+
readonly onClickOutside?: (e: React.MouseEvent, moreProps: any) => void;
|
|
33
|
+
readonly onContextMenuWhenHover?: (e: React.MouseEvent, moreProps: any) => void;
|
|
34
|
+
readonly defaultClassName?: string;
|
|
35
|
+
readonly r?: number;
|
|
36
|
+
readonly edgeFill?: string;
|
|
37
|
+
readonly edgeStroke?: string;
|
|
38
|
+
readonly edgeStrokeWidth?: number;
|
|
39
|
+
readonly withEdge?: boolean;
|
|
40
|
+
readonly tolerance?: number;
|
|
41
|
+
readonly selected?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class InteractiveStraightLine extends React.Component<StraightLineProps> {
|
|
45
|
+
public static defaultProps = {
|
|
46
|
+
onEdge1Drag: noop,
|
|
47
|
+
onEdge2Drag: noop,
|
|
48
|
+
edgeStrokeWidth: 3,
|
|
49
|
+
edgeStroke: "#000000",
|
|
50
|
+
edgeFill: "#FFFFFF",
|
|
51
|
+
r: 10,
|
|
52
|
+
withEdge: false,
|
|
53
|
+
strokeWidth: 1,
|
|
54
|
+
strokeDasharray: "Solid",
|
|
55
|
+
children: noop,
|
|
56
|
+
tolerance: 7,
|
|
57
|
+
selected: false,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
public render() {
|
|
61
|
+
const { selected, interactiveCursorClass } = this.props;
|
|
62
|
+
const {
|
|
63
|
+
onDragStart,
|
|
64
|
+
onDrag,
|
|
65
|
+
onDragComplete,
|
|
66
|
+
onHover,
|
|
67
|
+
onUnHover,
|
|
68
|
+
onDoubleClickWhenHover,
|
|
69
|
+
onClickWhenHover,
|
|
70
|
+
onClickOutside,
|
|
71
|
+
onContextMenuWhenHover,
|
|
72
|
+
} = this.props;
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<GenericChartComponent
|
|
76
|
+
isHover={this.isHover}
|
|
77
|
+
canvasToDraw={getMouseCanvas}
|
|
78
|
+
canvasDraw={this.drawOnCanvas}
|
|
79
|
+
interactiveCursorClass={interactiveCursorClass}
|
|
80
|
+
selected={selected}
|
|
81
|
+
onDragStart={onDragStart}
|
|
82
|
+
onDrag={onDrag}
|
|
83
|
+
onDragComplete={onDragComplete}
|
|
84
|
+
onHover={onHover}
|
|
85
|
+
onUnHover={onUnHover}
|
|
86
|
+
drawOn={["mousemove", "pan", "drag"]}
|
|
87
|
+
onDoubleClickWhenHover={onDoubleClickWhenHover}
|
|
88
|
+
onClickWhenHover={onClickWhenHover}
|
|
89
|
+
onClickOutside={onClickOutside}
|
|
90
|
+
onContextMenuWhenHover={onContextMenuWhenHover}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private readonly isHover = (moreProps: any) => {
|
|
96
|
+
const { tolerance, onHover } = this.props;
|
|
97
|
+
|
|
98
|
+
if (onHover !== undefined) {
|
|
99
|
+
const { x1Value, x2Value, y1Value, y2Value, type } = this.props;
|
|
100
|
+
const { mouseXY, xScale } = moreProps;
|
|
101
|
+
const {
|
|
102
|
+
chartConfig: { yScale },
|
|
103
|
+
} = moreProps;
|
|
104
|
+
|
|
105
|
+
const hovering = isHovering({
|
|
106
|
+
x1Value,
|
|
107
|
+
y1Value,
|
|
108
|
+
x2Value,
|
|
109
|
+
y2Value,
|
|
110
|
+
mouseXY,
|
|
111
|
+
type,
|
|
112
|
+
tolerance,
|
|
113
|
+
xScale,
|
|
114
|
+
yScale,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return hovering;
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
private readonly drawOnCanvas = (ctx: CanvasRenderingContext2D, moreProps: any) => {
|
|
123
|
+
const {
|
|
124
|
+
strokeWidth = InteractiveStraightLine.defaultProps.strokeWidth,
|
|
125
|
+
strokeDasharray,
|
|
126
|
+
strokeStyle,
|
|
127
|
+
} = this.props;
|
|
128
|
+
const { x1, y1, x2, y2 } = helper(this.props, moreProps);
|
|
129
|
+
|
|
130
|
+
ctx.lineWidth = strokeWidth;
|
|
131
|
+
ctx.strokeStyle = strokeStyle;
|
|
132
|
+
|
|
133
|
+
const lineDash = getStrokeDasharrayCanvas(strokeDasharray);
|
|
134
|
+
|
|
135
|
+
ctx.setLineDash(lineDash);
|
|
136
|
+
|
|
137
|
+
ctx.beginPath();
|
|
138
|
+
ctx.moveTo(x1, y1);
|
|
139
|
+
ctx.lineTo(x2, y2);
|
|
140
|
+
ctx.stroke();
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function isHovering2(start: any, end: any, [mouseX, mouseY]: any, tolerance: any) {
|
|
145
|
+
const m = getSlope(start, end);
|
|
146
|
+
|
|
147
|
+
if (m !== undefined) {
|
|
148
|
+
const b = getYIntercept(m, end);
|
|
149
|
+
const y = m * mouseX + b;
|
|
150
|
+
return (
|
|
151
|
+
mouseY < y + tolerance &&
|
|
152
|
+
mouseY > y - tolerance &&
|
|
153
|
+
mouseX > Math.min(start[0], end[0]) - tolerance &&
|
|
154
|
+
mouseX < Math.max(start[0], end[0]) + tolerance
|
|
155
|
+
);
|
|
156
|
+
} else {
|
|
157
|
+
return (
|
|
158
|
+
mouseY >= Math.min(start[1], end[1]) &&
|
|
159
|
+
mouseY <= Math.max(start[1], end[1]) &&
|
|
160
|
+
mouseX < start[0] + tolerance &&
|
|
161
|
+
mouseX > start[0] - tolerance
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function isHovering({ x1Value, y1Value, x2Value, y2Value, mouseXY, type, tolerance, xScale, yScale }: any) {
|
|
167
|
+
const line = generateLine({
|
|
168
|
+
type,
|
|
169
|
+
start: [x1Value, y1Value],
|
|
170
|
+
end: [x2Value, y2Value],
|
|
171
|
+
xScale,
|
|
172
|
+
yScale,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const start = [xScale(line.x1), yScale(line.y1)];
|
|
176
|
+
const end = [xScale(line.x2), yScale(line.y2)];
|
|
177
|
+
|
|
178
|
+
const m = getSlope(start, end);
|
|
179
|
+
const [mouseX, mouseY] = mouseXY;
|
|
180
|
+
|
|
181
|
+
if (m !== undefined) {
|
|
182
|
+
const b = getYIntercept(m, end);
|
|
183
|
+
const y = m * mouseX + b;
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
mouseY < y + tolerance &&
|
|
187
|
+
mouseY > y - tolerance &&
|
|
188
|
+
mouseX > Math.min(start[0], end[0]) - tolerance &&
|
|
189
|
+
mouseX < Math.max(start[0], end[0]) + tolerance
|
|
190
|
+
);
|
|
191
|
+
} else {
|
|
192
|
+
return (
|
|
193
|
+
mouseY >= Math.min(start[1], end[1]) &&
|
|
194
|
+
mouseY <= Math.max(start[1], end[1]) &&
|
|
195
|
+
mouseX < start[0] + tolerance &&
|
|
196
|
+
mouseX > start[0] - tolerance
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function helper(props: any, moreProps: any) {
|
|
202
|
+
const { x1Value, x2Value, y1Value, y2Value, type } = props;
|
|
203
|
+
|
|
204
|
+
const {
|
|
205
|
+
xScale,
|
|
206
|
+
chartConfig: { yScale },
|
|
207
|
+
} = moreProps;
|
|
208
|
+
|
|
209
|
+
const modLine = generateLine({
|
|
210
|
+
type,
|
|
211
|
+
start: [x1Value, y1Value],
|
|
212
|
+
end: [x2Value, y2Value],
|
|
213
|
+
xScale,
|
|
214
|
+
yScale,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const x1 = xScale(modLine.x1);
|
|
218
|
+
const y1 = yScale(modLine.y1);
|
|
219
|
+
const x2 = xScale(modLine.x2);
|
|
220
|
+
const y2 = yScale(modLine.y2);
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
x1,
|
|
224
|
+
y1,
|
|
225
|
+
x2,
|
|
226
|
+
y2,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function getSlope(start: any, end: any) {
|
|
231
|
+
const m /* slope */ = end[0] === start[0] ? undefined : (end[1] - start[1]) / (end[0] - start[0]);
|
|
232
|
+
return m;
|
|
233
|
+
}
|
|
234
|
+
export function getYIntercept(m: any, end: any) {
|
|
235
|
+
const b /* y intercept */ = -1 * m * end[0] + end[1];
|
|
236
|
+
return b;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function generateLine({ type, start, end, xScale, yScale }: any) {
|
|
240
|
+
const m /* slope */ = getSlope(start, end);
|
|
241
|
+
const b /* y intercept */ = getYIntercept(m, start);
|
|
242
|
+
|
|
243
|
+
switch (type) {
|
|
244
|
+
case "XLINE":
|
|
245
|
+
return getXLineCoordinates({
|
|
246
|
+
start,
|
|
247
|
+
end,
|
|
248
|
+
xScale,
|
|
249
|
+
yScale,
|
|
250
|
+
m,
|
|
251
|
+
b,
|
|
252
|
+
});
|
|
253
|
+
case "RAY":
|
|
254
|
+
return getRayCoordinates({
|
|
255
|
+
start,
|
|
256
|
+
end,
|
|
257
|
+
xScale,
|
|
258
|
+
yScale,
|
|
259
|
+
m,
|
|
260
|
+
b,
|
|
261
|
+
});
|
|
262
|
+
default:
|
|
263
|
+
case "LINE":
|
|
264
|
+
return getLineCoordinates({
|
|
265
|
+
start,
|
|
266
|
+
end,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function getXLineCoordinates({ start, end, xScale, yScale, m, b }: any) {
|
|
272
|
+
const [xBegin, xFinish] = xScale.domain();
|
|
273
|
+
const [yBegin, yFinish] = yScale.domain();
|
|
274
|
+
|
|
275
|
+
if (end[0] === start[0]) {
|
|
276
|
+
return {
|
|
277
|
+
x1: end[0],
|
|
278
|
+
y1: yBegin,
|
|
279
|
+
x2: end[0],
|
|
280
|
+
y2: yFinish,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
const [x1, x2] = end[0] > start[0] ? [xBegin, xFinish] : [xFinish, xBegin];
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
x1,
|
|
287
|
+
y1: m * x1 + b,
|
|
288
|
+
x2,
|
|
289
|
+
y2: m * x2 + b,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function getRayCoordinates({ start, end, xScale, yScale, m, b }: any) {
|
|
294
|
+
const [xBegin, xFinish] = xScale.domain();
|
|
295
|
+
const [yBegin, yFinish] = yScale.domain();
|
|
296
|
+
|
|
297
|
+
const x1 = start[0];
|
|
298
|
+
if (end[0] === start[0]) {
|
|
299
|
+
return {
|
|
300
|
+
x1,
|
|
301
|
+
y1: start[1],
|
|
302
|
+
x2: x1,
|
|
303
|
+
y2: end[1] > start[1] ? yFinish : yBegin,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const x2 = end[0] > start[0] ? xFinish : xBegin;
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
x1,
|
|
311
|
+
y1: m * x1 + b,
|
|
312
|
+
x2,
|
|
313
|
+
y2: m * x2 + b,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function getLineCoordinates({ start, end }: any) {
|
|
318
|
+
const [x1, y1] = start;
|
|
319
|
+
const [x2, y2] = end;
|
|
320
|
+
if (end[0] === start[0]) {
|
|
321
|
+
return {
|
|
322
|
+
x1,
|
|
323
|
+
y1: start[1],
|
|
324
|
+
x2: x1,
|
|
325
|
+
y2: end[1],
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
x1,
|
|
331
|
+
y1,
|
|
332
|
+
x2,
|
|
333
|
+
y2,
|
|
334
|
+
};
|
|
335
|
+
}
|