hyperprop-charting-library 0.1.135 → 0.1.136
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 +549 -5
- package/dist/hyperprop-charting-library.d.ts +49 -1
- package/dist/hyperprop-charting-library.js +549 -5
- package/dist/index.cjs +549 -5
- package/dist/index.d.cts +49 -1
- package/dist/index.d.ts +49 -1
- package/dist/index.js +549 -5
- package/docs/API.md +44 -3
- package/package.json +1 -1
|
@@ -4818,6 +4818,216 @@ function createChart(element, options = {}) {
|
|
|
4818
4818
|
drawDrawingLabel(drawing.label, (leftX + rightX) / 2, topY - 4, drawing.color);
|
|
4819
4819
|
}
|
|
4820
4820
|
}
|
|
4821
|
+
} else if (drawing.type === "ellipse") {
|
|
4822
|
+
const p0 = drawing.points[0];
|
|
4823
|
+
const p1 = drawing.points[1];
|
|
4824
|
+
if (p0 && p1) {
|
|
4825
|
+
const px0 = xFromDrawingPoint(p0);
|
|
4826
|
+
const px1 = xFromDrawingPoint(p1);
|
|
4827
|
+
const y0 = yFromPrice(p0.price);
|
|
4828
|
+
const y1 = yFromPrice(p1.price);
|
|
4829
|
+
const cx = (px0 + px1) / 2;
|
|
4830
|
+
const cy = (y0 + y1) / 2;
|
|
4831
|
+
const rx = Math.max(1, Math.abs(px1 - px0) / 2);
|
|
4832
|
+
const ry = Math.max(1, Math.abs(y1 - y0) / 2);
|
|
4833
|
+
ctx.save();
|
|
4834
|
+
ctx.globalAlpha = draft ? 0.6 : 1;
|
|
4835
|
+
ctx.beginPath();
|
|
4836
|
+
ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
|
|
4837
|
+
ctx.fillStyle = hexToRgba(drawing.color, 0.14);
|
|
4838
|
+
ctx.fill();
|
|
4839
|
+
ctx.lineWidth = Math.max(1, drawing.width);
|
|
4840
|
+
ctx.strokeStyle = drawing.color;
|
|
4841
|
+
applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
4842
|
+
ctx.stroke();
|
|
4843
|
+
ctx.restore();
|
|
4844
|
+
handleAt(px0, y0, drawing.color);
|
|
4845
|
+
handleAt(px1, y1, drawing.color);
|
|
4846
|
+
handleAt(px0, y1, drawing.color);
|
|
4847
|
+
handleAt(px1, y0, drawing.color);
|
|
4848
|
+
if (drawing.label) {
|
|
4849
|
+
drawDrawingLabel(drawing.label, cx, cy - ry - 4, drawing.color);
|
|
4850
|
+
}
|
|
4851
|
+
}
|
|
4852
|
+
} else if (drawing.type === "parallel-channel") {
|
|
4853
|
+
const p0 = drawing.points[0];
|
|
4854
|
+
const p1 = drawing.points[1];
|
|
4855
|
+
const p2 = drawing.points[2];
|
|
4856
|
+
if (p0 && p1) {
|
|
4857
|
+
const x0 = xFromDrawingPoint(p0);
|
|
4858
|
+
const y0 = yFromPrice(p0.price);
|
|
4859
|
+
const x1 = xFromDrawingPoint(p1);
|
|
4860
|
+
const y1 = yFromPrice(p1.price);
|
|
4861
|
+
ctx.beginPath();
|
|
4862
|
+
ctx.moveTo(x0, y0);
|
|
4863
|
+
ctx.lineTo(x1, y1);
|
|
4864
|
+
ctx.stroke();
|
|
4865
|
+
handleAt(x0, y0, drawing.color);
|
|
4866
|
+
handleAt(x1, y1, drawing.color);
|
|
4867
|
+
if (p2) {
|
|
4868
|
+
const indexSpan = p1.index - p0.index;
|
|
4869
|
+
const slope = indexSpan !== 0 ? (p1.price - p0.price) / indexSpan : 0;
|
|
4870
|
+
const basePriceAtP2 = p0.price + slope * (p2.index - p0.index);
|
|
4871
|
+
const offset = p2.price - basePriceAtP2;
|
|
4872
|
+
const q0x = x0;
|
|
4873
|
+
const q0y = yFromPrice(p0.price + offset);
|
|
4874
|
+
const q1x = x1;
|
|
4875
|
+
const q1y = yFromPrice(p1.price + offset);
|
|
4876
|
+
ctx.save();
|
|
4877
|
+
ctx.globalAlpha = draft ? 0.5 : 1;
|
|
4878
|
+
ctx.fillStyle = hexToRgba(drawing.color, 0.08);
|
|
4879
|
+
ctx.beginPath();
|
|
4880
|
+
ctx.moveTo(x0, y0);
|
|
4881
|
+
ctx.lineTo(x1, y1);
|
|
4882
|
+
ctx.lineTo(q1x, q1y);
|
|
4883
|
+
ctx.lineTo(q0x, q0y);
|
|
4884
|
+
ctx.closePath();
|
|
4885
|
+
ctx.fill();
|
|
4886
|
+
ctx.restore();
|
|
4887
|
+
ctx.beginPath();
|
|
4888
|
+
ctx.moveTo(q0x, q0y);
|
|
4889
|
+
ctx.lineTo(q1x, q1y);
|
|
4890
|
+
ctx.stroke();
|
|
4891
|
+
ctx.save();
|
|
4892
|
+
ctx.setLineDash([4, 4]);
|
|
4893
|
+
ctx.globalAlpha = draft ? 0.5 : 0.7;
|
|
4894
|
+
ctx.beginPath();
|
|
4895
|
+
ctx.moveTo(x0, (y0 + q0y) / 2);
|
|
4896
|
+
ctx.lineTo(x1, (y1 + q1y) / 2);
|
|
4897
|
+
ctx.stroke();
|
|
4898
|
+
ctx.restore();
|
|
4899
|
+
handleAt(xFromDrawingPoint(p2), yFromPrice(p2.price), drawing.color);
|
|
4900
|
+
}
|
|
4901
|
+
if (drawing.label) {
|
|
4902
|
+
drawDrawingLabel(drawing.label, (x0 + x1) / 2, Math.min(y0, y1) - 4, drawing.color);
|
|
4903
|
+
}
|
|
4904
|
+
}
|
|
4905
|
+
} else if (drawing.type === "arrow") {
|
|
4906
|
+
const first = drawing.points[0];
|
|
4907
|
+
const second = drawing.points[1];
|
|
4908
|
+
if (first && second) {
|
|
4909
|
+
const x0 = xFromDrawingPoint(first);
|
|
4910
|
+
const y0 = yFromPrice(first.price);
|
|
4911
|
+
const x1 = xFromDrawingPoint(second);
|
|
4912
|
+
const y1 = yFromPrice(second.price);
|
|
4913
|
+
const angle = Math.atan2(y1 - y0, x1 - x0);
|
|
4914
|
+
const head = 5 + Math.max(1, drawing.width) * 3;
|
|
4915
|
+
const shaftX = x1 - Math.cos(angle) * head * 0.6;
|
|
4916
|
+
const shaftY = y1 - Math.sin(angle) * head * 0.6;
|
|
4917
|
+
ctx.beginPath();
|
|
4918
|
+
ctx.moveTo(x0, y0);
|
|
4919
|
+
ctx.lineTo(shaftX, shaftY);
|
|
4920
|
+
ctx.stroke();
|
|
4921
|
+
ctx.save();
|
|
4922
|
+
ctx.setLineDash([]);
|
|
4923
|
+
ctx.fillStyle = drawing.color;
|
|
4924
|
+
ctx.beginPath();
|
|
4925
|
+
ctx.moveTo(x1, y1);
|
|
4926
|
+
ctx.lineTo(x1 - Math.cos(angle - Math.PI / 7) * head, y1 - Math.sin(angle - Math.PI / 7) * head);
|
|
4927
|
+
ctx.lineTo(x1 - Math.cos(angle + Math.PI / 7) * head, y1 - Math.sin(angle + Math.PI / 7) * head);
|
|
4928
|
+
ctx.closePath();
|
|
4929
|
+
ctx.fill();
|
|
4930
|
+
ctx.restore();
|
|
4931
|
+
handleAt(x0, y0, drawing.color);
|
|
4932
|
+
handleAt(x1, y1, drawing.color);
|
|
4933
|
+
if (drawing.label) {
|
|
4934
|
+
const midX = (x0 + x1) / 2;
|
|
4935
|
+
const midY = (y0 + y1) / 2;
|
|
4936
|
+
drawDrawingLabel(drawing.label, midX, midY, drawing.color);
|
|
4937
|
+
}
|
|
4938
|
+
}
|
|
4939
|
+
} else if (drawing.type === "brush") {
|
|
4940
|
+
const pts = drawing.points;
|
|
4941
|
+
if (pts.length >= 2) {
|
|
4942
|
+
ctx.save();
|
|
4943
|
+
ctx.lineJoin = "round";
|
|
4944
|
+
ctx.lineCap = "round";
|
|
4945
|
+
ctx.beginPath();
|
|
4946
|
+
const startX = xFromDrawingPoint(pts[0]);
|
|
4947
|
+
const startY = yFromPrice(pts[0].price);
|
|
4948
|
+
ctx.moveTo(startX, startY);
|
|
4949
|
+
if (pts.length === 2) {
|
|
4950
|
+
ctx.lineTo(xFromDrawingPoint(pts[1]), yFromPrice(pts[1].price));
|
|
4951
|
+
} else {
|
|
4952
|
+
for (let i = 1; i < pts.length - 1; i += 1) {
|
|
4953
|
+
const cxp = xFromDrawingPoint(pts[i]);
|
|
4954
|
+
const cyp = yFromPrice(pts[i].price);
|
|
4955
|
+
const nx = xFromDrawingPoint(pts[i + 1]);
|
|
4956
|
+
const ny = yFromPrice(pts[i + 1].price);
|
|
4957
|
+
ctx.quadraticCurveTo(cxp, cyp, (cxp + nx) / 2, (cyp + ny) / 2);
|
|
4958
|
+
}
|
|
4959
|
+
const last = pts[pts.length - 1];
|
|
4960
|
+
ctx.lineTo(xFromDrawingPoint(last), yFromPrice(last.price));
|
|
4961
|
+
}
|
|
4962
|
+
ctx.stroke();
|
|
4963
|
+
ctx.restore();
|
|
4964
|
+
if (isSelected) {
|
|
4965
|
+
handleAt(startX, startY, drawing.color);
|
|
4966
|
+
const last = pts[pts.length - 1];
|
|
4967
|
+
handleAt(xFromDrawingPoint(last), yFromPrice(last.price), drawing.color);
|
|
4968
|
+
}
|
|
4969
|
+
}
|
|
4970
|
+
} else if (drawing.type === "callout") {
|
|
4971
|
+
const anchor = drawing.points[0];
|
|
4972
|
+
const boxPoint = drawing.points[1];
|
|
4973
|
+
if (anchor && boxPoint) {
|
|
4974
|
+
const ax = xFromDrawingPoint(anchor);
|
|
4975
|
+
const ay = yFromPrice(anchor.price);
|
|
4976
|
+
const bx = xFromDrawingPoint(boxPoint);
|
|
4977
|
+
const by = yFromPrice(boxPoint.price);
|
|
4978
|
+
const fontSize = Math.max(6, drawing.fontSize);
|
|
4979
|
+
const prevFont = ctx.font;
|
|
4980
|
+
ctx.font = `500 ${fontSize}px ${mergedOptions.fontFamily}`;
|
|
4981
|
+
const rawLabel = drawing.label ?? "";
|
|
4982
|
+
const isPlaceholder = rawLabel.trim().length === 0;
|
|
4983
|
+
const lines = (isPlaceholder ? "Text" : rawLabel).split("\n");
|
|
4984
|
+
const textW = Math.max(1, ...lines.map((line) => measureTextWidth(line)));
|
|
4985
|
+
const padX = 10;
|
|
4986
|
+
const padY = 7;
|
|
4987
|
+
const lineH = Math.round(fontSize * 1.35);
|
|
4988
|
+
const blockW = textW + padX * 2;
|
|
4989
|
+
const blockH = lines.length * lineH + padY * 2;
|
|
4990
|
+
const edgeX = Math.max(bx, Math.min(bx + blockW, ax));
|
|
4991
|
+
const edgeY = Math.max(by, Math.min(by + blockH, ay));
|
|
4992
|
+
const insideBox = ax >= bx && ax <= bx + blockW && ay >= by && ay <= by + blockH;
|
|
4993
|
+
if (!insideBox) {
|
|
4994
|
+
ctx.save();
|
|
4995
|
+
ctx.setLineDash([]);
|
|
4996
|
+
ctx.lineWidth = Math.max(1, drawing.width);
|
|
4997
|
+
ctx.beginPath();
|
|
4998
|
+
ctx.moveTo(edgeX, edgeY);
|
|
4999
|
+
ctx.lineTo(ax, ay);
|
|
5000
|
+
ctx.stroke();
|
|
5001
|
+
const angle = Math.atan2(ay - edgeY, ax - edgeX);
|
|
5002
|
+
const head = 7;
|
|
5003
|
+
ctx.fillStyle = drawing.color;
|
|
5004
|
+
ctx.beginPath();
|
|
5005
|
+
ctx.moveTo(ax, ay);
|
|
5006
|
+
ctx.lineTo(ax - Math.cos(angle - Math.PI / 7) * head, ay - Math.sin(angle - Math.PI / 7) * head);
|
|
5007
|
+
ctx.lineTo(ax - Math.cos(angle + Math.PI / 7) * head, ay - Math.sin(angle + Math.PI / 7) * head);
|
|
5008
|
+
ctx.closePath();
|
|
5009
|
+
ctx.fill();
|
|
5010
|
+
ctx.restore();
|
|
5011
|
+
}
|
|
5012
|
+
ctx.save();
|
|
5013
|
+
ctx.setLineDash([]);
|
|
5014
|
+
ctx.fillStyle = hexToRgba(drawing.color, 0.16);
|
|
5015
|
+
fillRoundedRect(bx, by, blockW, blockH, 6);
|
|
5016
|
+
ctx.strokeStyle = drawing.color;
|
|
5017
|
+
ctx.lineWidth = Math.max(1, drawing.width);
|
|
5018
|
+
strokeRoundedRect(bx, by, blockW, blockH, 6);
|
|
5019
|
+
ctx.fillStyle = drawing.color;
|
|
5020
|
+
ctx.globalAlpha = (draft ? 0.72 : 1) * (isPlaceholder ? 0.55 : 1);
|
|
5021
|
+
ctx.textAlign = "left";
|
|
5022
|
+
ctx.textBaseline = "middle";
|
|
5023
|
+
lines.forEach((line, lineIndex) => {
|
|
5024
|
+
ctx.fillText(line, bx + padX, by + padY + lineIndex * lineH + lineH / 2);
|
|
5025
|
+
});
|
|
5026
|
+
ctx.restore();
|
|
5027
|
+
ctx.font = prevFont;
|
|
5028
|
+
handleAt(ax, ay, drawing.color);
|
|
5029
|
+
handleAt(bx, by, drawing.color);
|
|
5030
|
+
}
|
|
4821
5031
|
} else if (drawing.type === "price-range") {
|
|
4822
5032
|
const p0 = drawing.points[0];
|
|
4823
5033
|
const p1 = drawing.points[1];
|
|
@@ -6872,6 +7082,126 @@ function createChart(element, options = {}) {
|
|
|
6872
7082
|
if (x >= Math.min(px0, px1) && x <= Math.max(px0, px1) && y >= Math.min(y0, y1) && y <= Math.max(y0, y1)) {
|
|
6873
7083
|
return { drawing, target: "line" };
|
|
6874
7084
|
}
|
|
7085
|
+
} else if (drawing.type === "ellipse") {
|
|
7086
|
+
const p0 = drawing.points[0];
|
|
7087
|
+
const p1 = drawing.points[1];
|
|
7088
|
+
if (!p0 || !p1) continue;
|
|
7089
|
+
const px0 = canvasXFromDrawingPoint(p0);
|
|
7090
|
+
const px1 = canvasXFromDrawingPoint(p1);
|
|
7091
|
+
const y0 = canvasYFromDrawingPrice(p0.price);
|
|
7092
|
+
const y1 = canvasYFromDrawingPrice(p1.price);
|
|
7093
|
+
if (Math.hypot(x - px0, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 0 };
|
|
7094
|
+
if (Math.hypot(x - px1, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 1 };
|
|
7095
|
+
if (Math.hypot(x - px0, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 2 };
|
|
7096
|
+
if (Math.hypot(x - px1, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 3 };
|
|
7097
|
+
const cx = (px0 + px1) / 2;
|
|
7098
|
+
const cy = (y0 + y1) / 2;
|
|
7099
|
+
const rx = Math.max(1, Math.abs(px1 - px0) / 2);
|
|
7100
|
+
const ry = Math.max(1, Math.abs(y1 - y0) / 2);
|
|
7101
|
+
const norm = ((x - cx) / rx) ** 2 + ((y - cy) / ry) ** 2;
|
|
7102
|
+
if (norm <= 1) {
|
|
7103
|
+
return { drawing, target: "line" };
|
|
7104
|
+
}
|
|
7105
|
+
} else if (drawing.type === "parallel-channel") {
|
|
7106
|
+
const p0 = drawing.points[0];
|
|
7107
|
+
const p1 = drawing.points[1];
|
|
7108
|
+
const p2 = drawing.points[2];
|
|
7109
|
+
if (!p0 || !p1) continue;
|
|
7110
|
+
const x0 = canvasXFromDrawingPoint(p0);
|
|
7111
|
+
const y0 = canvasYFromDrawingPrice(p0.price);
|
|
7112
|
+
const x1 = canvasXFromDrawingPoint(p1);
|
|
7113
|
+
const y1 = canvasYFromDrawingPrice(p1.price);
|
|
7114
|
+
if (Math.hypot(x - x0, y - y0) <= 8) return { drawing, target: "handle", pointIndex: 0 };
|
|
7115
|
+
if (Math.hypot(x - x1, y - y1) <= 8) return { drawing, target: "handle", pointIndex: 1 };
|
|
7116
|
+
if (p2) {
|
|
7117
|
+
const x2 = canvasXFromDrawingPoint(p2);
|
|
7118
|
+
const y2 = canvasYFromDrawingPrice(p2.price);
|
|
7119
|
+
if (Math.hypot(x - x2, y - y2) <= 8) return { drawing, target: "handle", pointIndex: 2 };
|
|
7120
|
+
const indexSpan = p1.index - p0.index;
|
|
7121
|
+
const slope = indexSpan !== 0 ? (p1.price - p0.price) / indexSpan : 0;
|
|
7122
|
+
const offset = p2.price - (p0.price + slope * (p2.index - p0.index));
|
|
7123
|
+
const q0y = canvasYFromDrawingPrice(p0.price + offset);
|
|
7124
|
+
const q1y = canvasYFromDrawingPrice(p1.price + offset);
|
|
7125
|
+
if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6 || distanceToSegment(x, y, x0, q0y, x1, q1y) <= 6) {
|
|
7126
|
+
return { drawing, target: "line" };
|
|
7127
|
+
}
|
|
7128
|
+
const minX = Math.min(x0, x1);
|
|
7129
|
+
const maxX = Math.max(x0, x1);
|
|
7130
|
+
if (x >= minX && x <= maxX && maxX > minX) {
|
|
7131
|
+
const t = (x - x0) / (x1 - x0);
|
|
7132
|
+
const baseY = y0 + t * (y1 - y0);
|
|
7133
|
+
const parY = q0y + t * (q1y - q0y);
|
|
7134
|
+
if (y >= Math.min(baseY, parY) && y <= Math.max(baseY, parY)) {
|
|
7135
|
+
return { drawing, target: "line" };
|
|
7136
|
+
}
|
|
7137
|
+
}
|
|
7138
|
+
} else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6) {
|
|
7139
|
+
return { drawing, target: "line" };
|
|
7140
|
+
}
|
|
7141
|
+
} else if (drawing.type === "arrow") {
|
|
7142
|
+
const first = drawing.points[0];
|
|
7143
|
+
const second = drawing.points[1];
|
|
7144
|
+
if (!first || !second) continue;
|
|
7145
|
+
const x1 = canvasXFromDrawingPoint(first);
|
|
7146
|
+
const y1 = canvasYFromDrawingPrice(first.price);
|
|
7147
|
+
const x2 = canvasXFromDrawingPoint(second);
|
|
7148
|
+
const y2 = canvasYFromDrawingPrice(second.price);
|
|
7149
|
+
if (Math.hypot(x - x1, y - y1) <= 8) {
|
|
7150
|
+
return { drawing, target: "handle", pointIndex: 0 };
|
|
7151
|
+
}
|
|
7152
|
+
if (Math.hypot(x - x2, y - y2) <= 8) {
|
|
7153
|
+
return { drawing, target: "handle", pointIndex: 1 };
|
|
7154
|
+
}
|
|
7155
|
+
if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
|
|
7156
|
+
return { drawing, target: "line" };
|
|
7157
|
+
}
|
|
7158
|
+
} else if (drawing.type === "brush") {
|
|
7159
|
+
const pts = drawing.points;
|
|
7160
|
+
if (pts.length < 2) continue;
|
|
7161
|
+
let hitBody = false;
|
|
7162
|
+
let prevX = canvasXFromDrawingPoint(pts[0]);
|
|
7163
|
+
let prevY = canvasYFromDrawingPrice(pts[0].price);
|
|
7164
|
+
for (let i = 1; i < pts.length; i += 1) {
|
|
7165
|
+
const nextX = canvasXFromDrawingPoint(pts[i]);
|
|
7166
|
+
const nextY = canvasYFromDrawingPrice(pts[i].price);
|
|
7167
|
+
if (distanceToSegment(x, y, prevX, prevY, nextX, nextY) <= 7) {
|
|
7168
|
+
hitBody = true;
|
|
7169
|
+
break;
|
|
7170
|
+
}
|
|
7171
|
+
prevX = nextX;
|
|
7172
|
+
prevY = nextY;
|
|
7173
|
+
}
|
|
7174
|
+
if (hitBody) {
|
|
7175
|
+
return { drawing, target: "line" };
|
|
7176
|
+
}
|
|
7177
|
+
} else if (drawing.type === "callout") {
|
|
7178
|
+
const anchor = drawing.points[0];
|
|
7179
|
+
const boxPoint = drawing.points[1];
|
|
7180
|
+
if (!anchor || !boxPoint) continue;
|
|
7181
|
+
const ax = canvasXFromDrawingPoint(anchor);
|
|
7182
|
+
const ay = canvasYFromDrawingPrice(anchor.price);
|
|
7183
|
+
const bx = canvasXFromDrawingPoint(boxPoint);
|
|
7184
|
+
const by = canvasYFromDrawingPrice(boxPoint.price);
|
|
7185
|
+
if (Math.hypot(x - ax, y - ay) <= 9) return { drawing, target: "handle", pointIndex: 0 };
|
|
7186
|
+
if (Math.hypot(x - bx, y - by) <= 9) return { drawing, target: "handle", pointIndex: 1 };
|
|
7187
|
+
const fontSize = Math.max(6, drawing.fontSize);
|
|
7188
|
+
const prevFont = ctx.font;
|
|
7189
|
+
ctx.font = `500 ${fontSize}px ${mergedOptions.fontFamily}`;
|
|
7190
|
+
const rawLabel = drawing.label ?? "";
|
|
7191
|
+
const lines = (rawLabel.trim().length === 0 ? "Text" : rawLabel).split("\n");
|
|
7192
|
+
const textW = Math.max(1, ...lines.map((line) => measureTextWidth(line)));
|
|
7193
|
+
ctx.font = prevFont;
|
|
7194
|
+
const padX = 10;
|
|
7195
|
+
const padY = 7;
|
|
7196
|
+
const lineH = Math.round(fontSize * 1.35);
|
|
7197
|
+
const blockW = textW + padX * 2;
|
|
7198
|
+
const blockH = lines.length * lineH + padY * 2;
|
|
7199
|
+
if (x >= bx && x <= bx + blockW && y >= by && y <= by + blockH) {
|
|
7200
|
+
return { drawing, target: "line" };
|
|
7201
|
+
}
|
|
7202
|
+
if (distanceToSegment(x, y, bx + blockW / 2, by + blockH / 2, ax, ay) <= 5) {
|
|
7203
|
+
return { drawing, target: "line" };
|
|
7204
|
+
}
|
|
6875
7205
|
} else if (drawing.type === "price-range") {
|
|
6876
7206
|
const p0 = drawing.points[0];
|
|
6877
7207
|
const p1 = drawing.points[1];
|
|
@@ -7100,6 +7430,128 @@ function createChart(element, options = {}) {
|
|
|
7100
7430
|
scheduleDraw();
|
|
7101
7431
|
return true;
|
|
7102
7432
|
}
|
|
7433
|
+
if (activeDrawingTool === "arrow") {
|
|
7434
|
+
if (draftDrawing?.type === "arrow") {
|
|
7435
|
+
const anchor = draftDrawing.points[0];
|
|
7436
|
+
const endPoint = shiftKeyActive ? constrainAngleFromAnchor(anchor, x, y) ?? point : point;
|
|
7437
|
+
const completed = normalizeDrawingState({
|
|
7438
|
+
...serializeDrawing(draftDrawing),
|
|
7439
|
+
points: [anchor, endPoint]
|
|
7440
|
+
});
|
|
7441
|
+
drawings.push(completed);
|
|
7442
|
+
draftDrawing = null;
|
|
7443
|
+
activeDrawingTool = null;
|
|
7444
|
+
emitDrawingsChange();
|
|
7445
|
+
scheduleDraw();
|
|
7446
|
+
return true;
|
|
7447
|
+
}
|
|
7448
|
+
const defaults = getDrawingToolDefaults("arrow");
|
|
7449
|
+
draftDrawing = normalizeDrawingState({
|
|
7450
|
+
type: "arrow",
|
|
7451
|
+
points: [point, point],
|
|
7452
|
+
color: defaults.color ?? "#2563eb",
|
|
7453
|
+
style: defaults.style ?? "solid",
|
|
7454
|
+
width: defaults.width ?? 2
|
|
7455
|
+
});
|
|
7456
|
+
scheduleDraw();
|
|
7457
|
+
return true;
|
|
7458
|
+
}
|
|
7459
|
+
if (activeDrawingTool === "parallel-channel") {
|
|
7460
|
+
if (draftDrawing?.type === "parallel-channel") {
|
|
7461
|
+
if (draftDrawing.points.length < 3) {
|
|
7462
|
+
draftDrawing = normalizeDrawingState({
|
|
7463
|
+
...serializeDrawing(draftDrawing),
|
|
7464
|
+
points: [...draftDrawing.points.slice(0, -1), point, point]
|
|
7465
|
+
});
|
|
7466
|
+
scheduleDraw();
|
|
7467
|
+
return true;
|
|
7468
|
+
}
|
|
7469
|
+
const completed = normalizeDrawingState({
|
|
7470
|
+
...serializeDrawing(draftDrawing),
|
|
7471
|
+
points: [draftDrawing.points[0], draftDrawing.points[1], point]
|
|
7472
|
+
});
|
|
7473
|
+
drawings.push(completed);
|
|
7474
|
+
draftDrawing = null;
|
|
7475
|
+
activeDrawingTool = null;
|
|
7476
|
+
emitDrawingsChange();
|
|
7477
|
+
scheduleDraw();
|
|
7478
|
+
return true;
|
|
7479
|
+
}
|
|
7480
|
+
const defaults = getDrawingToolDefaults("parallel-channel");
|
|
7481
|
+
draftDrawing = normalizeDrawingState({
|
|
7482
|
+
type: "parallel-channel",
|
|
7483
|
+
points: [point, point],
|
|
7484
|
+
color: defaults.color ?? "#2563eb",
|
|
7485
|
+
style: defaults.style ?? "solid",
|
|
7486
|
+
width: defaults.width ?? 2
|
|
7487
|
+
});
|
|
7488
|
+
scheduleDraw();
|
|
7489
|
+
return true;
|
|
7490
|
+
}
|
|
7491
|
+
if (activeDrawingTool === "ellipse") {
|
|
7492
|
+
if (draftDrawing?.type === "ellipse") {
|
|
7493
|
+
const completed = normalizeDrawingState({
|
|
7494
|
+
...serializeDrawing(draftDrawing),
|
|
7495
|
+
points: [draftDrawing.points[0], point]
|
|
7496
|
+
});
|
|
7497
|
+
drawings.push(completed);
|
|
7498
|
+
draftDrawing = null;
|
|
7499
|
+
activeDrawingTool = null;
|
|
7500
|
+
emitDrawingsChange();
|
|
7501
|
+
scheduleDraw();
|
|
7502
|
+
return true;
|
|
7503
|
+
}
|
|
7504
|
+
const defaults = getDrawingToolDefaults("ellipse");
|
|
7505
|
+
draftDrawing = normalizeDrawingState({
|
|
7506
|
+
type: "ellipse",
|
|
7507
|
+
points: [point, point],
|
|
7508
|
+
color: defaults.color ?? "#2962ff",
|
|
7509
|
+
style: defaults.style ?? "solid",
|
|
7510
|
+
width: defaults.width ?? 1
|
|
7511
|
+
});
|
|
7512
|
+
scheduleDraw();
|
|
7513
|
+
return true;
|
|
7514
|
+
}
|
|
7515
|
+
if (activeDrawingTool === "callout") {
|
|
7516
|
+
if (draftDrawing?.type === "callout") {
|
|
7517
|
+
const completed = normalizeDrawingState({
|
|
7518
|
+
...serializeDrawing(draftDrawing),
|
|
7519
|
+
points: [draftDrawing.points[0], point]
|
|
7520
|
+
});
|
|
7521
|
+
drawings.push(completed);
|
|
7522
|
+
draftDrawing = null;
|
|
7523
|
+
activeDrawingTool = null;
|
|
7524
|
+
selectedDrawingId = completed.id;
|
|
7525
|
+
emitDrawingsChange();
|
|
7526
|
+
scheduleDraw();
|
|
7527
|
+
drawingEditTextHandler?.({ drawing: serializeDrawing(completed), target: "line", x, y });
|
|
7528
|
+
return true;
|
|
7529
|
+
}
|
|
7530
|
+
const defaults = getDrawingToolDefaults("callout");
|
|
7531
|
+
draftDrawing = normalizeDrawingState({
|
|
7532
|
+
type: "callout",
|
|
7533
|
+
points: [point, point],
|
|
7534
|
+
color: defaults.color ?? "#2962ff",
|
|
7535
|
+
style: defaults.style ?? "solid",
|
|
7536
|
+
width: defaults.width ?? 1,
|
|
7537
|
+
...defaults.fontSize === void 0 ? {} : { fontSize: defaults.fontSize },
|
|
7538
|
+
label: ""
|
|
7539
|
+
});
|
|
7540
|
+
scheduleDraw();
|
|
7541
|
+
return true;
|
|
7542
|
+
}
|
|
7543
|
+
if (activeDrawingTool === "brush") {
|
|
7544
|
+
const defaults = getDrawingToolDefaults("brush");
|
|
7545
|
+
draftDrawing = normalizeDrawingState({
|
|
7546
|
+
type: "brush",
|
|
7547
|
+
points: [point],
|
|
7548
|
+
color: defaults.color ?? "#f59e0b",
|
|
7549
|
+
style: defaults.style ?? "solid",
|
|
7550
|
+
width: defaults.width ?? 2
|
|
7551
|
+
});
|
|
7552
|
+
scheduleDraw();
|
|
7553
|
+
return true;
|
|
7554
|
+
}
|
|
7103
7555
|
if (activeDrawingTool === "fib-retracement") {
|
|
7104
7556
|
if (draftDrawing?.type === "fib-retracement") {
|
|
7105
7557
|
const completed = normalizeDrawingState({
|
|
@@ -7303,7 +7755,7 @@ function createChart(element, options = {}) {
|
|
|
7303
7755
|
}
|
|
7304
7756
|
return { ...drawing, points: pts };
|
|
7305
7757
|
}
|
|
7306
|
-
if (drawingDragState.target === "handle" && drawing.type === "rectangle") {
|
|
7758
|
+
if (drawingDragState.target === "handle" && (drawing.type === "rectangle" || drawing.type === "ellipse")) {
|
|
7307
7759
|
const pts = drawing.points.map((point) => ({ ...point }));
|
|
7308
7760
|
const p0 = pts[0];
|
|
7309
7761
|
const p1 = pts[1];
|
|
@@ -7322,7 +7774,7 @@ function createChart(element, options = {}) {
|
|
|
7322
7774
|
}
|
|
7323
7775
|
return { ...drawing, points: pts };
|
|
7324
7776
|
}
|
|
7325
|
-
if (drawingDragState.target === "handle" && shiftKeyActive && (drawing.type === "trendline" || drawing.type === "ray")) {
|
|
7777
|
+
if (drawingDragState.target === "handle" && shiftKeyActive && (drawing.type === "trendline" || drawing.type === "ray" || drawing.type === "arrow")) {
|
|
7326
7778
|
const pointIndex = drawingDragState.pointIndex ?? 0;
|
|
7327
7779
|
const anchor = drawing.points[pointIndex === 0 ? 1 : 0];
|
|
7328
7780
|
const constrained = anchor ? constrainAngleFromAnchor(anchor, x, y) : null;
|
|
@@ -7572,6 +8024,10 @@ function createChart(element, options = {}) {
|
|
|
7572
8024
|
if (region === "plot" && handleDrawingToolPointerDown(point.x, point.y)) {
|
|
7573
8025
|
setCrosshairPoint(null);
|
|
7574
8026
|
canvas.style.cursor = "crosshair";
|
|
8027
|
+
if (activeDrawingTool === "brush" && draftDrawing?.type === "brush") {
|
|
8028
|
+
activePointerId = event.pointerId;
|
|
8029
|
+
capturePointer(event.pointerId);
|
|
8030
|
+
}
|
|
7575
8031
|
return;
|
|
7576
8032
|
}
|
|
7577
8033
|
isDragging = true;
|
|
@@ -7656,9 +8112,30 @@ function createChart(element, options = {}) {
|
|
|
7656
8112
|
setCrosshairPoint(null);
|
|
7657
8113
|
return;
|
|
7658
8114
|
}
|
|
7659
|
-
if (draftDrawing
|
|
8115
|
+
if (draftDrawing?.type === "brush" && activeDrawingTool === "brush") {
|
|
8116
|
+
if (activePointerId !== null && event.pointerId !== activePointerId) {
|
|
8117
|
+
return;
|
|
8118
|
+
}
|
|
8119
|
+
const nextPoint = drawingPointFromCanvas(point.x, point.y);
|
|
8120
|
+
if (nextPoint) {
|
|
8121
|
+
const last = draftDrawing.points[draftDrawing.points.length - 1];
|
|
8122
|
+
const lastX = last ? canvasXFromDrawingPoint(last) : Number.NEGATIVE_INFINITY;
|
|
8123
|
+
const lastY = last ? canvasYFromDrawingPrice(last.price) : Number.NEGATIVE_INFINITY;
|
|
8124
|
+
if (Math.hypot(point.x - lastX, point.y - lastY) >= 3) {
|
|
8125
|
+
draftDrawing = {
|
|
8126
|
+
...draftDrawing,
|
|
8127
|
+
points: [...draftDrawing.points, nextPoint]
|
|
8128
|
+
};
|
|
8129
|
+
}
|
|
8130
|
+
canvas.style.cursor = "crosshair";
|
|
8131
|
+
setCrosshairPoint(null);
|
|
8132
|
+
scheduleDraw();
|
|
8133
|
+
}
|
|
8134
|
+
return;
|
|
8135
|
+
}
|
|
8136
|
+
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "arrow" || activeDrawingTool === "rectangle" || activeDrawingTool === "ellipse" || activeDrawingTool === "callout" || activeDrawingTool === "parallel-channel" || activeDrawingTool === "fib-retracement" || activeDrawingTool === "fib-extension")) {
|
|
7660
8137
|
let nextPoint = drawingPointFromCanvas(point.x, point.y);
|
|
7661
|
-
if (nextPoint && shiftKeyActive && (draftDrawing.type === "trendline" || draftDrawing.type === "ray") && draftDrawing.points[0]) {
|
|
8138
|
+
if (nextPoint && shiftKeyActive && (draftDrawing.type === "trendline" || draftDrawing.type === "ray" || draftDrawing.type === "arrow") && draftDrawing.points[0]) {
|
|
7662
8139
|
nextPoint = constrainAngleFromAnchor(draftDrawing.points[0], point.x, point.y) ?? nextPoint;
|
|
7663
8140
|
}
|
|
7664
8141
|
if (nextPoint) {
|
|
@@ -7895,6 +8372,25 @@ function createChart(element, options = {}) {
|
|
|
7895
8372
|
scheduleDraw();
|
|
7896
8373
|
return;
|
|
7897
8374
|
}
|
|
8375
|
+
if (draftDrawing?.type === "brush" && activeDrawingTool === "brush") {
|
|
8376
|
+
if (draftDrawing.points.length >= 2) {
|
|
8377
|
+
const completed = normalizeDrawingState(serializeDrawing(draftDrawing));
|
|
8378
|
+
drawings.push(completed);
|
|
8379
|
+
draftDrawing = null;
|
|
8380
|
+
activeDrawingTool = null;
|
|
8381
|
+
canvas.style.cursor = "default";
|
|
8382
|
+
emitDrawingsChange();
|
|
8383
|
+
} else {
|
|
8384
|
+
draftDrawing = null;
|
|
8385
|
+
canvas.style.cursor = "crosshair";
|
|
8386
|
+
}
|
|
8387
|
+
isDragging = false;
|
|
8388
|
+
dragMode = null;
|
|
8389
|
+
activePointerId = null;
|
|
8390
|
+
pointerDownInfo = null;
|
|
8391
|
+
scheduleDraw();
|
|
8392
|
+
return;
|
|
8393
|
+
}
|
|
7898
8394
|
if (orderDragState) {
|
|
7899
8395
|
const moved = orderDragState.lastPrice !== orderDragState.startPrice;
|
|
7900
8396
|
const finalLine = orderLines.find((line) => line.id === orderDragState?.orderId);
|
|
@@ -8038,7 +8534,7 @@ function createChart(element, options = {}) {
|
|
|
8038
8534
|
x: point.x,
|
|
8039
8535
|
y: point.y
|
|
8040
8536
|
};
|
|
8041
|
-
if (drawingHit.drawing.type === "text" || drawingHit.drawing.type === "note") {
|
|
8537
|
+
if (drawingHit.drawing.type === "text" || drawingHit.drawing.type === "note" || drawingHit.drawing.type === "callout") {
|
|
8042
8538
|
drawingEditTextHandler?.(payload);
|
|
8043
8539
|
} else {
|
|
8044
8540
|
drawingDoubleClickHandler?.(payload);
|
|
@@ -8485,6 +8981,51 @@ function createChart(element, options = {}) {
|
|
|
8485
8981
|
const onDrawingHover = (handler) => {
|
|
8486
8982
|
drawingHoverHandler = handler;
|
|
8487
8983
|
};
|
|
8984
|
+
const saveState = () => {
|
|
8985
|
+
const drawingDefaults = {};
|
|
8986
|
+
for (const [tool, defaults] of drawingToolDefaults) {
|
|
8987
|
+
drawingDefaults[tool] = { ...defaults };
|
|
8988
|
+
}
|
|
8989
|
+
return {
|
|
8990
|
+
version: 1,
|
|
8991
|
+
chartType: getChartType(),
|
|
8992
|
+
viewport: getViewport(),
|
|
8993
|
+
drawings: getDrawings(),
|
|
8994
|
+
indicators: getIndicators(),
|
|
8995
|
+
magnetMode: getMagnetMode(),
|
|
8996
|
+
drawingDefaults
|
|
8997
|
+
};
|
|
8998
|
+
};
|
|
8999
|
+
const loadState = (state) => {
|
|
9000
|
+
if (!state || typeof state !== "object") {
|
|
9001
|
+
return;
|
|
9002
|
+
}
|
|
9003
|
+
if (typeof state.chartType === "string") {
|
|
9004
|
+
setChartType(state.chartType);
|
|
9005
|
+
}
|
|
9006
|
+
if (Array.isArray(state.indicators)) {
|
|
9007
|
+
setIndicators(state.indicators);
|
|
9008
|
+
}
|
|
9009
|
+
if (Array.isArray(state.drawings)) {
|
|
9010
|
+
setDrawings(state.drawings);
|
|
9011
|
+
}
|
|
9012
|
+
if (state.magnetMode === "none" || state.magnetMode === "weak" || state.magnetMode === "strong") {
|
|
9013
|
+
setMagnetMode(state.magnetMode);
|
|
9014
|
+
}
|
|
9015
|
+
if (state.drawingDefaults && typeof state.drawingDefaults === "object") {
|
|
9016
|
+
for (const [tool, defaults] of Object.entries(state.drawingDefaults)) {
|
|
9017
|
+
if (defaults && typeof defaults === "object") {
|
|
9018
|
+
setDrawingDefaults(tool, defaults);
|
|
9019
|
+
}
|
|
9020
|
+
}
|
|
9021
|
+
}
|
|
9022
|
+
if (state.viewport && typeof state.viewport === "object") {
|
|
9023
|
+
setViewport(state.viewport);
|
|
9024
|
+
}
|
|
9025
|
+
};
|
|
9026
|
+
const takeScreenshot = (options2 = {}) => {
|
|
9027
|
+
return canvas.toDataURL(options2.type ?? "image/png", options2.quality);
|
|
9028
|
+
};
|
|
8488
9029
|
const destroy = () => {
|
|
8489
9030
|
cancelKineticPan();
|
|
8490
9031
|
if (smoothingRafId !== null) {
|
|
@@ -8580,6 +9121,9 @@ function createChart(element, options = {}) {
|
|
|
8580
9121
|
updateIndicator,
|
|
8581
9122
|
removeIndicator,
|
|
8582
9123
|
setIndicators,
|
|
9124
|
+
saveState,
|
|
9125
|
+
loadState,
|
|
9126
|
+
takeScreenshot,
|
|
8583
9127
|
resize,
|
|
8584
9128
|
destroy
|
|
8585
9129
|
};
|