@tradingaction/tooltip 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/BollingerBandTooltip.d.ts +40 -0
- package/lib/BollingerBandTooltip.js +48 -0
- package/lib/BollingerBandTooltip.js.map +1 -0
- package/lib/GroupTooltip.d.ts +41 -0
- package/lib/GroupTooltip.js +80 -0
- package/lib/GroupTooltip.js.map +1 -0
- package/lib/HoverTooltip.d.ts +57 -0
- package/lib/HoverTooltip.js +174 -0
- package/lib/HoverTooltip.js.map +1 -0
- package/lib/MACDTooltip.d.ts +46 -0
- package/lib/MACDTooltip.js +58 -0
- package/lib/MACDTooltip.js.map +1 -0
- package/lib/MovingAverageTooltip.d.ts +65 -0
- package/lib/MovingAverageTooltip.js +71 -0
- package/lib/MovingAverageTooltip.js.map +1 -0
- package/lib/OHLCTooltip.d.ts +65 -0
- package/lib/OHLCTooltip.js +78 -0
- package/lib/OHLCTooltip.js.map +1 -0
- package/lib/RSITooltip.d.ts +32 -0
- package/lib/RSITooltip.js +36 -0
- package/lib/RSITooltip.js.map +1 -0
- package/lib/SingleTooltip.d.ts +29 -0
- package/lib/SingleTooltip.js +82 -0
- package/lib/SingleTooltip.js.map +1 -0
- package/lib/SingleValueTooltip.d.ts +46 -0
- package/lib/SingleValueTooltip.js +65 -0
- package/lib/SingleValueTooltip.js.map +1 -0
- package/lib/StochasticTooltip.d.ts +43 -0
- package/lib/StochasticTooltip.js +44 -0
- package/lib/StochasticTooltip.js.map +1 -0
- package/lib/ToolTipTSpanLabel.d.ts +8 -0
- package/lib/ToolTipTSpanLabel.js +23 -0
- package/lib/ToolTipTSpanLabel.js.map +1 -0
- package/lib/ToolTipText.d.ts +9 -0
- package/lib/ToolTipText.js +24 -0
- package/lib/ToolTipText.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/package.json +51 -0
- package/src/BollingerBandTooltip.tsx +99 -0
- package/src/GroupTooltip.tsx +152 -0
- package/src/HoverTooltip.tsx +270 -0
- package/src/MACDTooltip.tsx +116 -0
- package/src/MovingAverageTooltip.tsx +167 -0
- package/src/OHLCTooltip.tsx +150 -0
- package/src/RSITooltip.tsx +80 -0
- package/src/SingleTooltip.tsx +122 -0
- package/src/SingleValueTooltip.tsx +131 -0
- package/src/StochasticTooltip.tsx +97 -0
- package/src/ToolTipTSpanLabel.tsx +14 -0
- package/src/ToolTipText.tsx +15 -0
- package/src/index.ts +12 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { max, sum } from "d3-array";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { ChartCanvasContext, first, GenericComponent, isDefined, last } from "@tradingaction/core";
|
|
4
|
+
|
|
5
|
+
const PADDING = 4;
|
|
6
|
+
const X = 8;
|
|
7
|
+
const Y = 8;
|
|
8
|
+
|
|
9
|
+
const roundRect = (
|
|
10
|
+
ctx: CanvasRenderingContext2D,
|
|
11
|
+
x: number,
|
|
12
|
+
y: number,
|
|
13
|
+
width: number,
|
|
14
|
+
height: number,
|
|
15
|
+
radius: number,
|
|
16
|
+
) => {
|
|
17
|
+
ctx.beginPath();
|
|
18
|
+
ctx.moveTo(x + radius, y);
|
|
19
|
+
ctx.lineTo(x + width - radius, y);
|
|
20
|
+
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
|
21
|
+
ctx.lineTo(x + width, y + height - radius);
|
|
22
|
+
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
|
23
|
+
ctx.lineTo(x + radius, y + height);
|
|
24
|
+
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
|
25
|
+
ctx.lineTo(x, y + radius);
|
|
26
|
+
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
27
|
+
ctx.closePath();
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const defaultBackgroundShapeCanvas = (
|
|
31
|
+
props: HoverTooltipProps,
|
|
32
|
+
{ width, height }: { width: number; height: number },
|
|
33
|
+
ctx: CanvasRenderingContext2D,
|
|
34
|
+
) => {
|
|
35
|
+
const { toolTipFillStyle, toolTipStrokeStyle } = props;
|
|
36
|
+
|
|
37
|
+
ctx.beginPath();
|
|
38
|
+
roundRect(ctx, 0, 0, width, height, 4);
|
|
39
|
+
if (toolTipFillStyle !== undefined) {
|
|
40
|
+
ctx.fillStyle = toolTipFillStyle;
|
|
41
|
+
ctx.shadowColor = "#898";
|
|
42
|
+
ctx.shadowBlur = 4;
|
|
43
|
+
ctx.fill();
|
|
44
|
+
ctx.shadowBlur = 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (toolTipStrokeStyle !== undefined) {
|
|
48
|
+
ctx.strokeStyle = toolTipStrokeStyle;
|
|
49
|
+
ctx.stroke();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const defaultTooltipCanvas = (props: HoverTooltipProps, content: any, ctx: CanvasRenderingContext2D) => {
|
|
54
|
+
const { fontSize = 14, fontFamily, fontFill } = props;
|
|
55
|
+
|
|
56
|
+
const startY = Y + fontSize * 0.9;
|
|
57
|
+
ctx.font = `bold ${fontSize}px ${fontFamily}`;
|
|
58
|
+
if (fontFill !== undefined) {
|
|
59
|
+
ctx.fillStyle = fontFill;
|
|
60
|
+
}
|
|
61
|
+
ctx.textAlign = "left";
|
|
62
|
+
ctx.fillText(content.x, X, startY);
|
|
63
|
+
|
|
64
|
+
const maxLabel = max(content.y, (y: any) => ctx.measureText(y.label as string).width) ?? 0;
|
|
65
|
+
|
|
66
|
+
for (let i = 0; i < content.y.length; i++) {
|
|
67
|
+
const y = content.y[i];
|
|
68
|
+
const textY = (i + 1) * PADDING + startY + fontSize * (i + 1);
|
|
69
|
+
ctx.font = `${fontSize}px ${fontFamily}`;
|
|
70
|
+
ctx.fillStyle = y.stroke ?? fontFill;
|
|
71
|
+
ctx.fillText(y.label, X, textY);
|
|
72
|
+
|
|
73
|
+
if (fontFill !== undefined) {
|
|
74
|
+
ctx.fillStyle = fontFill;
|
|
75
|
+
}
|
|
76
|
+
ctx.fillText(y.value, X * 2 + maxLabel, textY);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const drawOnCanvas = (
|
|
81
|
+
ctx: CanvasRenderingContext2D,
|
|
82
|
+
props: HoverTooltipProps,
|
|
83
|
+
context: any,
|
|
84
|
+
pointer: any,
|
|
85
|
+
height: number,
|
|
86
|
+
) => {
|
|
87
|
+
const { margin, ratio } = context;
|
|
88
|
+
const { backgroundShapeCanvas, tooltipCanvas, background } = props;
|
|
89
|
+
|
|
90
|
+
const originX = 0.5 * ratio + margin.left;
|
|
91
|
+
const originY = 0.5 * ratio + margin.top;
|
|
92
|
+
|
|
93
|
+
ctx.save();
|
|
94
|
+
|
|
95
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
96
|
+
ctx.scale(ratio, ratio);
|
|
97
|
+
|
|
98
|
+
ctx.translate(originX, originY);
|
|
99
|
+
|
|
100
|
+
const { x, y, content, centerX, pointWidth, bgSize } = pointer;
|
|
101
|
+
|
|
102
|
+
if (background?.fillStyle !== undefined) {
|
|
103
|
+
ctx.fillStyle = background.fillStyle;
|
|
104
|
+
}
|
|
105
|
+
ctx.beginPath();
|
|
106
|
+
ctx.rect(centerX - pointWidth / 2, 0, pointWidth, height);
|
|
107
|
+
ctx.fill();
|
|
108
|
+
|
|
109
|
+
ctx.translate(x, y);
|
|
110
|
+
|
|
111
|
+
backgroundShapeCanvas(props, bgSize, ctx);
|
|
112
|
+
|
|
113
|
+
tooltipCanvas(props, content, ctx);
|
|
114
|
+
|
|
115
|
+
ctx.restore();
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const calculateTooltipSize = (props: HoverTooltipProps, content: any, ctx: CanvasRenderingContext2D) => {
|
|
119
|
+
const { fontFamily, fontSize = 12, fontFill } = props;
|
|
120
|
+
|
|
121
|
+
ctx.font = `bold ${fontSize}px ${fontFamily}`;
|
|
122
|
+
if (fontFill !== undefined) {
|
|
123
|
+
ctx.fillStyle = fontFill;
|
|
124
|
+
}
|
|
125
|
+
ctx.textAlign = "left";
|
|
126
|
+
|
|
127
|
+
const measureText = (str: string) => ({
|
|
128
|
+
width: ctx.measureText(str).width,
|
|
129
|
+
height: fontSize + PADDING,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const { width, height } = content.y
|
|
133
|
+
.map(({ label, value }: any) => measureText(`${label} ${value}`))
|
|
134
|
+
// Sum all y and x sizes (begin with x label size)
|
|
135
|
+
.reduce((res: any, size: any) => sumSizes(res, size), measureText(String(content.x)));
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
width: width + 2 * X,
|
|
139
|
+
height: height + 2 * Y,
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const sumSizes = (...sizes: any[]) => {
|
|
144
|
+
return {
|
|
145
|
+
width: Math.max(...sizes.map((size) => size.width)),
|
|
146
|
+
height: sum(sizes, (d) => d.height),
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const normalizeX = (x: number, bgSize: any, pointWidth: number, width: number) => {
|
|
151
|
+
return x < width / 2 ? x + pointWidth / 2 + PADDING : x - bgSize.width - pointWidth / 2 - PADDING;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const normalizeY = (y: number, bgSize: any) => {
|
|
155
|
+
return y - bgSize.height <= 0 ? y + PADDING : y - bgSize.height - PADDING;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const defaultOrigin = (props: HoverTooltipProps, moreProps: any, bgSize: any, pointWidth: any) => {
|
|
159
|
+
const { chartId, yAccessor } = props;
|
|
160
|
+
|
|
161
|
+
const { mouseXY, xAccessor, currentItem, xScale, chartConfig, width } = moreProps;
|
|
162
|
+
|
|
163
|
+
let y = last(mouseXY);
|
|
164
|
+
|
|
165
|
+
const xValue = xAccessor(currentItem);
|
|
166
|
+
|
|
167
|
+
let x = Math.round(xScale(xValue));
|
|
168
|
+
|
|
169
|
+
if (isDefined(chartId) && isDefined(yAccessor) && isDefined(chartConfig) && isDefined(chartConfig.findIndex)) {
|
|
170
|
+
const yValue = yAccessor(currentItem);
|
|
171
|
+
const chartIndex = chartConfig.findIndex((c: any) => c.id === chartId);
|
|
172
|
+
|
|
173
|
+
y = Math.round(chartConfig[chartIndex].yScale(yValue));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
x = normalizeX(x, bgSize, pointWidth, width);
|
|
177
|
+
y = normalizeY(y, bgSize);
|
|
178
|
+
|
|
179
|
+
return [x, y];
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export interface HoverTooltipProps {
|
|
183
|
+
readonly background?: {
|
|
184
|
+
fillStyle?: string;
|
|
185
|
+
height?: number;
|
|
186
|
+
strokeStyle?: string;
|
|
187
|
+
width?: number;
|
|
188
|
+
};
|
|
189
|
+
readonly backgroundShapeCanvas: (
|
|
190
|
+
props: HoverTooltipProps,
|
|
191
|
+
{ width, height }: { width: number; height: number },
|
|
192
|
+
ctx: CanvasRenderingContext2D,
|
|
193
|
+
) => void;
|
|
194
|
+
readonly chartId?: number | string;
|
|
195
|
+
readonly fontFamily?: string;
|
|
196
|
+
readonly fontFill?: string;
|
|
197
|
+
readonly fontSize?: number;
|
|
198
|
+
readonly origin?: (
|
|
199
|
+
props: HoverTooltipProps,
|
|
200
|
+
moreProps: any,
|
|
201
|
+
bgSize: { width: number; height: number },
|
|
202
|
+
pointWidth: number,
|
|
203
|
+
) => [number, number];
|
|
204
|
+
readonly tooltip: {
|
|
205
|
+
content: (data: any) => { x: string; y: { label: string; value?: string; stroke?: string }[] };
|
|
206
|
+
};
|
|
207
|
+
readonly toolTipFillStyle?: string;
|
|
208
|
+
readonly toolTipStrokeStyle?: string;
|
|
209
|
+
readonly tooltipCanvas: (props: HoverTooltipProps, content: any, ctx: CanvasRenderingContext2D) => void;
|
|
210
|
+
readonly yAccessor: (data: any) => number;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export class HoverTooltip extends React.Component<HoverTooltipProps> {
|
|
214
|
+
public static defaultProps = {
|
|
215
|
+
background: {
|
|
216
|
+
fillStyle: "rgba(33, 148, 243, 0.1)",
|
|
217
|
+
},
|
|
218
|
+
toolTipFillStyle: "rgba(250, 250, 250, 1)",
|
|
219
|
+
toolTipStrokeStyle: "rgba(33, 148, 243)",
|
|
220
|
+
tooltipCanvas: defaultTooltipCanvas,
|
|
221
|
+
origin: defaultOrigin,
|
|
222
|
+
backgroundShapeCanvas: defaultBackgroundShapeCanvas,
|
|
223
|
+
fontFill: "#000000",
|
|
224
|
+
fontFamily: "-apple-system, system-ui, Roboto, 'Helvetica Neue', Ubuntu, sans-serif",
|
|
225
|
+
fontSize: 14,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
public static contextType = ChartCanvasContext;
|
|
229
|
+
|
|
230
|
+
public render() {
|
|
231
|
+
return <GenericComponent canvasDraw={this.drawOnCanvas} drawOn={["mousemove", "pan"]} />;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private readonly drawOnCanvas = (ctx: CanvasRenderingContext2D, moreProps: any) => {
|
|
235
|
+
const pointer = this.helper(ctx, moreProps);
|
|
236
|
+
if (pointer === undefined) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const { height } = moreProps;
|
|
241
|
+
|
|
242
|
+
drawOnCanvas(ctx, this.props, this.context, pointer, height);
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
private readonly helper = (ctx: CanvasRenderingContext2D, moreProps: any) => {
|
|
246
|
+
const { show, xScale, currentItem, plotData, xAccessor, displayXAccessor } = moreProps;
|
|
247
|
+
|
|
248
|
+
const { origin = HoverTooltip.defaultProps.origin, tooltip } = this.props;
|
|
249
|
+
|
|
250
|
+
if (!show || currentItem === undefined) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const xValue = xAccessor(currentItem);
|
|
255
|
+
if (xValue === undefined) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const content = tooltip.content({ currentItem, xAccessor: displayXAccessor });
|
|
260
|
+
const centerX = xScale(xValue);
|
|
261
|
+
const pointWidth =
|
|
262
|
+
Math.abs(xScale(xAccessor(last(plotData))) - xScale(xAccessor(first(plotData)))) / (plotData.length - 1);
|
|
263
|
+
|
|
264
|
+
const bgSize = calculateTooltipSize(this.props, content, ctx);
|
|
265
|
+
|
|
266
|
+
const [x, y] = origin(this.props, moreProps, bgSize, pointWidth);
|
|
267
|
+
|
|
268
|
+
return { x, y, content, centerX, pointWidth, bgSize };
|
|
269
|
+
};
|
|
270
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { functor, GenericChartComponent, last } from "@tradingaction/core";
|
|
2
|
+
import { format } from "d3-format";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { ToolTipText } from "./ToolTipText";
|
|
5
|
+
import { ToolTipTSpanLabel } from "./ToolTipTSpanLabel";
|
|
6
|
+
|
|
7
|
+
export interface MACDTooltipProps {
|
|
8
|
+
readonly origin: number[] | ((width: number, height: number) => [number, number]);
|
|
9
|
+
readonly className?: string;
|
|
10
|
+
readonly fontFamily?: string;
|
|
11
|
+
readonly fontSize?: number;
|
|
12
|
+
readonly fontWeight?: number;
|
|
13
|
+
readonly labelFill?: string;
|
|
14
|
+
readonly labelFontWeight?: number;
|
|
15
|
+
readonly onClick?: (event: React.MouseEvent) => void;
|
|
16
|
+
readonly options: {
|
|
17
|
+
slow: number;
|
|
18
|
+
fast: number;
|
|
19
|
+
signal: number;
|
|
20
|
+
};
|
|
21
|
+
readonly appearance: {
|
|
22
|
+
strokeStyle: {
|
|
23
|
+
macd: string;
|
|
24
|
+
signal: string;
|
|
25
|
+
};
|
|
26
|
+
fillStyle: {
|
|
27
|
+
divergence: string;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
readonly displayFormat: (value: number) => string;
|
|
31
|
+
readonly displayInit?: string;
|
|
32
|
+
readonly displayValuesFor: (props: MACDTooltipProps, moreProps: any) => any | undefined;
|
|
33
|
+
readonly yAccessor: (data: any) => { macd: number; signal: number; divergence: number };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class MACDTooltip extends React.Component<MACDTooltipProps> {
|
|
37
|
+
public static defaultProps = {
|
|
38
|
+
className: "react-financial-charts-tooltip",
|
|
39
|
+
displayFormat: format(".2f"),
|
|
40
|
+
displayInit: "n/a",
|
|
41
|
+
displayValuesFor: (_: any, props: any) => props.currentItem,
|
|
42
|
+
origin: [0, 0],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
public render() {
|
|
46
|
+
return <GenericChartComponent clip={false} svgDraw={this.renderSVG} drawOn={["mousemove"]} />;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private readonly renderSVG = (moreProps: any) => {
|
|
50
|
+
const {
|
|
51
|
+
onClick,
|
|
52
|
+
displayInit,
|
|
53
|
+
fontFamily,
|
|
54
|
+
fontSize,
|
|
55
|
+
fontWeight,
|
|
56
|
+
displayValuesFor,
|
|
57
|
+
displayFormat,
|
|
58
|
+
className,
|
|
59
|
+
yAccessor,
|
|
60
|
+
options,
|
|
61
|
+
origin: originProp,
|
|
62
|
+
appearance,
|
|
63
|
+
labelFill,
|
|
64
|
+
labelFontWeight,
|
|
65
|
+
} = this.props;
|
|
66
|
+
|
|
67
|
+
const {
|
|
68
|
+
chartConfig: { width, height },
|
|
69
|
+
fullData,
|
|
70
|
+
} = moreProps;
|
|
71
|
+
|
|
72
|
+
const currentItem = displayValuesFor(this.props, moreProps) ?? last(fullData);
|
|
73
|
+
|
|
74
|
+
const macdValue = currentItem && yAccessor(currentItem);
|
|
75
|
+
|
|
76
|
+
const macd = (macdValue?.macd && displayFormat(macdValue.macd)) || displayInit;
|
|
77
|
+
const signal = (macdValue?.signal && displayFormat(macdValue.signal)) || displayInit;
|
|
78
|
+
const divergence = (macdValue?.divergence && displayFormat(macdValue.divergence)) || displayInit;
|
|
79
|
+
|
|
80
|
+
const origin = functor(originProp);
|
|
81
|
+
const [x, y] = origin(width, height);
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<g className={className} transform={`translate(${x}, ${y})`} onClick={onClick}>
|
|
85
|
+
<ToolTipText x={0} y={0} fontFamily={fontFamily} fontSize={fontSize} fontWeight={fontWeight}>
|
|
86
|
+
<ToolTipTSpanLabel fill={labelFill} fontWeight={labelFontWeight}>
|
|
87
|
+
MACD (
|
|
88
|
+
</ToolTipTSpanLabel>
|
|
89
|
+
<tspan fill={appearance.strokeStyle.macd}>{options.slow}</tspan>
|
|
90
|
+
<ToolTipTSpanLabel fill={labelFill} fontWeight={labelFontWeight}>
|
|
91
|
+
,{" "}
|
|
92
|
+
</ToolTipTSpanLabel>
|
|
93
|
+
<tspan fill={appearance.strokeStyle.macd}>{options.fast}</tspan>
|
|
94
|
+
<ToolTipTSpanLabel fill={labelFill} fontWeight={labelFontWeight}>
|
|
95
|
+
):{" "}
|
|
96
|
+
</ToolTipTSpanLabel>
|
|
97
|
+
<tspan fill={appearance.strokeStyle.macd}>{macd}</tspan>
|
|
98
|
+
<ToolTipTSpanLabel fill={labelFill} fontWeight={labelFontWeight}>
|
|
99
|
+
{" "}
|
|
100
|
+
Signal (
|
|
101
|
+
</ToolTipTSpanLabel>
|
|
102
|
+
<tspan fill={appearance.strokeStyle.signal}>{options.signal}</tspan>
|
|
103
|
+
<ToolTipTSpanLabel fill={labelFill} fontWeight={labelFontWeight}>
|
|
104
|
+
):{" "}
|
|
105
|
+
</ToolTipTSpanLabel>
|
|
106
|
+
<tspan fill={appearance.strokeStyle.signal}>{signal}</tspan>
|
|
107
|
+
<ToolTipTSpanLabel fill={labelFill} fontWeight={labelFontWeight}>
|
|
108
|
+
{" "}
|
|
109
|
+
Divergence:{" "}
|
|
110
|
+
</ToolTipTSpanLabel>
|
|
111
|
+
<tspan fill={appearance.fillStyle.divergence}>{divergence}</tspan>
|
|
112
|
+
</ToolTipText>
|
|
113
|
+
</g>
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { functor, GenericChartComponent, last, MoreProps } from "@tradingaction/core";
|
|
2
|
+
import { format } from "d3-format";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { ToolTipText } from "./ToolTipText";
|
|
5
|
+
import { ToolTipTSpanLabel } from "./ToolTipTSpanLabel";
|
|
6
|
+
|
|
7
|
+
export interface SingleMAToolTipProps {
|
|
8
|
+
readonly color: string;
|
|
9
|
+
readonly displayName: string;
|
|
10
|
+
readonly fontFamily?: string;
|
|
11
|
+
readonly fontSize?: number;
|
|
12
|
+
readonly fontWeight?: number;
|
|
13
|
+
readonly forChart: number | string;
|
|
14
|
+
readonly labelFill?: string;
|
|
15
|
+
readonly labelFontWeight?: number;
|
|
16
|
+
readonly onClick?: (event: React.MouseEvent<SVGRectElement, MouseEvent>, details: any) => void;
|
|
17
|
+
readonly options: IMovingAverageTooltip;
|
|
18
|
+
readonly origin: [number, number];
|
|
19
|
+
readonly textFill?: string;
|
|
20
|
+
readonly value: string;
|
|
21
|
+
readonly icons?: SingleMATooltipIcon[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface SingleMATooltipIcon {
|
|
25
|
+
unicode: string;
|
|
26
|
+
onClick: (option: any) => void;
|
|
27
|
+
size?: any;
|
|
28
|
+
fillColor?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class SingleMAToolTip extends React.Component<SingleMAToolTipProps> {
|
|
32
|
+
public render() {
|
|
33
|
+
const { color, displayName, fontSize, fontFamily, fontWeight, textFill, labelFill, labelFontWeight, value } =
|
|
34
|
+
this.props;
|
|
35
|
+
|
|
36
|
+
const translate = "translate(" + this.props.origin[0] + ", " + this.props.origin[1] + ")";
|
|
37
|
+
|
|
38
|
+
const handleIconClick = (e: React.MouseEvent<SVGTSpanElement, MouseEvent>, icon: SingleMATooltipIcon) => {
|
|
39
|
+
if (typeof icon.onClick == "function") {
|
|
40
|
+
e.stopPropagation();
|
|
41
|
+
icon.onClick(this.props.options);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<g transform={translate} onClick={this.onClick}>
|
|
47
|
+
<line x1={0} y1={2} x2={0} y2={28} stroke={color} strokeWidth={4} />
|
|
48
|
+
<ToolTipText x={5} y={11} fontFamily={fontFamily} fontSize={fontSize} fontWeight={fontWeight}>
|
|
49
|
+
<ToolTipTSpanLabel fill={labelFill} fontWeight={labelFontWeight}>
|
|
50
|
+
{displayName}
|
|
51
|
+
</ToolTipTSpanLabel>
|
|
52
|
+
<tspan x={5} dy={15} fill={textFill}>
|
|
53
|
+
{value}
|
|
54
|
+
</tspan>
|
|
55
|
+
{this.props.icons?.map((i) => {
|
|
56
|
+
return <tspan fontSize={i.size} fill={i.fillColor || '#000'} onClick={(e) => handleIconClick(e, i)}> {i.unicode}</tspan>;
|
|
57
|
+
})}
|
|
58
|
+
</ToolTipText>
|
|
59
|
+
</g>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private readonly onClick = (event: React.MouseEvent<SVGRectElement, MouseEvent>) => {
|
|
64
|
+
const { onClick, forChart, options } = this.props;
|
|
65
|
+
if (onClick !== undefined) {
|
|
66
|
+
onClick(event, { chartId: forChart, ...options });
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface IMovingAverageTooltip {
|
|
72
|
+
data?: any;//from input data-structure
|
|
73
|
+
yAccessor: (data: any) => number;
|
|
74
|
+
type: string;
|
|
75
|
+
stroke: string;
|
|
76
|
+
windowSize: number;
|
|
77
|
+
icons?: SingleMATooltipIcon[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface MovingAverageTooltipProps {
|
|
81
|
+
readonly className?: string;
|
|
82
|
+
readonly displayFormat: (value: number) => string;
|
|
83
|
+
readonly origin: number[];
|
|
84
|
+
readonly displayInit?: string;
|
|
85
|
+
readonly displayValuesFor?: (props: MovingAverageTooltipProps, moreProps: any) => any;
|
|
86
|
+
readonly onClick?: (event: React.MouseEvent<SVGRectElement, MouseEvent>) => void;
|
|
87
|
+
readonly textFill?: string;
|
|
88
|
+
readonly labelFill?: string;
|
|
89
|
+
readonly fontFamily?: string;
|
|
90
|
+
readonly fontSize?: number;
|
|
91
|
+
readonly fontWeight?: number;
|
|
92
|
+
readonly width?: number;
|
|
93
|
+
readonly options: IMovingAverageTooltip[];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// tslint:disable-next-line: max-classes-per-file
|
|
97
|
+
export class MovingAverageTooltip extends React.Component<MovingAverageTooltipProps> {
|
|
98
|
+
public static defaultProps = {
|
|
99
|
+
className: "react-financial-charts-tooltip react-financial-charts-moving-average-tooltip",
|
|
100
|
+
displayFormat: format(".2f"),
|
|
101
|
+
displayInit: "n/a",
|
|
102
|
+
displayValuesFor: (_: any, props: any) => props.currentItem,
|
|
103
|
+
origin: [0, 10],
|
|
104
|
+
width: 65,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
public render() {
|
|
108
|
+
return <GenericChartComponent clip={false} svgDraw={this.renderSVG} drawOn={["mousemove"]} />;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private readonly renderSVG = (moreProps: MoreProps) => {
|
|
112
|
+
const { chartId, chartConfig, chartConfig: { height = 0 } = {}, fullData } = moreProps;
|
|
113
|
+
|
|
114
|
+
const {
|
|
115
|
+
className,
|
|
116
|
+
displayInit = MovingAverageTooltip.defaultProps.displayInit,
|
|
117
|
+
onClick,
|
|
118
|
+
width = 65,
|
|
119
|
+
fontFamily,
|
|
120
|
+
fontSize,
|
|
121
|
+
fontWeight,
|
|
122
|
+
textFill,
|
|
123
|
+
labelFill,
|
|
124
|
+
origin: originProp,
|
|
125
|
+
displayFormat,
|
|
126
|
+
displayValuesFor = MovingAverageTooltip.defaultProps.displayValuesFor,
|
|
127
|
+
options,
|
|
128
|
+
} = this.props;
|
|
129
|
+
|
|
130
|
+
const currentItem = displayValuesFor(this.props, moreProps) ?? last(fullData);
|
|
131
|
+
|
|
132
|
+
const config = chartConfig!;
|
|
133
|
+
|
|
134
|
+
const origin = functor(originProp);
|
|
135
|
+
const [x, y] = origin(width, height);
|
|
136
|
+
const [ox, oy] = config.origin;
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<g transform={`translate(${ox + x}, ${oy + y})`} className={className}>
|
|
140
|
+
{options.map((each, idx) => {
|
|
141
|
+
const yValue = currentItem && each.yAccessor(currentItem);
|
|
142
|
+
|
|
143
|
+
const tooltipLabel = `${each.type} (${each.windowSize})`;
|
|
144
|
+
const yDisplayValue = yValue ? displayFormat(yValue) : displayInit;
|
|
145
|
+
return (
|
|
146
|
+
<SingleMAToolTip
|
|
147
|
+
key={idx}
|
|
148
|
+
origin={[width * idx, 0]}
|
|
149
|
+
color={each.stroke}
|
|
150
|
+
displayName={tooltipLabel}
|
|
151
|
+
value={yDisplayValue}
|
|
152
|
+
options={each}
|
|
153
|
+
forChart={chartId}
|
|
154
|
+
onClick={onClick}
|
|
155
|
+
fontFamily={fontFamily}
|
|
156
|
+
fontSize={fontSize}
|
|
157
|
+
fontWeight={fontWeight}
|
|
158
|
+
textFill={textFill}
|
|
159
|
+
labelFill={labelFill}
|
|
160
|
+
icons={each.icons}
|
|
161
|
+
/>
|
|
162
|
+
);
|
|
163
|
+
})}
|
|
164
|
+
</g>
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { functor, GenericChartComponent, last } from "@tradingaction/core";
|
|
2
|
+
import { format } from "d3-format";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { ToolTipText } from "./ToolTipText";
|
|
5
|
+
import { ToolTipTSpanLabel } from "./ToolTipTSpanLabel";
|
|
6
|
+
|
|
7
|
+
const displayTextsDefault = {
|
|
8
|
+
o: "O: ",
|
|
9
|
+
h: " H: ",
|
|
10
|
+
l: " L: ",
|
|
11
|
+
c: " C: ",
|
|
12
|
+
v: " Vol: ",
|
|
13
|
+
na: "n/a",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export interface OHLCTooltipProps {
|
|
17
|
+
readonly accessor?: (data: any) => any;
|
|
18
|
+
readonly className?: string;
|
|
19
|
+
readonly changeFormat?: (n: number | { valueOf(): number }) => string;
|
|
20
|
+
readonly displayTexts?: {
|
|
21
|
+
o: string;
|
|
22
|
+
h: string;
|
|
23
|
+
l: string;
|
|
24
|
+
c: string;
|
|
25
|
+
v: string;
|
|
26
|
+
na: string;
|
|
27
|
+
};
|
|
28
|
+
readonly displayValuesFor?: (props: OHLCTooltipProps, moreProps: any) => any;
|
|
29
|
+
readonly fontFamily?: string;
|
|
30
|
+
readonly fontSize?: number;
|
|
31
|
+
readonly fontWeight?: number;
|
|
32
|
+
readonly labelFill?: string;
|
|
33
|
+
readonly labelFontWeight?: number;
|
|
34
|
+
readonly ohlcFormat?: (n: number | { valueOf(): number }) => string;
|
|
35
|
+
readonly onClick?: (event: React.MouseEvent<SVGGElement, MouseEvent>) => void;
|
|
36
|
+
readonly origin?: [number, number] | ((width: number, height: number) => [number, number]);
|
|
37
|
+
readonly percentFormat?: (n: number | { valueOf(): number }) => string;
|
|
38
|
+
readonly volumeFormat?: (n: number | { valueOf(): number }) => string;
|
|
39
|
+
readonly textFill?: string | ((item: any) => string);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class OHLCTooltip extends React.Component<OHLCTooltipProps> {
|
|
43
|
+
public static defaultProps = {
|
|
44
|
+
accessor: (d: unknown) => d,
|
|
45
|
+
changeFormat: format("+.2f"),
|
|
46
|
+
className: "react-financial-charts-tooltip-hover",
|
|
47
|
+
displayTexts: displayTextsDefault,
|
|
48
|
+
displayValuesFor: (_: any, props: any) => props.currentItem,
|
|
49
|
+
fontFamily: "-apple-system, system-ui, 'Helvetica Neue', Ubuntu, sans-serif",
|
|
50
|
+
ohlcFormat: format(".2f"),
|
|
51
|
+
origin: [0, 0],
|
|
52
|
+
percentFormat: format("+.2%"),
|
|
53
|
+
volumeFormat: format("0.2s"),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
public render() {
|
|
57
|
+
return <GenericChartComponent clip={false} svgDraw={this.renderSVG} drawOn={["mousemove"]} />;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private readonly renderSVG = (moreProps: any) => {
|
|
61
|
+
const {
|
|
62
|
+
accessor,
|
|
63
|
+
changeFormat = OHLCTooltip.defaultProps.changeFormat,
|
|
64
|
+
className,
|
|
65
|
+
displayTexts = OHLCTooltip.defaultProps.displayTexts,
|
|
66
|
+
displayValuesFor = OHLCTooltip.defaultProps.displayValuesFor,
|
|
67
|
+
fontFamily,
|
|
68
|
+
fontSize,
|
|
69
|
+
fontWeight,
|
|
70
|
+
labelFill,
|
|
71
|
+
labelFontWeight,
|
|
72
|
+
ohlcFormat = OHLCTooltip.defaultProps.ohlcFormat,
|
|
73
|
+
onClick,
|
|
74
|
+
percentFormat = OHLCTooltip.defaultProps.percentFormat,
|
|
75
|
+
volumeFormat = OHLCTooltip.defaultProps.volumeFormat,
|
|
76
|
+
textFill,
|
|
77
|
+
} = this.props;
|
|
78
|
+
|
|
79
|
+
const {
|
|
80
|
+
chartConfig: { width, height },
|
|
81
|
+
fullData,
|
|
82
|
+
} = moreProps;
|
|
83
|
+
|
|
84
|
+
const currentItem = displayValuesFor(this.props, moreProps) ?? last(fullData);
|
|
85
|
+
|
|
86
|
+
let open: string = displayTexts.na;
|
|
87
|
+
let high: string = displayTexts.na;
|
|
88
|
+
let low: string = displayTexts.na;
|
|
89
|
+
let close: string = displayTexts.na;
|
|
90
|
+
let change: string = displayTexts.na;
|
|
91
|
+
|
|
92
|
+
if (currentItem !== undefined && accessor !== undefined) {
|
|
93
|
+
const item = accessor(currentItem);
|
|
94
|
+
let formatForChange = `${changeFormat(item.close - item.open)}`;
|
|
95
|
+
const percent = percentFormat((item.close - item.open) / item.open);
|
|
96
|
+
if (percent) {
|
|
97
|
+
formatForChange += ` (${percent})`;
|
|
98
|
+
}
|
|
99
|
+
const volumeValue = volumeFormat(item.volume || 0).replace(/G/, "B");
|
|
100
|
+
if (volumeValue) {
|
|
101
|
+
formatForChange += ` ${displayTexts.v}${volumeValue}`;
|
|
102
|
+
}
|
|
103
|
+
if (item !== undefined) {
|
|
104
|
+
open = ohlcFormat(item.open);
|
|
105
|
+
high = ohlcFormat(item.high);
|
|
106
|
+
low = ohlcFormat(item.low);
|
|
107
|
+
close = ohlcFormat(item.close);
|
|
108
|
+
change = formatForChange;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const { origin: originProp } = this.props;
|
|
113
|
+
const [x, y] = functor(originProp)(width, height);
|
|
114
|
+
const valueFill = functor(textFill)(currentItem);
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<g className={className} transform={`translate(${x}, ${y})`} onClick={onClick}>
|
|
118
|
+
<ToolTipText x={0} y={0} fontFamily={fontFamily} fontSize={fontSize} fontWeight={fontWeight}>
|
|
119
|
+
<ToolTipTSpanLabel fill={labelFill} fontWeight={labelFontWeight} key="label_O">
|
|
120
|
+
{displayTexts.o}
|
|
121
|
+
</ToolTipTSpanLabel>
|
|
122
|
+
<tspan key="value_O" fill={valueFill}>
|
|
123
|
+
{open}
|
|
124
|
+
</tspan>
|
|
125
|
+
<ToolTipTSpanLabel fill={labelFill} fontWeight={labelFontWeight} key="label_H">
|
|
126
|
+
{displayTexts.h}
|
|
127
|
+
</ToolTipTSpanLabel>
|
|
128
|
+
<tspan key="value_H" fill={valueFill}>
|
|
129
|
+
{high}
|
|
130
|
+
</tspan>
|
|
131
|
+
<ToolTipTSpanLabel fill={labelFill} fontWeight={labelFontWeight} key="label_L">
|
|
132
|
+
{displayTexts.l}
|
|
133
|
+
</ToolTipTSpanLabel>
|
|
134
|
+
<tspan key="value_L" fill={valueFill}>
|
|
135
|
+
{low}
|
|
136
|
+
</tspan>
|
|
137
|
+
<ToolTipTSpanLabel fill={labelFill} fontWeight={labelFontWeight} key="label_C">
|
|
138
|
+
{displayTexts.c}
|
|
139
|
+
</ToolTipTSpanLabel>
|
|
140
|
+
<tspan key="value_C" fill={valueFill}>
|
|
141
|
+
{close}
|
|
142
|
+
</tspan>
|
|
143
|
+
<tspan key="value_Change" fill={valueFill}>
|
|
144
|
+
{` ${change}`}
|
|
145
|
+
</tspan>
|
|
146
|
+
</ToolTipText>
|
|
147
|
+
</g>
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
}
|