@tradingaction/series 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/AlternateDataSeries.d.ts +5 -0
- package/lib/AlternateDataSeries.js +16 -0
- package/lib/AlternateDataSeries.js.map +1 -0
- package/lib/AlternatingFillAreaSeries.d.ts +77 -0
- package/lib/AlternatingFillAreaSeries.js +69 -0
- package/lib/AlternatingFillAreaSeries.js.map +1 -0
- package/lib/AreaOnlySeries.d.ts +42 -0
- package/lib/AreaOnlySeries.js +53 -0
- package/lib/AreaOnlySeries.js.map +1 -0
- package/lib/AreaSeries.d.ts +46 -0
- package/lib/AreaSeries.js +21 -0
- package/lib/AreaSeries.js.map +1 -0
- package/lib/BarSeries.d.ts +36 -0
- package/lib/BarSeries.js +91 -0
- package/lib/BarSeries.js.map +1 -0
- package/lib/BollingerSeries.d.ts +33 -0
- package/lib/BollingerSeries.js +59 -0
- package/lib/BollingerSeries.js.map +1 -0
- package/lib/CandlestickSeries.d.ts +65 -0
- package/lib/CandlestickSeries.js +115 -0
- package/lib/CandlestickSeries.js.map +1 -0
- package/lib/ElderRaySeries.d.ts +45 -0
- package/lib/ElderRaySeries.js +66 -0
- package/lib/ElderRaySeries.js.map +1 -0
- package/lib/GroupedBarSeries.d.ts +33 -0
- package/lib/GroupedBarSeries.js +22 -0
- package/lib/GroupedBarSeries.js.map +1 -0
- package/lib/KagiSeries.d.ts +45 -0
- package/lib/KagiSeries.js +108 -0
- package/lib/KagiSeries.js.map +1 -0
- package/lib/LineSeries.d.ts +84 -0
- package/lib/LineSeries.js +102 -0
- package/lib/LineSeries.js.map +1 -0
- package/lib/MACDSeries.d.ts +55 -0
- package/lib/MACDSeries.js +62 -0
- package/lib/MACDSeries.js.map +1 -0
- package/lib/OHLCSeries.d.ts +32 -0
- package/lib/OHLCSeries.js +67 -0
- package/lib/OHLCSeries.js.map +1 -0
- package/lib/OverlayBarSeries.d.ts +35 -0
- package/lib/OverlayBarSeries.js +71 -0
- package/lib/OverlayBarSeries.js.map +1 -0
- package/lib/PointAndFigureSeries.d.ts +33 -0
- package/lib/PointAndFigureSeries.js +92 -0
- package/lib/PointAndFigureSeries.js.map +1 -0
- package/lib/RSISeries.d.ts +68 -0
- package/lib/RSISeries.js +82 -0
- package/lib/RSISeries.js.map +1 -0
- package/lib/RenkoSeries.d.ts +42 -0
- package/lib/RenkoSeries.js +65 -0
- package/lib/RenkoSeries.js.map +1 -0
- package/lib/SARSeries.d.ts +34 -0
- package/lib/SARSeries.js +69 -0
- package/lib/SARSeries.js.map +1 -0
- package/lib/SVGComponent.d.ts +8 -0
- package/lib/SVGComponent.js +9 -0
- package/lib/SVGComponent.js.map +1 -0
- package/lib/ScatterSeries.d.ts +24 -0
- package/lib/ScatterSeries.js +60 -0
- package/lib/ScatterSeries.js.map +1 -0
- package/lib/StackedBarSeries.d.ts +44 -0
- package/lib/StackedBarSeries.js +157 -0
- package/lib/StackedBarSeries.js.map +1 -0
- package/lib/StochasticSeries.d.ts +39 -0
- package/lib/StochasticSeries.js +42 -0
- package/lib/StochasticSeries.js.map +1 -0
- package/lib/StraightLine.d.ts +38 -0
- package/lib/StraightLine.js +81 -0
- package/lib/StraightLine.js.map +1 -0
- package/lib/VolumeProfileSeries.d.ts +45 -0
- package/lib/VolumeProfileSeries.js +161 -0
- package/lib/VolumeProfileSeries.js.map +1 -0
- package/lib/index.d.ts +23 -0
- package/lib/index.js +24 -0
- package/lib/index.js.map +1 -0
- package/lib/markers/CircleMarker.d.ts +25 -0
- package/lib/markers/CircleMarker.js +35 -0
- package/lib/markers/CircleMarker.js.map +1 -0
- package/lib/markers/SquareMarker.d.ts +25 -0
- package/lib/markers/SquareMarker.js +37 -0
- package/lib/markers/SquareMarker.js.map +1 -0
- package/lib/markers/TriangleMarker.d.ts +27 -0
- package/lib/markers/TriangleMarker.js +89 -0
- package/lib/markers/TriangleMarker.js.map +1 -0
- package/lib/markers/index.d.ts +3 -0
- package/lib/markers/index.js +4 -0
- package/lib/markers/index.js.map +1 -0
- package/package.json +52 -0
- package/src/AlternateDataSeries.tsx +29 -0
- package/src/AlternatingFillAreaSeries.tsx +159 -0
- package/src/AreaOnlySeries.tsx +106 -0
- package/src/AreaSeries.tsx +95 -0
- package/src/BarSeries.tsx +151 -0
- package/src/BollingerSeries.tsx +90 -0
- package/src/CandlestickSeries.tsx +188 -0
- package/src/ElderRaySeries.tsx +99 -0
- package/src/GroupedBarSeries.tsx +48 -0
- package/src/KagiSeries.tsx +155 -0
- package/src/LineSeries.tsx +223 -0
- package/src/MACDSeries.tsx +110 -0
- package/src/OHLCSeries.tsx +116 -0
- package/src/OverlayBarSeries.tsx +115 -0
- package/src/PointAndFigureSeries.tsx +140 -0
- package/src/RSISeries.tsx +158 -0
- package/src/RenkoSeries.tsx +118 -0
- package/src/SARSeries.tsx +111 -0
- package/src/SVGComponent.tsx +13 -0
- package/src/ScatterSeries.tsx +105 -0
- package/src/StackedBarSeries.tsx +272 -0
- package/src/StochasticSeries.tsx +70 -0
- package/src/StraightLine.tsx +166 -0
- package/src/VolumeProfileSeries.tsx +247 -0
- package/src/index.ts +23 -0
- package/src/markers/CircleMarker.tsx +69 -0
- package/src/markers/SquareMarker.tsx +71 -0
- package/src/markers/TriangleMarker.tsx +131 -0
- package/src/markers/index.ts +3 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { isDefined, isNotDefined, getAxisCanvas, GenericChartComponent } from "@tradingaction/core";
|
|
3
|
+
|
|
4
|
+
export interface KagiSeriesProps {
|
|
5
|
+
/**
|
|
6
|
+
* Current value stroke
|
|
7
|
+
*/
|
|
8
|
+
readonly currentValueStroke?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Fill values.
|
|
11
|
+
*/
|
|
12
|
+
readonly fill: {
|
|
13
|
+
yang: string;
|
|
14
|
+
yin: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Stroke values.
|
|
18
|
+
*/
|
|
19
|
+
readonly stroke: {
|
|
20
|
+
yang: string;
|
|
21
|
+
yin: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Stroke width.
|
|
25
|
+
*/
|
|
26
|
+
readonly strokeWidth?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* `KagiSeries` tracks price movement mostly independantly of time.
|
|
31
|
+
*/
|
|
32
|
+
export class KagiSeries extends React.Component<KagiSeriesProps> {
|
|
33
|
+
public static defaultProps = {
|
|
34
|
+
currentValueStroke: "#000000",
|
|
35
|
+
fill: {
|
|
36
|
+
yang: "none",
|
|
37
|
+
yin: "none",
|
|
38
|
+
},
|
|
39
|
+
stroke: {
|
|
40
|
+
yang: "#26a69a",
|
|
41
|
+
yin: "#ef5350",
|
|
42
|
+
},
|
|
43
|
+
strokeWidth: 2,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
public render() {
|
|
47
|
+
return <GenericChartComponent canvasToDraw={getAxisCanvas} canvasDraw={this.drawOnCanvas} drawOn={["pan"]} />;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private readonly drawOnCanvas = (ctx: CanvasRenderingContext2D, moreProps: any) => {
|
|
51
|
+
const { stroke, strokeWidth, currentValueStroke } = this.props;
|
|
52
|
+
const {
|
|
53
|
+
xAccessor,
|
|
54
|
+
xScale,
|
|
55
|
+
chartConfig: { yScale },
|
|
56
|
+
plotData,
|
|
57
|
+
} = moreProps;
|
|
58
|
+
|
|
59
|
+
const paths = this.helper(plotData, xAccessor);
|
|
60
|
+
|
|
61
|
+
let begin = true;
|
|
62
|
+
|
|
63
|
+
paths.forEach((each) => {
|
|
64
|
+
// @ts-ignore
|
|
65
|
+
ctx.strokeStyle = stroke[each.type];
|
|
66
|
+
if (strokeWidth !== undefined) {
|
|
67
|
+
ctx.lineWidth = strokeWidth;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
ctx.beginPath();
|
|
71
|
+
let prevX: any;
|
|
72
|
+
each.plot.forEach((d: any) => {
|
|
73
|
+
const [x1, y] = [xScale(d[0]), yScale(d[1])];
|
|
74
|
+
if (begin) {
|
|
75
|
+
ctx.moveTo(x1, y);
|
|
76
|
+
begin = false;
|
|
77
|
+
} else {
|
|
78
|
+
if (isDefined(prevX)) {
|
|
79
|
+
ctx.lineTo(prevX, y);
|
|
80
|
+
}
|
|
81
|
+
ctx.lineTo(x1, y);
|
|
82
|
+
}
|
|
83
|
+
prevX = x1;
|
|
84
|
+
});
|
|
85
|
+
ctx.stroke();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const lastPlot = paths[paths.length - 1].plot;
|
|
89
|
+
const last = lastPlot[lastPlot.length - 1];
|
|
90
|
+
ctx.beginPath();
|
|
91
|
+
ctx.lineWidth = 1;
|
|
92
|
+
|
|
93
|
+
const [x, y1, y2] = [xScale(last[0]), yScale(last[2]), yScale(last[3])];
|
|
94
|
+
ctx.moveTo(x, y1);
|
|
95
|
+
ctx.lineTo(x + 10, y1);
|
|
96
|
+
ctx.stroke();
|
|
97
|
+
|
|
98
|
+
ctx.beginPath();
|
|
99
|
+
if (currentValueStroke !== undefined) {
|
|
100
|
+
ctx.strokeStyle = currentValueStroke;
|
|
101
|
+
}
|
|
102
|
+
ctx.moveTo(x - 10, y2);
|
|
103
|
+
ctx.lineTo(x, y2);
|
|
104
|
+
ctx.stroke();
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
private readonly helper = (plotData: any[], xAccessor: any) => {
|
|
108
|
+
const kagiLine: any[] = [];
|
|
109
|
+
let kagi: {
|
|
110
|
+
added?: boolean;
|
|
111
|
+
plot?: any;
|
|
112
|
+
type?: any;
|
|
113
|
+
} = {};
|
|
114
|
+
let d = plotData[0];
|
|
115
|
+
let idx = xAccessor(d);
|
|
116
|
+
|
|
117
|
+
// tslint:disable-next-line: prefer-for-of
|
|
118
|
+
for (let i = 0; i < plotData.length; i++) {
|
|
119
|
+
d = plotData[i];
|
|
120
|
+
|
|
121
|
+
if (isNotDefined(d.close)) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (isNotDefined(kagi.type)) {
|
|
125
|
+
kagi.type = d.startAs;
|
|
126
|
+
}
|
|
127
|
+
if (isNotDefined(kagi.plot)) {
|
|
128
|
+
kagi.plot = [];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
idx = xAccessor(d);
|
|
132
|
+
kagi.plot.push([idx, d.open]);
|
|
133
|
+
|
|
134
|
+
if (isDefined(d.changeTo)) {
|
|
135
|
+
kagi.plot.push([idx, d.changePoint]);
|
|
136
|
+
kagi.added = true;
|
|
137
|
+
kagiLine.push(kagi);
|
|
138
|
+
|
|
139
|
+
kagi = {
|
|
140
|
+
type: d.changeTo,
|
|
141
|
+
plot: [],
|
|
142
|
+
added: false,
|
|
143
|
+
};
|
|
144
|
+
kagi.plot.push([idx, d.changePoint]);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!kagi.added) {
|
|
149
|
+
kagi.plot.push([idx, d.close, d.current, d.reverseAt]);
|
|
150
|
+
kagiLine.push(kagi);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return kagiLine;
|
|
154
|
+
};
|
|
155
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getClosestItemIndexes,
|
|
3
|
+
getStrokeDasharrayCanvas,
|
|
4
|
+
strokeDashTypes,
|
|
5
|
+
getAxisCanvas,
|
|
6
|
+
getMouseCanvas,
|
|
7
|
+
GenericChartComponent,
|
|
8
|
+
MoreProps,
|
|
9
|
+
} from "@tradingaction/core";
|
|
10
|
+
import { line, CurveFactoryLineOnly, CurveFactory } from "d3-shape";
|
|
11
|
+
import * as React from "react";
|
|
12
|
+
|
|
13
|
+
export interface LineSeriesProps {
|
|
14
|
+
readonly canvasClip?: (context: CanvasRenderingContext2D, moreProps: any) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Wether to connect the line between undefined data points.
|
|
17
|
+
*/
|
|
18
|
+
readonly connectNulls?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* A factory for a curve generator for the line.
|
|
21
|
+
*/
|
|
22
|
+
readonly curve?: CurveFactory | CurveFactoryLineOnly;
|
|
23
|
+
/**
|
|
24
|
+
* Function to decide if a data point has been defined.
|
|
25
|
+
*/
|
|
26
|
+
readonly defined?: (d: number | undefined) => boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Whether to highlight the line when within the `hoverTolerance`.
|
|
29
|
+
*/
|
|
30
|
+
readonly highlightOnHover?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Width to increase the line to on hover.
|
|
33
|
+
*/
|
|
34
|
+
readonly hoverStrokeWidth?: number;
|
|
35
|
+
/**
|
|
36
|
+
* The distance between the cursor and the closest point in the line.
|
|
37
|
+
*/
|
|
38
|
+
readonly hoverTolerance?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Click handler.
|
|
41
|
+
*/
|
|
42
|
+
readonly onClick?: (e: React.MouseEvent, moreProps: any) => void;
|
|
43
|
+
/**
|
|
44
|
+
* Double click handler.
|
|
45
|
+
*/
|
|
46
|
+
readonly onDoubleClick?: (e: React.MouseEvent, moreProps: any) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Hover handler.
|
|
49
|
+
*/
|
|
50
|
+
readonly onHover?: (e: React.MouseEvent, moreProps: any) => void;
|
|
51
|
+
/**
|
|
52
|
+
* Unhover handler.
|
|
53
|
+
*/
|
|
54
|
+
readonly onUnHover?: (e: React.MouseEvent, moreProps: any) => void;
|
|
55
|
+
/**
|
|
56
|
+
* Context menu handler.
|
|
57
|
+
*/
|
|
58
|
+
readonly onContextMenu?: (e: React.MouseEvent, moreProps: any) => void;
|
|
59
|
+
/**
|
|
60
|
+
* Color, gradient, or pattern to use for the stroke.
|
|
61
|
+
*/
|
|
62
|
+
readonly strokeStyle?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Stroke dash.
|
|
65
|
+
*/
|
|
66
|
+
readonly strokeDasharray?: strokeDashTypes;
|
|
67
|
+
/**
|
|
68
|
+
* Stroke width.
|
|
69
|
+
*/
|
|
70
|
+
readonly strokeWidth?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Selector for data to plot.
|
|
73
|
+
*/
|
|
74
|
+
readonly yAccessor: (data: any) => number | undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* `LineSeries` component.
|
|
79
|
+
*/
|
|
80
|
+
export class LineSeries extends React.Component<LineSeriesProps> {
|
|
81
|
+
public static defaultProps = {
|
|
82
|
+
connectNulls: false,
|
|
83
|
+
defined: (d: number | undefined) => d !== undefined && !isNaN(d),
|
|
84
|
+
hoverStrokeWidth: 4,
|
|
85
|
+
hoverTolerance: 6,
|
|
86
|
+
highlightOnHover: false,
|
|
87
|
+
strokeDasharray: "Solid",
|
|
88
|
+
strokeStyle: "#2196f3",
|
|
89
|
+
strokeWidth: 1,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
public render() {
|
|
93
|
+
const { highlightOnHover, onClick, onContextMenu, onDoubleClick, onHover, onUnHover, strokeDasharray } =
|
|
94
|
+
this.props;
|
|
95
|
+
|
|
96
|
+
const hoverProps =
|
|
97
|
+
highlightOnHover || onHover || onUnHover
|
|
98
|
+
? {
|
|
99
|
+
isHover: this.isHover,
|
|
100
|
+
drawOn: ["mousemove", "pan"],
|
|
101
|
+
canvasToDraw: getMouseCanvas,
|
|
102
|
+
}
|
|
103
|
+
: {
|
|
104
|
+
drawOn: ["pan"],
|
|
105
|
+
canvasToDraw: getAxisCanvas,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const lineDash = getStrokeDasharrayCanvas(strokeDasharray);
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<GenericChartComponent
|
|
112
|
+
canvasDraw={this.drawOnCanvas(lineDash)}
|
|
113
|
+
onClickWhenHover={onClick}
|
|
114
|
+
onDoubleClickWhenHover={onDoubleClick}
|
|
115
|
+
onContextMenuWhenHover={onContextMenu}
|
|
116
|
+
onHover={onHover}
|
|
117
|
+
onUnHover={onUnHover}
|
|
118
|
+
{...hoverProps}
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private readonly drawOnCanvas = (lineDash?: number[]) => (ctx: CanvasRenderingContext2D, moreProps: MoreProps) => {
|
|
124
|
+
const {
|
|
125
|
+
connectNulls,
|
|
126
|
+
yAccessor,
|
|
127
|
+
hoverStrokeWidth = LineSeries.defaultProps.hoverStrokeWidth,
|
|
128
|
+
defined = LineSeries.defaultProps.defined,
|
|
129
|
+
curve,
|
|
130
|
+
canvasClip,
|
|
131
|
+
strokeStyle,
|
|
132
|
+
strokeWidth = LineSeries.defaultProps.strokeWidth,
|
|
133
|
+
} = this.props;
|
|
134
|
+
|
|
135
|
+
const { xAccessor, xScale, chartConfig, plotData, hovering } = moreProps;
|
|
136
|
+
if (!chartConfig) {
|
|
137
|
+
console.warn("LineSeries received no chartConfig, is it inside of a Chart?");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const { yScale } = chartConfig;
|
|
141
|
+
|
|
142
|
+
if (canvasClip !== undefined) {
|
|
143
|
+
ctx.save();
|
|
144
|
+
canvasClip(ctx, moreProps);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
ctx.lineWidth = hovering ? hoverStrokeWidth : strokeWidth;
|
|
148
|
+
|
|
149
|
+
if (strokeStyle !== undefined) {
|
|
150
|
+
ctx.strokeStyle = strokeStyle;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (lineDash !== undefined) {
|
|
154
|
+
ctx.setLineDash(lineDash);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const dataSeries = line()
|
|
158
|
+
.x((d) => Math.round(xScale(xAccessor(d))))
|
|
159
|
+
.y((d) => Math.round(yScale(yAccessor(d)!)));
|
|
160
|
+
|
|
161
|
+
if (curve !== undefined) {
|
|
162
|
+
dataSeries.curve(curve);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!connectNulls) {
|
|
166
|
+
dataSeries.defined((d) => defined(yAccessor(d)));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
ctx.beginPath();
|
|
170
|
+
dataSeries.context(ctx)(plotData);
|
|
171
|
+
ctx.stroke();
|
|
172
|
+
|
|
173
|
+
if (canvasClip !== undefined) {
|
|
174
|
+
ctx.restore();
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
private readonly isHover = (moreProps: any) => {
|
|
179
|
+
const { highlightOnHover, yAccessor, hoverTolerance = LineSeries.defaultProps.hoverTolerance } = this.props;
|
|
180
|
+
if (!highlightOnHover) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const {
|
|
185
|
+
chartConfig: { yScale, origin },
|
|
186
|
+
xAccessor,
|
|
187
|
+
mouseXY,
|
|
188
|
+
currentItem,
|
|
189
|
+
xScale,
|
|
190
|
+
plotData,
|
|
191
|
+
} = moreProps;
|
|
192
|
+
|
|
193
|
+
const [x, y] = mouseXY;
|
|
194
|
+
const radius = hoverTolerance;
|
|
195
|
+
|
|
196
|
+
const { left, right } = getClosestItemIndexes(plotData, xScale.invert(x), xAccessor);
|
|
197
|
+
if (left === right) {
|
|
198
|
+
const cy = yScale(yAccessor(currentItem)) + origin[1];
|
|
199
|
+
const cx = xScale(xAccessor(currentItem)) + origin[0];
|
|
200
|
+
|
|
201
|
+
const hovering1 = Math.pow(x - cx, 2) + Math.pow(y - cy, 2) < Math.pow(radius, 2);
|
|
202
|
+
|
|
203
|
+
return hovering1;
|
|
204
|
+
} else {
|
|
205
|
+
const l = plotData[left];
|
|
206
|
+
const r = plotData[right];
|
|
207
|
+
const x1 = xScale(xAccessor(l)) + origin[0];
|
|
208
|
+
const y1 = yScale(yAccessor(l)) + origin[1];
|
|
209
|
+
const x2 = xScale(xAccessor(r)) + origin[0];
|
|
210
|
+
const y2 = yScale(yAccessor(r)) + origin[1];
|
|
211
|
+
|
|
212
|
+
// y = m * x + b
|
|
213
|
+
const m /* slope */ = (y2 - y1) / (x2 - x1);
|
|
214
|
+
const b /* y intercept */ = -1 * m * x1 + y1;
|
|
215
|
+
|
|
216
|
+
const desiredY = Math.round(m * x + b);
|
|
217
|
+
|
|
218
|
+
const hovering2 = y >= desiredY - radius && y <= desiredY + radius;
|
|
219
|
+
|
|
220
|
+
return hovering2;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { ScaleContinuousNumeric, ScaleTime } from "d3-scale";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { BarSeries } from "./BarSeries";
|
|
4
|
+
import { LineSeries } from "./LineSeries";
|
|
5
|
+
import { StraightLine } from "./StraightLine";
|
|
6
|
+
|
|
7
|
+
export interface MACDSeriesProps {
|
|
8
|
+
readonly className?: string;
|
|
9
|
+
readonly clip?: boolean;
|
|
10
|
+
readonly fillStyle?: {
|
|
11
|
+
divergence: string;
|
|
12
|
+
};
|
|
13
|
+
readonly strokeStyle?: {
|
|
14
|
+
macd: string;
|
|
15
|
+
signal: string;
|
|
16
|
+
zero: string;
|
|
17
|
+
};
|
|
18
|
+
readonly widthRatio?: number;
|
|
19
|
+
readonly width?: number | ((props: { widthRatio: number }, moreProps: any) => number);
|
|
20
|
+
readonly yAccessor: (data: any) => { divergence: number; signal: number; macd: number } | undefined;
|
|
21
|
+
readonly zeroLineStroke?: string;
|
|
22
|
+
readonly zeroLineOpacity?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The MACD turns two trend-following indicators, moving averages, into a momentum oscillator by subtracting the longer moving average from the shorter one.
|
|
27
|
+
*/
|
|
28
|
+
export class MACDSeries extends React.Component<MACDSeriesProps> {
|
|
29
|
+
public static defaultProps = {
|
|
30
|
+
className: "react-financial-charts-macd-series",
|
|
31
|
+
clip: true,
|
|
32
|
+
fillStyle: {
|
|
33
|
+
divergence: "rgba(70, 130, 180, 0.6)",
|
|
34
|
+
},
|
|
35
|
+
strokeStyle: {
|
|
36
|
+
macd: "#0093FF",
|
|
37
|
+
signal: "#D84315",
|
|
38
|
+
zero: "rgba(0, 0, 0, 0.3)",
|
|
39
|
+
},
|
|
40
|
+
widthRatio: 0.5,
|
|
41
|
+
width: BarSeries.defaultProps.width,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
public render() {
|
|
45
|
+
const {
|
|
46
|
+
className,
|
|
47
|
+
clip,
|
|
48
|
+
fillStyle = MACDSeries.defaultProps.fillStyle,
|
|
49
|
+
strokeStyle = MACDSeries.defaultProps.strokeStyle,
|
|
50
|
+
widthRatio,
|
|
51
|
+
width,
|
|
52
|
+
} = this.props;
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<g className={className}>
|
|
56
|
+
<BarSeries
|
|
57
|
+
baseAt={this.yAccessorForDivergenceBase}
|
|
58
|
+
width={width}
|
|
59
|
+
widthRatio={widthRatio}
|
|
60
|
+
fillStyle={fillStyle.divergence}
|
|
61
|
+
clip={clip}
|
|
62
|
+
yAccessor={this.yAccessorForDivergence}
|
|
63
|
+
/>
|
|
64
|
+
<LineSeries yAccessor={this.yAccessorForMACD} strokeStyle={strokeStyle.macd} />
|
|
65
|
+
<LineSeries yAccessor={this.yAccessorForSignal} strokeStyle={strokeStyle.signal} />
|
|
66
|
+
<StraightLine strokeStyle={strokeStyle.zero} yValue={0} />
|
|
67
|
+
</g>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private readonly yAccessorForDivergenceBase = (
|
|
72
|
+
xScale: ScaleContinuousNumeric<number, number> | ScaleTime<number, number>,
|
|
73
|
+
yScale: ScaleContinuousNumeric<number, number>,
|
|
74
|
+
) => {
|
|
75
|
+
return yScale(0);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
private readonly yAccessorForDivergence = (d: any) => {
|
|
79
|
+
const { yAccessor } = this.props;
|
|
80
|
+
|
|
81
|
+
const dataItem = yAccessor(d);
|
|
82
|
+
if (dataItem !== undefined) {
|
|
83
|
+
return dataItem.divergence;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return undefined;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
private readonly yAccessorForSignal = (d: any) => {
|
|
90
|
+
const { yAccessor } = this.props;
|
|
91
|
+
|
|
92
|
+
const dataItem = yAccessor(d);
|
|
93
|
+
if (dataItem !== undefined) {
|
|
94
|
+
return dataItem.signal;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return undefined;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
private readonly yAccessorForMACD = (d: any) => {
|
|
101
|
+
const { yAccessor } = this.props;
|
|
102
|
+
|
|
103
|
+
const dataItem = yAccessor(d);
|
|
104
|
+
if (dataItem !== undefined) {
|
|
105
|
+
return dataItem.macd;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return undefined;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { group } from "d3-array";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { functor, isDefined, getAxisCanvas, GenericChartComponent } from "@tradingaction/core";
|
|
4
|
+
|
|
5
|
+
interface IOHLC {
|
|
6
|
+
readonly close?: number;
|
|
7
|
+
readonly high?: number;
|
|
8
|
+
readonly low?: number;
|
|
9
|
+
readonly open?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface OHLCSeriesProps {
|
|
13
|
+
readonly clip?: boolean;
|
|
14
|
+
readonly stroke?: string | ((datum: any) => string);
|
|
15
|
+
readonly strokeWidth?: number;
|
|
16
|
+
readonly yAccessor: (datum: any) => IOHLC;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class OHLCSeries extends React.Component<OHLCSeriesProps> {
|
|
20
|
+
public static defaultProps = {
|
|
21
|
+
yAccessor: (d: any) => ({ open: d.open, high: d.high, low: d.low, close: d.close }),
|
|
22
|
+
classNames: (d: any) => (isDefined(d.absoluteChange) ? (d.absoluteChange > 0 ? "up" : "down") : "firstbar"),
|
|
23
|
+
stroke: (d: any) => (isDefined(d.absoluteChange) ? (d.absoluteChange > 0 ? "#26a69a" : "#ef5350") : "#000000"),
|
|
24
|
+
strokeWidth: 1,
|
|
25
|
+
clip: true,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
public render() {
|
|
29
|
+
const { clip } = this.props;
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<GenericChartComponent
|
|
33
|
+
canvasToDraw={getAxisCanvas}
|
|
34
|
+
canvasDraw={this.drawOnCanvas}
|
|
35
|
+
clip={clip}
|
|
36
|
+
drawOn={["pan"]}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private readonly drawOnCanvas = (ctx: CanvasRenderingContext2D, moreProps: any) => {
|
|
42
|
+
const {
|
|
43
|
+
xAccessor,
|
|
44
|
+
xScale,
|
|
45
|
+
chartConfig: { yScale },
|
|
46
|
+
plotData,
|
|
47
|
+
} = moreProps;
|
|
48
|
+
|
|
49
|
+
const barData = this.getOHLCBars(this.props, xAccessor, xScale, yScale, plotData);
|
|
50
|
+
|
|
51
|
+
this.drawBarDataOnCanvas(ctx, barData);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
private readonly getOHLCBars = (
|
|
55
|
+
props: OHLCSeriesProps,
|
|
56
|
+
xAccessor: any,
|
|
57
|
+
xScale: any,
|
|
58
|
+
yScale: any,
|
|
59
|
+
plotData: any,
|
|
60
|
+
) => {
|
|
61
|
+
const { stroke: strokeProp, strokeWidth: strokeWidthProp = 1, yAccessor } = props;
|
|
62
|
+
|
|
63
|
+
const strokeFunc = functor(strokeProp);
|
|
64
|
+
|
|
65
|
+
const width = xScale(xAccessor(plotData[plotData.length - 1])) - xScale(xAccessor(plotData[0]));
|
|
66
|
+
|
|
67
|
+
const barWidth = Math.max(1, Math.round(width / (plotData.length - 1) / 2) - 1.5);
|
|
68
|
+
|
|
69
|
+
const strokeWidth = Math.min(barWidth, strokeWidthProp);
|
|
70
|
+
|
|
71
|
+
const bars = plotData
|
|
72
|
+
.filter((d: any) => yAccessor(d).close !== undefined)
|
|
73
|
+
.map((d: any) => {
|
|
74
|
+
const ohlc = yAccessor(d);
|
|
75
|
+
const x = Math.round(xScale(xAccessor(d)));
|
|
76
|
+
const y1 = yScale(ohlc.high);
|
|
77
|
+
const y2 = yScale(ohlc.low);
|
|
78
|
+
const openX1 = x - barWidth;
|
|
79
|
+
const openX2 = x + strokeWidth / 2;
|
|
80
|
+
const openY = yScale(ohlc.open);
|
|
81
|
+
const closeX1 = x - strokeWidth / 2;
|
|
82
|
+
const closeX2 = x + barWidth;
|
|
83
|
+
const closeY = yScale(ohlc.close);
|
|
84
|
+
const stroke = strokeFunc(d);
|
|
85
|
+
|
|
86
|
+
return { x, y1, y2, openX1, openX2, openY, closeX1, closeX2, closeY, stroke };
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return { barWidth, strokeWidth, bars };
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
private readonly drawBarDataOnCanvas = (ctx: CanvasRenderingContext2D, barData: any) => {
|
|
93
|
+
const { strokeWidth, bars } = barData;
|
|
94
|
+
|
|
95
|
+
const wickNest = group(bars, (d: any) => d.stroke);
|
|
96
|
+
|
|
97
|
+
ctx.lineWidth = strokeWidth;
|
|
98
|
+
|
|
99
|
+
wickNest.forEach((values, key) => {
|
|
100
|
+
ctx.strokeStyle = key;
|
|
101
|
+
values.forEach((d) => {
|
|
102
|
+
ctx.beginPath();
|
|
103
|
+
ctx.moveTo(d.x, d.y1);
|
|
104
|
+
ctx.lineTo(d.x, d.y2);
|
|
105
|
+
|
|
106
|
+
ctx.moveTo(d.openX1, d.openY);
|
|
107
|
+
ctx.lineTo(d.openX2, d.openY);
|
|
108
|
+
|
|
109
|
+
ctx.moveTo(d.closeX1, d.closeY);
|
|
110
|
+
ctx.lineTo(d.closeX2, d.closeY);
|
|
111
|
+
|
|
112
|
+
ctx.stroke();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
}
|