hyperprop-charting-library 0.1.58 → 0.1.60
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/dist/hyperprop-charting-library.cjs +130 -12
- package/dist/hyperprop-charting-library.d.ts +1 -1
- package/dist/hyperprop-charting-library.js +130 -12
- package/dist/index.cjs +130 -12
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +130 -12
- package/package.json +1 -1
|
@@ -2021,6 +2021,16 @@ function createChart(element, options = {}) {
|
|
|
2021
2021
|
return chartBottom - (price - yMin) / yRange * chartHeight;
|
|
2022
2022
|
};
|
|
2023
2023
|
const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
|
|
2024
|
+
const FIB_LEVELS = [
|
|
2025
|
+
{ ratio: 0, color: "#787b86" },
|
|
2026
|
+
{ ratio: 0.236, color: "#f23645", band: "rgba(242,54,69,0.10)" },
|
|
2027
|
+
{ ratio: 0.382, color: "#ff9800", band: "rgba(255,152,0,0.10)" },
|
|
2028
|
+
{ ratio: 0.5, color: "#4caf50", band: "rgba(76,175,80,0.10)" },
|
|
2029
|
+
{ ratio: 0.618, color: "#089981", band: "rgba(8,153,129,0.10)" },
|
|
2030
|
+
{ ratio: 0.786, color: "#00bcd4", band: "rgba(0,188,212,0.10)" },
|
|
2031
|
+
{ ratio: 1, color: "#787b86", band: "rgba(120,123,134,0.10)" },
|
|
2032
|
+
{ ratio: 1.618, color: "#2962ff", band: "rgba(41,98,255,0.08)" }
|
|
2033
|
+
];
|
|
2024
2034
|
const drawDrawingHandle = (x, y, color) => {
|
|
2025
2035
|
ctx.save();
|
|
2026
2036
|
ctx.setLineDash([]);
|
|
@@ -2033,27 +2043,24 @@ function createChart(element, options = {}) {
|
|
|
2033
2043
|
ctx.stroke();
|
|
2034
2044
|
ctx.restore();
|
|
2035
2045
|
};
|
|
2036
|
-
const drawDrawingLabel = (labelText, anchorX, anchorY, color
|
|
2046
|
+
const drawDrawingLabel = (labelText, anchorX, anchorY, color) => {
|
|
2037
2047
|
if (!labelText) return;
|
|
2038
2048
|
ctx.save();
|
|
2039
|
-
ctx.translate(anchorX, anchorY);
|
|
2040
|
-
ctx.rotate(angle);
|
|
2041
|
-
ctx.translate(0, -8);
|
|
2042
2049
|
const prevFont = ctx.font;
|
|
2043
2050
|
ctx.font = `500 12px ${mergedOptions.fontFamily}`;
|
|
2044
2051
|
const textWidth = ctx.measureText(labelText).width;
|
|
2045
2052
|
const paddingX = 6;
|
|
2046
|
-
const paddingY = 3;
|
|
2047
2053
|
const bgWidth = textWidth + paddingX * 2;
|
|
2048
2054
|
const bgHeight = 18;
|
|
2055
|
+
const bgX = anchorX - bgWidth / 2;
|
|
2056
|
+
const bgY = anchorY - bgHeight - 6;
|
|
2049
2057
|
ctx.fillStyle = `${color}1f`;
|
|
2050
|
-
fillRoundedRect(
|
|
2058
|
+
fillRoundedRect(bgX, bgY, bgWidth, bgHeight, 4);
|
|
2051
2059
|
ctx.fillStyle = color;
|
|
2052
2060
|
ctx.textAlign = "center";
|
|
2053
2061
|
ctx.textBaseline = "middle";
|
|
2054
|
-
ctx.fillText(labelText,
|
|
2062
|
+
ctx.fillText(labelText, anchorX, bgY + bgHeight / 2);
|
|
2055
2063
|
ctx.font = prevFont;
|
|
2056
|
-
void paddingY;
|
|
2057
2064
|
ctx.restore();
|
|
2058
2065
|
};
|
|
2059
2066
|
const drawDrawing = (drawing, draft = false) => {
|
|
@@ -2076,7 +2083,8 @@ function createChart(element, options = {}) {
|
|
|
2076
2083
|
ctx.stroke();
|
|
2077
2084
|
drawDrawingHandle(handleX, y, drawing.color);
|
|
2078
2085
|
if (drawing.label) {
|
|
2079
|
-
|
|
2086
|
+
const midX = (chartLeft + chartRight) / 2;
|
|
2087
|
+
drawDrawingLabel(drawing.label, midX, y, drawing.color);
|
|
2080
2088
|
}
|
|
2081
2089
|
}
|
|
2082
2090
|
} else if (drawing.type === "trendline") {
|
|
@@ -2096,9 +2104,74 @@ function createChart(element, options = {}) {
|
|
|
2096
2104
|
if (drawing.label) {
|
|
2097
2105
|
const midX = (firstX + secondX) / 2;
|
|
2098
2106
|
const midY = (firstY + secondY) / 2;
|
|
2099
|
-
|
|
2100
|
-
|
|
2107
|
+
drawDrawingLabel(drawing.label, midX, midY, drawing.color);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
} else if (drawing.type === "fib-retracement") {
|
|
2111
|
+
const first = drawing.points[0];
|
|
2112
|
+
const second = drawing.points[1];
|
|
2113
|
+
if (first && second) {
|
|
2114
|
+
const firstX = xFromDrawingPoint(first);
|
|
2115
|
+
const secondX = xFromDrawingPoint(second);
|
|
2116
|
+
const firstY = yFromPrice(first.price);
|
|
2117
|
+
const secondY = yFromPrice(second.price);
|
|
2118
|
+
const lineLeft = chartLeft;
|
|
2119
|
+
const lineRight = chartRight;
|
|
2120
|
+
const levelLines = FIB_LEVELS.map((level) => {
|
|
2121
|
+
const price = first.price + (second.price - first.price) * (1 - level.ratio);
|
|
2122
|
+
return { ...level, price, y: yFromPrice(price) };
|
|
2123
|
+
});
|
|
2124
|
+
for (let index = 0; index < levelLines.length - 1; index += 1) {
|
|
2125
|
+
const top = levelLines[index];
|
|
2126
|
+
const bottom = levelLines[index + 1];
|
|
2127
|
+
const fillColor = bottom.band ?? top.band;
|
|
2128
|
+
if (!fillColor) continue;
|
|
2129
|
+
const bandTop = Math.min(top.y, bottom.y);
|
|
2130
|
+
const bandBottom = Math.max(top.y, bottom.y);
|
|
2131
|
+
ctx.save();
|
|
2132
|
+
ctx.fillStyle = fillColor;
|
|
2133
|
+
ctx.globalAlpha = draft ? 0.5 : 1;
|
|
2134
|
+
ctx.fillRect(lineLeft, bandTop, lineRight - lineLeft, bandBottom - bandTop);
|
|
2135
|
+
ctx.restore();
|
|
2136
|
+
}
|
|
2137
|
+
ctx.save();
|
|
2138
|
+
ctx.lineWidth = drawing.width;
|
|
2139
|
+
applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2140
|
+
for (const level of levelLines) {
|
|
2141
|
+
ctx.strokeStyle = level.color;
|
|
2142
|
+
ctx.beginPath();
|
|
2143
|
+
ctx.moveTo(crisp(lineLeft), crisp(level.y));
|
|
2144
|
+
ctx.lineTo(crisp(lineRight), crisp(level.y));
|
|
2145
|
+
ctx.stroke();
|
|
2101
2146
|
}
|
|
2147
|
+
ctx.restore();
|
|
2148
|
+
ctx.save();
|
|
2149
|
+
ctx.strokeStyle = "rgba(148,163,184,0.7)";
|
|
2150
|
+
ctx.setLineDash([4, 4]);
|
|
2151
|
+
ctx.lineWidth = 1;
|
|
2152
|
+
ctx.beginPath();
|
|
2153
|
+
ctx.moveTo(firstX, firstY);
|
|
2154
|
+
ctx.lineTo(secondX, secondY);
|
|
2155
|
+
ctx.stroke();
|
|
2156
|
+
ctx.restore();
|
|
2157
|
+
drawDrawingHandle(firstX, firstY, drawing.color);
|
|
2158
|
+
drawDrawingHandle(secondX, secondY, drawing.color);
|
|
2159
|
+
const prevFont = ctx.font;
|
|
2160
|
+
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2161
|
+
for (const level of levelLines) {
|
|
2162
|
+
const labelText = `${level.ratio} (${formatPrice(level.price)})`;
|
|
2163
|
+
const textWidth = ctx.measureText(labelText).width;
|
|
2164
|
+
const padding = 4;
|
|
2165
|
+
const bgX = lineLeft + 4;
|
|
2166
|
+
const bgY = level.y - 9;
|
|
2167
|
+
ctx.fillStyle = mergedOptions.backgroundColor;
|
|
2168
|
+
fillRoundedRect(bgX, bgY, textWidth + padding * 2, 16, 3);
|
|
2169
|
+
ctx.fillStyle = level.color;
|
|
2170
|
+
ctx.textAlign = "left";
|
|
2171
|
+
ctx.textBaseline = "middle";
|
|
2172
|
+
ctx.fillText(labelText, bgX + padding, bgY + 8);
|
|
2173
|
+
}
|
|
2174
|
+
ctx.font = prevFont;
|
|
2102
2175
|
}
|
|
2103
2176
|
}
|
|
2104
2177
|
ctx.restore();
|
|
@@ -3186,6 +3259,28 @@ function createChart(element, options = {}) {
|
|
|
3186
3259
|
if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
|
|
3187
3260
|
return { drawing, target: "line" };
|
|
3188
3261
|
}
|
|
3262
|
+
} else if (drawing.type === "fib-retracement") {
|
|
3263
|
+
const first = drawing.points[0];
|
|
3264
|
+
const second = drawing.points[1];
|
|
3265
|
+
if (!first || !second) continue;
|
|
3266
|
+
const x1 = canvasXFromDrawingPoint(first);
|
|
3267
|
+
const y1 = canvasYFromDrawingPrice(first.price);
|
|
3268
|
+
const x2 = canvasXFromDrawingPoint(second);
|
|
3269
|
+
const y2 = canvasYFromDrawingPrice(second.price);
|
|
3270
|
+
if (Math.hypot(x - x1, y - y1) <= 9) {
|
|
3271
|
+
return { drawing, target: "handle", pointIndex: 0 };
|
|
3272
|
+
}
|
|
3273
|
+
if (Math.hypot(x - x2, y - y2) <= 9) {
|
|
3274
|
+
return { drawing, target: "handle", pointIndex: 1 };
|
|
3275
|
+
}
|
|
3276
|
+
const fibRatios = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
|
|
3277
|
+
for (const ratio of fibRatios) {
|
|
3278
|
+
const price = first.price + (second.price - first.price) * (1 - ratio);
|
|
3279
|
+
const lineY = canvasYFromDrawingPrice(price);
|
|
3280
|
+
if (Math.abs(y - lineY) <= 5) {
|
|
3281
|
+
return { drawing, target: "line" };
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3189
3284
|
}
|
|
3190
3285
|
}
|
|
3191
3286
|
return null;
|
|
@@ -3287,6 +3382,29 @@ function createChart(element, options = {}) {
|
|
|
3287
3382
|
draw();
|
|
3288
3383
|
return true;
|
|
3289
3384
|
}
|
|
3385
|
+
if (activeDrawingTool === "fib-retracement") {
|
|
3386
|
+
if (draftDrawing?.type === "fib-retracement") {
|
|
3387
|
+
const completed = normalizeDrawingState({
|
|
3388
|
+
...serializeDrawing(draftDrawing),
|
|
3389
|
+
points: [draftDrawing.points[0], point]
|
|
3390
|
+
});
|
|
3391
|
+
drawings.push(completed);
|
|
3392
|
+
draftDrawing = null;
|
|
3393
|
+
activeDrawingTool = null;
|
|
3394
|
+
emitDrawingsChange();
|
|
3395
|
+
draw();
|
|
3396
|
+
return true;
|
|
3397
|
+
}
|
|
3398
|
+
draftDrawing = normalizeDrawingState({
|
|
3399
|
+
type: "fib-retracement",
|
|
3400
|
+
points: [point, point],
|
|
3401
|
+
color: "#2563eb",
|
|
3402
|
+
style: "solid",
|
|
3403
|
+
width: 1
|
|
3404
|
+
});
|
|
3405
|
+
draw();
|
|
3406
|
+
return true;
|
|
3407
|
+
}
|
|
3290
3408
|
return false;
|
|
3291
3409
|
};
|
|
3292
3410
|
const updateDrawingDrag = (x, y) => {
|
|
@@ -3522,7 +3640,7 @@ function createChart(element, options = {}) {
|
|
|
3522
3640
|
setCrosshairPoint(null);
|
|
3523
3641
|
return;
|
|
3524
3642
|
}
|
|
3525
|
-
if (draftDrawing && activeDrawingTool === "trendline") {
|
|
3643
|
+
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "fib-retracement")) {
|
|
3526
3644
|
const nextPoint = drawingPointFromCanvas(point.x, point.y);
|
|
3527
3645
|
if (nextPoint) {
|
|
3528
3646
|
draftDrawing = {
|
|
@@ -43,7 +43,7 @@ interface ChartOptions {
|
|
|
43
43
|
drawings?: DrawingObjectOptions[];
|
|
44
44
|
}
|
|
45
45
|
type IndicatorPane = "overlay" | "separate";
|
|
46
|
-
type DrawingToolType = "horizontal-line" | "trendline";
|
|
46
|
+
type DrawingToolType = "horizontal-line" | "trendline" | "fib-retracement";
|
|
47
47
|
interface DrawingPoint {
|
|
48
48
|
index: number;
|
|
49
49
|
price: number;
|
|
@@ -1997,6 +1997,16 @@ function createChart(element, options = {}) {
|
|
|
1997
1997
|
return chartBottom - (price - yMin) / yRange * chartHeight;
|
|
1998
1998
|
};
|
|
1999
1999
|
const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
|
|
2000
|
+
const FIB_LEVELS = [
|
|
2001
|
+
{ ratio: 0, color: "#787b86" },
|
|
2002
|
+
{ ratio: 0.236, color: "#f23645", band: "rgba(242,54,69,0.10)" },
|
|
2003
|
+
{ ratio: 0.382, color: "#ff9800", band: "rgba(255,152,0,0.10)" },
|
|
2004
|
+
{ ratio: 0.5, color: "#4caf50", band: "rgba(76,175,80,0.10)" },
|
|
2005
|
+
{ ratio: 0.618, color: "#089981", band: "rgba(8,153,129,0.10)" },
|
|
2006
|
+
{ ratio: 0.786, color: "#00bcd4", band: "rgba(0,188,212,0.10)" },
|
|
2007
|
+
{ ratio: 1, color: "#787b86", band: "rgba(120,123,134,0.10)" },
|
|
2008
|
+
{ ratio: 1.618, color: "#2962ff", band: "rgba(41,98,255,0.08)" }
|
|
2009
|
+
];
|
|
2000
2010
|
const drawDrawingHandle = (x, y, color) => {
|
|
2001
2011
|
ctx.save();
|
|
2002
2012
|
ctx.setLineDash([]);
|
|
@@ -2009,27 +2019,24 @@ function createChart(element, options = {}) {
|
|
|
2009
2019
|
ctx.stroke();
|
|
2010
2020
|
ctx.restore();
|
|
2011
2021
|
};
|
|
2012
|
-
const drawDrawingLabel = (labelText, anchorX, anchorY, color
|
|
2022
|
+
const drawDrawingLabel = (labelText, anchorX, anchorY, color) => {
|
|
2013
2023
|
if (!labelText) return;
|
|
2014
2024
|
ctx.save();
|
|
2015
|
-
ctx.translate(anchorX, anchorY);
|
|
2016
|
-
ctx.rotate(angle);
|
|
2017
|
-
ctx.translate(0, -8);
|
|
2018
2025
|
const prevFont = ctx.font;
|
|
2019
2026
|
ctx.font = `500 12px ${mergedOptions.fontFamily}`;
|
|
2020
2027
|
const textWidth = ctx.measureText(labelText).width;
|
|
2021
2028
|
const paddingX = 6;
|
|
2022
|
-
const paddingY = 3;
|
|
2023
2029
|
const bgWidth = textWidth + paddingX * 2;
|
|
2024
2030
|
const bgHeight = 18;
|
|
2031
|
+
const bgX = anchorX - bgWidth / 2;
|
|
2032
|
+
const bgY = anchorY - bgHeight - 6;
|
|
2025
2033
|
ctx.fillStyle = `${color}1f`;
|
|
2026
|
-
fillRoundedRect(
|
|
2034
|
+
fillRoundedRect(bgX, bgY, bgWidth, bgHeight, 4);
|
|
2027
2035
|
ctx.fillStyle = color;
|
|
2028
2036
|
ctx.textAlign = "center";
|
|
2029
2037
|
ctx.textBaseline = "middle";
|
|
2030
|
-
ctx.fillText(labelText,
|
|
2038
|
+
ctx.fillText(labelText, anchorX, bgY + bgHeight / 2);
|
|
2031
2039
|
ctx.font = prevFont;
|
|
2032
|
-
void paddingY;
|
|
2033
2040
|
ctx.restore();
|
|
2034
2041
|
};
|
|
2035
2042
|
const drawDrawing = (drawing, draft = false) => {
|
|
@@ -2052,7 +2059,8 @@ function createChart(element, options = {}) {
|
|
|
2052
2059
|
ctx.stroke();
|
|
2053
2060
|
drawDrawingHandle(handleX, y, drawing.color);
|
|
2054
2061
|
if (drawing.label) {
|
|
2055
|
-
|
|
2062
|
+
const midX = (chartLeft + chartRight) / 2;
|
|
2063
|
+
drawDrawingLabel(drawing.label, midX, y, drawing.color);
|
|
2056
2064
|
}
|
|
2057
2065
|
}
|
|
2058
2066
|
} else if (drawing.type === "trendline") {
|
|
@@ -2072,9 +2080,74 @@ function createChart(element, options = {}) {
|
|
|
2072
2080
|
if (drawing.label) {
|
|
2073
2081
|
const midX = (firstX + secondX) / 2;
|
|
2074
2082
|
const midY = (firstY + secondY) / 2;
|
|
2075
|
-
|
|
2076
|
-
|
|
2083
|
+
drawDrawingLabel(drawing.label, midX, midY, drawing.color);
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
} else if (drawing.type === "fib-retracement") {
|
|
2087
|
+
const first = drawing.points[0];
|
|
2088
|
+
const second = drawing.points[1];
|
|
2089
|
+
if (first && second) {
|
|
2090
|
+
const firstX = xFromDrawingPoint(first);
|
|
2091
|
+
const secondX = xFromDrawingPoint(second);
|
|
2092
|
+
const firstY = yFromPrice(first.price);
|
|
2093
|
+
const secondY = yFromPrice(second.price);
|
|
2094
|
+
const lineLeft = chartLeft;
|
|
2095
|
+
const lineRight = chartRight;
|
|
2096
|
+
const levelLines = FIB_LEVELS.map((level) => {
|
|
2097
|
+
const price = first.price + (second.price - first.price) * (1 - level.ratio);
|
|
2098
|
+
return { ...level, price, y: yFromPrice(price) };
|
|
2099
|
+
});
|
|
2100
|
+
for (let index = 0; index < levelLines.length - 1; index += 1) {
|
|
2101
|
+
const top = levelLines[index];
|
|
2102
|
+
const bottom = levelLines[index + 1];
|
|
2103
|
+
const fillColor = bottom.band ?? top.band;
|
|
2104
|
+
if (!fillColor) continue;
|
|
2105
|
+
const bandTop = Math.min(top.y, bottom.y);
|
|
2106
|
+
const bandBottom = Math.max(top.y, bottom.y);
|
|
2107
|
+
ctx.save();
|
|
2108
|
+
ctx.fillStyle = fillColor;
|
|
2109
|
+
ctx.globalAlpha = draft ? 0.5 : 1;
|
|
2110
|
+
ctx.fillRect(lineLeft, bandTop, lineRight - lineLeft, bandBottom - bandTop);
|
|
2111
|
+
ctx.restore();
|
|
2112
|
+
}
|
|
2113
|
+
ctx.save();
|
|
2114
|
+
ctx.lineWidth = drawing.width;
|
|
2115
|
+
applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2116
|
+
for (const level of levelLines) {
|
|
2117
|
+
ctx.strokeStyle = level.color;
|
|
2118
|
+
ctx.beginPath();
|
|
2119
|
+
ctx.moveTo(crisp(lineLeft), crisp(level.y));
|
|
2120
|
+
ctx.lineTo(crisp(lineRight), crisp(level.y));
|
|
2121
|
+
ctx.stroke();
|
|
2077
2122
|
}
|
|
2123
|
+
ctx.restore();
|
|
2124
|
+
ctx.save();
|
|
2125
|
+
ctx.strokeStyle = "rgba(148,163,184,0.7)";
|
|
2126
|
+
ctx.setLineDash([4, 4]);
|
|
2127
|
+
ctx.lineWidth = 1;
|
|
2128
|
+
ctx.beginPath();
|
|
2129
|
+
ctx.moveTo(firstX, firstY);
|
|
2130
|
+
ctx.lineTo(secondX, secondY);
|
|
2131
|
+
ctx.stroke();
|
|
2132
|
+
ctx.restore();
|
|
2133
|
+
drawDrawingHandle(firstX, firstY, drawing.color);
|
|
2134
|
+
drawDrawingHandle(secondX, secondY, drawing.color);
|
|
2135
|
+
const prevFont = ctx.font;
|
|
2136
|
+
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2137
|
+
for (const level of levelLines) {
|
|
2138
|
+
const labelText = `${level.ratio} (${formatPrice(level.price)})`;
|
|
2139
|
+
const textWidth = ctx.measureText(labelText).width;
|
|
2140
|
+
const padding = 4;
|
|
2141
|
+
const bgX = lineLeft + 4;
|
|
2142
|
+
const bgY = level.y - 9;
|
|
2143
|
+
ctx.fillStyle = mergedOptions.backgroundColor;
|
|
2144
|
+
fillRoundedRect(bgX, bgY, textWidth + padding * 2, 16, 3);
|
|
2145
|
+
ctx.fillStyle = level.color;
|
|
2146
|
+
ctx.textAlign = "left";
|
|
2147
|
+
ctx.textBaseline = "middle";
|
|
2148
|
+
ctx.fillText(labelText, bgX + padding, bgY + 8);
|
|
2149
|
+
}
|
|
2150
|
+
ctx.font = prevFont;
|
|
2078
2151
|
}
|
|
2079
2152
|
}
|
|
2080
2153
|
ctx.restore();
|
|
@@ -3162,6 +3235,28 @@ function createChart(element, options = {}) {
|
|
|
3162
3235
|
if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
|
|
3163
3236
|
return { drawing, target: "line" };
|
|
3164
3237
|
}
|
|
3238
|
+
} else if (drawing.type === "fib-retracement") {
|
|
3239
|
+
const first = drawing.points[0];
|
|
3240
|
+
const second = drawing.points[1];
|
|
3241
|
+
if (!first || !second) continue;
|
|
3242
|
+
const x1 = canvasXFromDrawingPoint(first);
|
|
3243
|
+
const y1 = canvasYFromDrawingPrice(first.price);
|
|
3244
|
+
const x2 = canvasXFromDrawingPoint(second);
|
|
3245
|
+
const y2 = canvasYFromDrawingPrice(second.price);
|
|
3246
|
+
if (Math.hypot(x - x1, y - y1) <= 9) {
|
|
3247
|
+
return { drawing, target: "handle", pointIndex: 0 };
|
|
3248
|
+
}
|
|
3249
|
+
if (Math.hypot(x - x2, y - y2) <= 9) {
|
|
3250
|
+
return { drawing, target: "handle", pointIndex: 1 };
|
|
3251
|
+
}
|
|
3252
|
+
const fibRatios = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
|
|
3253
|
+
for (const ratio of fibRatios) {
|
|
3254
|
+
const price = first.price + (second.price - first.price) * (1 - ratio);
|
|
3255
|
+
const lineY = canvasYFromDrawingPrice(price);
|
|
3256
|
+
if (Math.abs(y - lineY) <= 5) {
|
|
3257
|
+
return { drawing, target: "line" };
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3165
3260
|
}
|
|
3166
3261
|
}
|
|
3167
3262
|
return null;
|
|
@@ -3263,6 +3358,29 @@ function createChart(element, options = {}) {
|
|
|
3263
3358
|
draw();
|
|
3264
3359
|
return true;
|
|
3265
3360
|
}
|
|
3361
|
+
if (activeDrawingTool === "fib-retracement") {
|
|
3362
|
+
if (draftDrawing?.type === "fib-retracement") {
|
|
3363
|
+
const completed = normalizeDrawingState({
|
|
3364
|
+
...serializeDrawing(draftDrawing),
|
|
3365
|
+
points: [draftDrawing.points[0], point]
|
|
3366
|
+
});
|
|
3367
|
+
drawings.push(completed);
|
|
3368
|
+
draftDrawing = null;
|
|
3369
|
+
activeDrawingTool = null;
|
|
3370
|
+
emitDrawingsChange();
|
|
3371
|
+
draw();
|
|
3372
|
+
return true;
|
|
3373
|
+
}
|
|
3374
|
+
draftDrawing = normalizeDrawingState({
|
|
3375
|
+
type: "fib-retracement",
|
|
3376
|
+
points: [point, point],
|
|
3377
|
+
color: "#2563eb",
|
|
3378
|
+
style: "solid",
|
|
3379
|
+
width: 1
|
|
3380
|
+
});
|
|
3381
|
+
draw();
|
|
3382
|
+
return true;
|
|
3383
|
+
}
|
|
3266
3384
|
return false;
|
|
3267
3385
|
};
|
|
3268
3386
|
const updateDrawingDrag = (x, y) => {
|
|
@@ -3498,7 +3616,7 @@ function createChart(element, options = {}) {
|
|
|
3498
3616
|
setCrosshairPoint(null);
|
|
3499
3617
|
return;
|
|
3500
3618
|
}
|
|
3501
|
-
if (draftDrawing && activeDrawingTool === "trendline") {
|
|
3619
|
+
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "fib-retracement")) {
|
|
3502
3620
|
const nextPoint = drawingPointFromCanvas(point.x, point.y);
|
|
3503
3621
|
if (nextPoint) {
|
|
3504
3622
|
draftDrawing = {
|
package/dist/index.cjs
CHANGED
|
@@ -2021,6 +2021,16 @@ function createChart(element, options = {}) {
|
|
|
2021
2021
|
return chartBottom - (price - yMin) / yRange * chartHeight;
|
|
2022
2022
|
};
|
|
2023
2023
|
const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
|
|
2024
|
+
const FIB_LEVELS = [
|
|
2025
|
+
{ ratio: 0, color: "#787b86" },
|
|
2026
|
+
{ ratio: 0.236, color: "#f23645", band: "rgba(242,54,69,0.10)" },
|
|
2027
|
+
{ ratio: 0.382, color: "#ff9800", band: "rgba(255,152,0,0.10)" },
|
|
2028
|
+
{ ratio: 0.5, color: "#4caf50", band: "rgba(76,175,80,0.10)" },
|
|
2029
|
+
{ ratio: 0.618, color: "#089981", band: "rgba(8,153,129,0.10)" },
|
|
2030
|
+
{ ratio: 0.786, color: "#00bcd4", band: "rgba(0,188,212,0.10)" },
|
|
2031
|
+
{ ratio: 1, color: "#787b86", band: "rgba(120,123,134,0.10)" },
|
|
2032
|
+
{ ratio: 1.618, color: "#2962ff", band: "rgba(41,98,255,0.08)" }
|
|
2033
|
+
];
|
|
2024
2034
|
const drawDrawingHandle = (x, y, color) => {
|
|
2025
2035
|
ctx.save();
|
|
2026
2036
|
ctx.setLineDash([]);
|
|
@@ -2033,27 +2043,24 @@ function createChart(element, options = {}) {
|
|
|
2033
2043
|
ctx.stroke();
|
|
2034
2044
|
ctx.restore();
|
|
2035
2045
|
};
|
|
2036
|
-
const drawDrawingLabel = (labelText, anchorX, anchorY, color
|
|
2046
|
+
const drawDrawingLabel = (labelText, anchorX, anchorY, color) => {
|
|
2037
2047
|
if (!labelText) return;
|
|
2038
2048
|
ctx.save();
|
|
2039
|
-
ctx.translate(anchorX, anchorY);
|
|
2040
|
-
ctx.rotate(angle);
|
|
2041
|
-
ctx.translate(0, -8);
|
|
2042
2049
|
const prevFont = ctx.font;
|
|
2043
2050
|
ctx.font = `500 12px ${mergedOptions.fontFamily}`;
|
|
2044
2051
|
const textWidth = ctx.measureText(labelText).width;
|
|
2045
2052
|
const paddingX = 6;
|
|
2046
|
-
const paddingY = 3;
|
|
2047
2053
|
const bgWidth = textWidth + paddingX * 2;
|
|
2048
2054
|
const bgHeight = 18;
|
|
2055
|
+
const bgX = anchorX - bgWidth / 2;
|
|
2056
|
+
const bgY = anchorY - bgHeight - 6;
|
|
2049
2057
|
ctx.fillStyle = `${color}1f`;
|
|
2050
|
-
fillRoundedRect(
|
|
2058
|
+
fillRoundedRect(bgX, bgY, bgWidth, bgHeight, 4);
|
|
2051
2059
|
ctx.fillStyle = color;
|
|
2052
2060
|
ctx.textAlign = "center";
|
|
2053
2061
|
ctx.textBaseline = "middle";
|
|
2054
|
-
ctx.fillText(labelText,
|
|
2062
|
+
ctx.fillText(labelText, anchorX, bgY + bgHeight / 2);
|
|
2055
2063
|
ctx.font = prevFont;
|
|
2056
|
-
void paddingY;
|
|
2057
2064
|
ctx.restore();
|
|
2058
2065
|
};
|
|
2059
2066
|
const drawDrawing = (drawing, draft = false) => {
|
|
@@ -2076,7 +2083,8 @@ function createChart(element, options = {}) {
|
|
|
2076
2083
|
ctx.stroke();
|
|
2077
2084
|
drawDrawingHandle(handleX, y, drawing.color);
|
|
2078
2085
|
if (drawing.label) {
|
|
2079
|
-
|
|
2086
|
+
const midX = (chartLeft + chartRight) / 2;
|
|
2087
|
+
drawDrawingLabel(drawing.label, midX, y, drawing.color);
|
|
2080
2088
|
}
|
|
2081
2089
|
}
|
|
2082
2090
|
} else if (drawing.type === "trendline") {
|
|
@@ -2096,9 +2104,74 @@ function createChart(element, options = {}) {
|
|
|
2096
2104
|
if (drawing.label) {
|
|
2097
2105
|
const midX = (firstX + secondX) / 2;
|
|
2098
2106
|
const midY = (firstY + secondY) / 2;
|
|
2099
|
-
|
|
2100
|
-
|
|
2107
|
+
drawDrawingLabel(drawing.label, midX, midY, drawing.color);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
} else if (drawing.type === "fib-retracement") {
|
|
2111
|
+
const first = drawing.points[0];
|
|
2112
|
+
const second = drawing.points[1];
|
|
2113
|
+
if (first && second) {
|
|
2114
|
+
const firstX = xFromDrawingPoint(first);
|
|
2115
|
+
const secondX = xFromDrawingPoint(second);
|
|
2116
|
+
const firstY = yFromPrice(first.price);
|
|
2117
|
+
const secondY = yFromPrice(second.price);
|
|
2118
|
+
const lineLeft = chartLeft;
|
|
2119
|
+
const lineRight = chartRight;
|
|
2120
|
+
const levelLines = FIB_LEVELS.map((level) => {
|
|
2121
|
+
const price = first.price + (second.price - first.price) * (1 - level.ratio);
|
|
2122
|
+
return { ...level, price, y: yFromPrice(price) };
|
|
2123
|
+
});
|
|
2124
|
+
for (let index = 0; index < levelLines.length - 1; index += 1) {
|
|
2125
|
+
const top = levelLines[index];
|
|
2126
|
+
const bottom = levelLines[index + 1];
|
|
2127
|
+
const fillColor = bottom.band ?? top.band;
|
|
2128
|
+
if (!fillColor) continue;
|
|
2129
|
+
const bandTop = Math.min(top.y, bottom.y);
|
|
2130
|
+
const bandBottom = Math.max(top.y, bottom.y);
|
|
2131
|
+
ctx.save();
|
|
2132
|
+
ctx.fillStyle = fillColor;
|
|
2133
|
+
ctx.globalAlpha = draft ? 0.5 : 1;
|
|
2134
|
+
ctx.fillRect(lineLeft, bandTop, lineRight - lineLeft, bandBottom - bandTop);
|
|
2135
|
+
ctx.restore();
|
|
2136
|
+
}
|
|
2137
|
+
ctx.save();
|
|
2138
|
+
ctx.lineWidth = drawing.width;
|
|
2139
|
+
applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2140
|
+
for (const level of levelLines) {
|
|
2141
|
+
ctx.strokeStyle = level.color;
|
|
2142
|
+
ctx.beginPath();
|
|
2143
|
+
ctx.moveTo(crisp(lineLeft), crisp(level.y));
|
|
2144
|
+
ctx.lineTo(crisp(lineRight), crisp(level.y));
|
|
2145
|
+
ctx.stroke();
|
|
2101
2146
|
}
|
|
2147
|
+
ctx.restore();
|
|
2148
|
+
ctx.save();
|
|
2149
|
+
ctx.strokeStyle = "rgba(148,163,184,0.7)";
|
|
2150
|
+
ctx.setLineDash([4, 4]);
|
|
2151
|
+
ctx.lineWidth = 1;
|
|
2152
|
+
ctx.beginPath();
|
|
2153
|
+
ctx.moveTo(firstX, firstY);
|
|
2154
|
+
ctx.lineTo(secondX, secondY);
|
|
2155
|
+
ctx.stroke();
|
|
2156
|
+
ctx.restore();
|
|
2157
|
+
drawDrawingHandle(firstX, firstY, drawing.color);
|
|
2158
|
+
drawDrawingHandle(secondX, secondY, drawing.color);
|
|
2159
|
+
const prevFont = ctx.font;
|
|
2160
|
+
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2161
|
+
for (const level of levelLines) {
|
|
2162
|
+
const labelText = `${level.ratio} (${formatPrice(level.price)})`;
|
|
2163
|
+
const textWidth = ctx.measureText(labelText).width;
|
|
2164
|
+
const padding = 4;
|
|
2165
|
+
const bgX = lineLeft + 4;
|
|
2166
|
+
const bgY = level.y - 9;
|
|
2167
|
+
ctx.fillStyle = mergedOptions.backgroundColor;
|
|
2168
|
+
fillRoundedRect(bgX, bgY, textWidth + padding * 2, 16, 3);
|
|
2169
|
+
ctx.fillStyle = level.color;
|
|
2170
|
+
ctx.textAlign = "left";
|
|
2171
|
+
ctx.textBaseline = "middle";
|
|
2172
|
+
ctx.fillText(labelText, bgX + padding, bgY + 8);
|
|
2173
|
+
}
|
|
2174
|
+
ctx.font = prevFont;
|
|
2102
2175
|
}
|
|
2103
2176
|
}
|
|
2104
2177
|
ctx.restore();
|
|
@@ -3186,6 +3259,28 @@ function createChart(element, options = {}) {
|
|
|
3186
3259
|
if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
|
|
3187
3260
|
return { drawing, target: "line" };
|
|
3188
3261
|
}
|
|
3262
|
+
} else if (drawing.type === "fib-retracement") {
|
|
3263
|
+
const first = drawing.points[0];
|
|
3264
|
+
const second = drawing.points[1];
|
|
3265
|
+
if (!first || !second) continue;
|
|
3266
|
+
const x1 = canvasXFromDrawingPoint(first);
|
|
3267
|
+
const y1 = canvasYFromDrawingPrice(first.price);
|
|
3268
|
+
const x2 = canvasXFromDrawingPoint(second);
|
|
3269
|
+
const y2 = canvasYFromDrawingPrice(second.price);
|
|
3270
|
+
if (Math.hypot(x - x1, y - y1) <= 9) {
|
|
3271
|
+
return { drawing, target: "handle", pointIndex: 0 };
|
|
3272
|
+
}
|
|
3273
|
+
if (Math.hypot(x - x2, y - y2) <= 9) {
|
|
3274
|
+
return { drawing, target: "handle", pointIndex: 1 };
|
|
3275
|
+
}
|
|
3276
|
+
const fibRatios = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
|
|
3277
|
+
for (const ratio of fibRatios) {
|
|
3278
|
+
const price = first.price + (second.price - first.price) * (1 - ratio);
|
|
3279
|
+
const lineY = canvasYFromDrawingPrice(price);
|
|
3280
|
+
if (Math.abs(y - lineY) <= 5) {
|
|
3281
|
+
return { drawing, target: "line" };
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3189
3284
|
}
|
|
3190
3285
|
}
|
|
3191
3286
|
return null;
|
|
@@ -3287,6 +3382,29 @@ function createChart(element, options = {}) {
|
|
|
3287
3382
|
draw();
|
|
3288
3383
|
return true;
|
|
3289
3384
|
}
|
|
3385
|
+
if (activeDrawingTool === "fib-retracement") {
|
|
3386
|
+
if (draftDrawing?.type === "fib-retracement") {
|
|
3387
|
+
const completed = normalizeDrawingState({
|
|
3388
|
+
...serializeDrawing(draftDrawing),
|
|
3389
|
+
points: [draftDrawing.points[0], point]
|
|
3390
|
+
});
|
|
3391
|
+
drawings.push(completed);
|
|
3392
|
+
draftDrawing = null;
|
|
3393
|
+
activeDrawingTool = null;
|
|
3394
|
+
emitDrawingsChange();
|
|
3395
|
+
draw();
|
|
3396
|
+
return true;
|
|
3397
|
+
}
|
|
3398
|
+
draftDrawing = normalizeDrawingState({
|
|
3399
|
+
type: "fib-retracement",
|
|
3400
|
+
points: [point, point],
|
|
3401
|
+
color: "#2563eb",
|
|
3402
|
+
style: "solid",
|
|
3403
|
+
width: 1
|
|
3404
|
+
});
|
|
3405
|
+
draw();
|
|
3406
|
+
return true;
|
|
3407
|
+
}
|
|
3290
3408
|
return false;
|
|
3291
3409
|
};
|
|
3292
3410
|
const updateDrawingDrag = (x, y) => {
|
|
@@ -3522,7 +3640,7 @@ function createChart(element, options = {}) {
|
|
|
3522
3640
|
setCrosshairPoint(null);
|
|
3523
3641
|
return;
|
|
3524
3642
|
}
|
|
3525
|
-
if (draftDrawing && activeDrawingTool === "trendline") {
|
|
3643
|
+
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "fib-retracement")) {
|
|
3526
3644
|
const nextPoint = drawingPointFromCanvas(point.x, point.y);
|
|
3527
3645
|
if (nextPoint) {
|
|
3528
3646
|
draftDrawing = {
|
package/dist/index.d.cts
CHANGED
|
@@ -43,7 +43,7 @@ interface ChartOptions {
|
|
|
43
43
|
drawings?: DrawingObjectOptions[];
|
|
44
44
|
}
|
|
45
45
|
type IndicatorPane = "overlay" | "separate";
|
|
46
|
-
type DrawingToolType = "horizontal-line" | "trendline";
|
|
46
|
+
type DrawingToolType = "horizontal-line" | "trendline" | "fib-retracement";
|
|
47
47
|
interface DrawingPoint {
|
|
48
48
|
index: number;
|
|
49
49
|
price: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ interface ChartOptions {
|
|
|
43
43
|
drawings?: DrawingObjectOptions[];
|
|
44
44
|
}
|
|
45
45
|
type IndicatorPane = "overlay" | "separate";
|
|
46
|
-
type DrawingToolType = "horizontal-line" | "trendline";
|
|
46
|
+
type DrawingToolType = "horizontal-line" | "trendline" | "fib-retracement";
|
|
47
47
|
interface DrawingPoint {
|
|
48
48
|
index: number;
|
|
49
49
|
price: number;
|
package/dist/index.js
CHANGED
|
@@ -1997,6 +1997,16 @@ function createChart(element, options = {}) {
|
|
|
1997
1997
|
return chartBottom - (price - yMin) / yRange * chartHeight;
|
|
1998
1998
|
};
|
|
1999
1999
|
const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
|
|
2000
|
+
const FIB_LEVELS = [
|
|
2001
|
+
{ ratio: 0, color: "#787b86" },
|
|
2002
|
+
{ ratio: 0.236, color: "#f23645", band: "rgba(242,54,69,0.10)" },
|
|
2003
|
+
{ ratio: 0.382, color: "#ff9800", band: "rgba(255,152,0,0.10)" },
|
|
2004
|
+
{ ratio: 0.5, color: "#4caf50", band: "rgba(76,175,80,0.10)" },
|
|
2005
|
+
{ ratio: 0.618, color: "#089981", band: "rgba(8,153,129,0.10)" },
|
|
2006
|
+
{ ratio: 0.786, color: "#00bcd4", band: "rgba(0,188,212,0.10)" },
|
|
2007
|
+
{ ratio: 1, color: "#787b86", band: "rgba(120,123,134,0.10)" },
|
|
2008
|
+
{ ratio: 1.618, color: "#2962ff", band: "rgba(41,98,255,0.08)" }
|
|
2009
|
+
];
|
|
2000
2010
|
const drawDrawingHandle = (x, y, color) => {
|
|
2001
2011
|
ctx.save();
|
|
2002
2012
|
ctx.setLineDash([]);
|
|
@@ -2009,27 +2019,24 @@ function createChart(element, options = {}) {
|
|
|
2009
2019
|
ctx.stroke();
|
|
2010
2020
|
ctx.restore();
|
|
2011
2021
|
};
|
|
2012
|
-
const drawDrawingLabel = (labelText, anchorX, anchorY, color
|
|
2022
|
+
const drawDrawingLabel = (labelText, anchorX, anchorY, color) => {
|
|
2013
2023
|
if (!labelText) return;
|
|
2014
2024
|
ctx.save();
|
|
2015
|
-
ctx.translate(anchorX, anchorY);
|
|
2016
|
-
ctx.rotate(angle);
|
|
2017
|
-
ctx.translate(0, -8);
|
|
2018
2025
|
const prevFont = ctx.font;
|
|
2019
2026
|
ctx.font = `500 12px ${mergedOptions.fontFamily}`;
|
|
2020
2027
|
const textWidth = ctx.measureText(labelText).width;
|
|
2021
2028
|
const paddingX = 6;
|
|
2022
|
-
const paddingY = 3;
|
|
2023
2029
|
const bgWidth = textWidth + paddingX * 2;
|
|
2024
2030
|
const bgHeight = 18;
|
|
2031
|
+
const bgX = anchorX - bgWidth / 2;
|
|
2032
|
+
const bgY = anchorY - bgHeight - 6;
|
|
2025
2033
|
ctx.fillStyle = `${color}1f`;
|
|
2026
|
-
fillRoundedRect(
|
|
2034
|
+
fillRoundedRect(bgX, bgY, bgWidth, bgHeight, 4);
|
|
2027
2035
|
ctx.fillStyle = color;
|
|
2028
2036
|
ctx.textAlign = "center";
|
|
2029
2037
|
ctx.textBaseline = "middle";
|
|
2030
|
-
ctx.fillText(labelText,
|
|
2038
|
+
ctx.fillText(labelText, anchorX, bgY + bgHeight / 2);
|
|
2031
2039
|
ctx.font = prevFont;
|
|
2032
|
-
void paddingY;
|
|
2033
2040
|
ctx.restore();
|
|
2034
2041
|
};
|
|
2035
2042
|
const drawDrawing = (drawing, draft = false) => {
|
|
@@ -2052,7 +2059,8 @@ function createChart(element, options = {}) {
|
|
|
2052
2059
|
ctx.stroke();
|
|
2053
2060
|
drawDrawingHandle(handleX, y, drawing.color);
|
|
2054
2061
|
if (drawing.label) {
|
|
2055
|
-
|
|
2062
|
+
const midX = (chartLeft + chartRight) / 2;
|
|
2063
|
+
drawDrawingLabel(drawing.label, midX, y, drawing.color);
|
|
2056
2064
|
}
|
|
2057
2065
|
}
|
|
2058
2066
|
} else if (drawing.type === "trendline") {
|
|
@@ -2072,9 +2080,74 @@ function createChart(element, options = {}) {
|
|
|
2072
2080
|
if (drawing.label) {
|
|
2073
2081
|
const midX = (firstX + secondX) / 2;
|
|
2074
2082
|
const midY = (firstY + secondY) / 2;
|
|
2075
|
-
|
|
2076
|
-
|
|
2083
|
+
drawDrawingLabel(drawing.label, midX, midY, drawing.color);
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
} else if (drawing.type === "fib-retracement") {
|
|
2087
|
+
const first = drawing.points[0];
|
|
2088
|
+
const second = drawing.points[1];
|
|
2089
|
+
if (first && second) {
|
|
2090
|
+
const firstX = xFromDrawingPoint(first);
|
|
2091
|
+
const secondX = xFromDrawingPoint(second);
|
|
2092
|
+
const firstY = yFromPrice(first.price);
|
|
2093
|
+
const secondY = yFromPrice(second.price);
|
|
2094
|
+
const lineLeft = chartLeft;
|
|
2095
|
+
const lineRight = chartRight;
|
|
2096
|
+
const levelLines = FIB_LEVELS.map((level) => {
|
|
2097
|
+
const price = first.price + (second.price - first.price) * (1 - level.ratio);
|
|
2098
|
+
return { ...level, price, y: yFromPrice(price) };
|
|
2099
|
+
});
|
|
2100
|
+
for (let index = 0; index < levelLines.length - 1; index += 1) {
|
|
2101
|
+
const top = levelLines[index];
|
|
2102
|
+
const bottom = levelLines[index + 1];
|
|
2103
|
+
const fillColor = bottom.band ?? top.band;
|
|
2104
|
+
if (!fillColor) continue;
|
|
2105
|
+
const bandTop = Math.min(top.y, bottom.y);
|
|
2106
|
+
const bandBottom = Math.max(top.y, bottom.y);
|
|
2107
|
+
ctx.save();
|
|
2108
|
+
ctx.fillStyle = fillColor;
|
|
2109
|
+
ctx.globalAlpha = draft ? 0.5 : 1;
|
|
2110
|
+
ctx.fillRect(lineLeft, bandTop, lineRight - lineLeft, bandBottom - bandTop);
|
|
2111
|
+
ctx.restore();
|
|
2112
|
+
}
|
|
2113
|
+
ctx.save();
|
|
2114
|
+
ctx.lineWidth = drawing.width;
|
|
2115
|
+
applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2116
|
+
for (const level of levelLines) {
|
|
2117
|
+
ctx.strokeStyle = level.color;
|
|
2118
|
+
ctx.beginPath();
|
|
2119
|
+
ctx.moveTo(crisp(lineLeft), crisp(level.y));
|
|
2120
|
+
ctx.lineTo(crisp(lineRight), crisp(level.y));
|
|
2121
|
+
ctx.stroke();
|
|
2077
2122
|
}
|
|
2123
|
+
ctx.restore();
|
|
2124
|
+
ctx.save();
|
|
2125
|
+
ctx.strokeStyle = "rgba(148,163,184,0.7)";
|
|
2126
|
+
ctx.setLineDash([4, 4]);
|
|
2127
|
+
ctx.lineWidth = 1;
|
|
2128
|
+
ctx.beginPath();
|
|
2129
|
+
ctx.moveTo(firstX, firstY);
|
|
2130
|
+
ctx.lineTo(secondX, secondY);
|
|
2131
|
+
ctx.stroke();
|
|
2132
|
+
ctx.restore();
|
|
2133
|
+
drawDrawingHandle(firstX, firstY, drawing.color);
|
|
2134
|
+
drawDrawingHandle(secondX, secondY, drawing.color);
|
|
2135
|
+
const prevFont = ctx.font;
|
|
2136
|
+
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2137
|
+
for (const level of levelLines) {
|
|
2138
|
+
const labelText = `${level.ratio} (${formatPrice(level.price)})`;
|
|
2139
|
+
const textWidth = ctx.measureText(labelText).width;
|
|
2140
|
+
const padding = 4;
|
|
2141
|
+
const bgX = lineLeft + 4;
|
|
2142
|
+
const bgY = level.y - 9;
|
|
2143
|
+
ctx.fillStyle = mergedOptions.backgroundColor;
|
|
2144
|
+
fillRoundedRect(bgX, bgY, textWidth + padding * 2, 16, 3);
|
|
2145
|
+
ctx.fillStyle = level.color;
|
|
2146
|
+
ctx.textAlign = "left";
|
|
2147
|
+
ctx.textBaseline = "middle";
|
|
2148
|
+
ctx.fillText(labelText, bgX + padding, bgY + 8);
|
|
2149
|
+
}
|
|
2150
|
+
ctx.font = prevFont;
|
|
2078
2151
|
}
|
|
2079
2152
|
}
|
|
2080
2153
|
ctx.restore();
|
|
@@ -3162,6 +3235,28 @@ function createChart(element, options = {}) {
|
|
|
3162
3235
|
if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
|
|
3163
3236
|
return { drawing, target: "line" };
|
|
3164
3237
|
}
|
|
3238
|
+
} else if (drawing.type === "fib-retracement") {
|
|
3239
|
+
const first = drawing.points[0];
|
|
3240
|
+
const second = drawing.points[1];
|
|
3241
|
+
if (!first || !second) continue;
|
|
3242
|
+
const x1 = canvasXFromDrawingPoint(first);
|
|
3243
|
+
const y1 = canvasYFromDrawingPrice(first.price);
|
|
3244
|
+
const x2 = canvasXFromDrawingPoint(second);
|
|
3245
|
+
const y2 = canvasYFromDrawingPrice(second.price);
|
|
3246
|
+
if (Math.hypot(x - x1, y - y1) <= 9) {
|
|
3247
|
+
return { drawing, target: "handle", pointIndex: 0 };
|
|
3248
|
+
}
|
|
3249
|
+
if (Math.hypot(x - x2, y - y2) <= 9) {
|
|
3250
|
+
return { drawing, target: "handle", pointIndex: 1 };
|
|
3251
|
+
}
|
|
3252
|
+
const fibRatios = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
|
|
3253
|
+
for (const ratio of fibRatios) {
|
|
3254
|
+
const price = first.price + (second.price - first.price) * (1 - ratio);
|
|
3255
|
+
const lineY = canvasYFromDrawingPrice(price);
|
|
3256
|
+
if (Math.abs(y - lineY) <= 5) {
|
|
3257
|
+
return { drawing, target: "line" };
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3165
3260
|
}
|
|
3166
3261
|
}
|
|
3167
3262
|
return null;
|
|
@@ -3263,6 +3358,29 @@ function createChart(element, options = {}) {
|
|
|
3263
3358
|
draw();
|
|
3264
3359
|
return true;
|
|
3265
3360
|
}
|
|
3361
|
+
if (activeDrawingTool === "fib-retracement") {
|
|
3362
|
+
if (draftDrawing?.type === "fib-retracement") {
|
|
3363
|
+
const completed = normalizeDrawingState({
|
|
3364
|
+
...serializeDrawing(draftDrawing),
|
|
3365
|
+
points: [draftDrawing.points[0], point]
|
|
3366
|
+
});
|
|
3367
|
+
drawings.push(completed);
|
|
3368
|
+
draftDrawing = null;
|
|
3369
|
+
activeDrawingTool = null;
|
|
3370
|
+
emitDrawingsChange();
|
|
3371
|
+
draw();
|
|
3372
|
+
return true;
|
|
3373
|
+
}
|
|
3374
|
+
draftDrawing = normalizeDrawingState({
|
|
3375
|
+
type: "fib-retracement",
|
|
3376
|
+
points: [point, point],
|
|
3377
|
+
color: "#2563eb",
|
|
3378
|
+
style: "solid",
|
|
3379
|
+
width: 1
|
|
3380
|
+
});
|
|
3381
|
+
draw();
|
|
3382
|
+
return true;
|
|
3383
|
+
}
|
|
3266
3384
|
return false;
|
|
3267
3385
|
};
|
|
3268
3386
|
const updateDrawingDrag = (x, y) => {
|
|
@@ -3498,7 +3616,7 @@ function createChart(element, options = {}) {
|
|
|
3498
3616
|
setCrosshairPoint(null);
|
|
3499
3617
|
return;
|
|
3500
3618
|
}
|
|
3501
|
-
if (draftDrawing && activeDrawingTool === "trendline") {
|
|
3619
|
+
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "fib-retracement")) {
|
|
3502
3620
|
const nextPoint = drawingPointFromCanvas(point.x, point.y);
|
|
3503
3621
|
if (nextPoint) {
|
|
3504
3622
|
draftDrawing = {
|