hyperprop-charting-library 0.1.73 → 0.1.75
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 +85 -66
- package/dist/hyperprop-charting-library.d.ts +1 -0
- package/dist/hyperprop-charting-library.js +85 -66
- package/dist/index.cjs +85 -66
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +85 -66
- package/docs/API.md +1 -0
- package/package.json +1 -1
|
@@ -984,6 +984,7 @@ function createChart(element, options = {}) {
|
|
|
984
984
|
let drawingSelectHandler = null;
|
|
985
985
|
let drawingHoverHandler = null;
|
|
986
986
|
let lastHoveredDrawingId = null;
|
|
987
|
+
let selectedDrawingId = null;
|
|
987
988
|
const drawingToolDefaults = /* @__PURE__ */ new Map();
|
|
988
989
|
const getDrawingToolDefaults = (tool) => drawingToolDefaults.get(tool) ?? {};
|
|
989
990
|
const emitDrawingsChange = () => {
|
|
@@ -2162,6 +2163,12 @@ function createChart(element, options = {}) {
|
|
|
2162
2163
|
if (!drawing.visible) {
|
|
2163
2164
|
return;
|
|
2164
2165
|
}
|
|
2166
|
+
const isSelected = draft || drawing.id === selectedDrawingId;
|
|
2167
|
+
const handleAt = (hx, hy, _color) => {
|
|
2168
|
+
if (isSelected) {
|
|
2169
|
+
drawDrawingHandle(hx, hy, drawing.color);
|
|
2170
|
+
}
|
|
2171
|
+
};
|
|
2165
2172
|
ctx.save();
|
|
2166
2173
|
ctx.strokeStyle = drawing.color;
|
|
2167
2174
|
ctx.lineWidth = drawing.width;
|
|
@@ -2180,7 +2187,7 @@ function createChart(element, options = {}) {
|
|
|
2180
2187
|
ctx.moveTo(crisp(chartLeft), crisp(y));
|
|
2181
2188
|
ctx.lineTo(crisp(chartRight), crisp(y));
|
|
2182
2189
|
ctx.stroke();
|
|
2183
|
-
|
|
2190
|
+
handleAt(handleX, y, drawing.color);
|
|
2184
2191
|
if (drawing.label) {
|
|
2185
2192
|
const midX = (chartLeft + chartRight) / 2;
|
|
2186
2193
|
drawDrawingLabel(drawing.label, midX, y, drawing.color);
|
|
@@ -2195,7 +2202,7 @@ function createChart(element, options = {}) {
|
|
|
2195
2202
|
ctx.lineTo(crisp(x), crisp(chartBottom));
|
|
2196
2203
|
ctx.stroke();
|
|
2197
2204
|
const handleY = (chartTop + chartBottom) / 2;
|
|
2198
|
-
|
|
2205
|
+
handleAt(x, handleY, drawing.color);
|
|
2199
2206
|
if (drawing.label) {
|
|
2200
2207
|
drawDrawingLabel(drawing.label, x, chartTop + 16, drawing.color);
|
|
2201
2208
|
}
|
|
@@ -2212,8 +2219,8 @@ function createChart(element, options = {}) {
|
|
|
2212
2219
|
ctx.moveTo(firstX, firstY);
|
|
2213
2220
|
ctx.lineTo(secondX, secondY);
|
|
2214
2221
|
ctx.stroke();
|
|
2215
|
-
|
|
2216
|
-
|
|
2222
|
+
handleAt(firstX, firstY, drawing.color);
|
|
2223
|
+
handleAt(secondX, secondY, drawing.color);
|
|
2217
2224
|
if (drawing.label) {
|
|
2218
2225
|
const midX = (firstX + secondX) / 2;
|
|
2219
2226
|
const midY = (firstY + secondY) / 2;
|
|
@@ -2242,8 +2249,8 @@ function createChart(element, options = {}) {
|
|
|
2242
2249
|
ctx.moveTo(firstX, firstY);
|
|
2243
2250
|
ctx.lineTo(endX, endY);
|
|
2244
2251
|
ctx.stroke();
|
|
2245
|
-
|
|
2246
|
-
|
|
2252
|
+
handleAt(firstX, firstY, drawing.color);
|
|
2253
|
+
handleAt(secondX, secondY, drawing.color);
|
|
2247
2254
|
if (drawing.label) {
|
|
2248
2255
|
const midX = (firstX + secondX) / 2;
|
|
2249
2256
|
const midY = (firstY + secondY) / 2;
|
|
@@ -2297,8 +2304,8 @@ function createChart(element, options = {}) {
|
|
|
2297
2304
|
ctx.lineTo(secondX, secondY);
|
|
2298
2305
|
ctx.stroke();
|
|
2299
2306
|
ctx.restore();
|
|
2300
|
-
|
|
2301
|
-
|
|
2307
|
+
handleAt(firstX, firstY, drawing.color);
|
|
2308
|
+
handleAt(secondX, secondY, drawing.color);
|
|
2302
2309
|
const prevFont = ctx.font;
|
|
2303
2310
|
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2304
2311
|
levelLines.forEach((level, index) => {
|
|
@@ -2337,12 +2344,12 @@ function createChart(element, options = {}) {
|
|
|
2337
2344
|
}
|
|
2338
2345
|
ctx.stroke();
|
|
2339
2346
|
ctx.restore();
|
|
2340
|
-
|
|
2341
|
-
|
|
2347
|
+
handleAt(x0, y0, drawing.color);
|
|
2348
|
+
handleAt(x1, y1, drawing.color);
|
|
2342
2349
|
if (p2) {
|
|
2343
2350
|
const x2 = xFromDrawingPoint(p2);
|
|
2344
2351
|
const y2 = yFromPrice(p2.price);
|
|
2345
|
-
|
|
2352
|
+
handleAt(x2, y2, drawing.color);
|
|
2346
2353
|
const palette = drawing.colors.length > 0 ? drawing.colors : null;
|
|
2347
2354
|
const levelColorAt = (index) => palette ? palette[index % palette.length] : drawing.color;
|
|
2348
2355
|
const move = p1.price - p0.price;
|
|
@@ -2422,61 +2429,63 @@ function createChart(element, options = {}) {
|
|
|
2422
2429
|
ctx.fillStyle = lossFill;
|
|
2423
2430
|
ctx.fillRect(boxX0, Math.min(entryY, stopY), boxW, Math.abs(stopY - entryY));
|
|
2424
2431
|
ctx.restore();
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
const
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2432
|
+
if (isSelected) {
|
|
2433
|
+
ctx.save();
|
|
2434
|
+
ctx.setLineDash([]);
|
|
2435
|
+
ctx.lineWidth = Math.max(1, drawing.width);
|
|
2436
|
+
ctx.strokeStyle = profitLine;
|
|
2437
|
+
ctx.beginPath();
|
|
2438
|
+
ctx.moveTo(crisp(boxX0), crisp(targetY));
|
|
2439
|
+
ctx.lineTo(crisp(boxX1), crisp(targetY));
|
|
2440
|
+
ctx.stroke();
|
|
2441
|
+
ctx.strokeStyle = lossLine;
|
|
2442
|
+
ctx.beginPath();
|
|
2443
|
+
ctx.moveTo(crisp(boxX0), crisp(stopY));
|
|
2444
|
+
ctx.lineTo(crisp(boxX1), crisp(stopY));
|
|
2445
|
+
ctx.stroke();
|
|
2446
|
+
ctx.strokeStyle = drawing.color;
|
|
2447
|
+
ctx.beginPath();
|
|
2448
|
+
ctx.moveTo(crisp(boxX0), crisp(entryY));
|
|
2449
|
+
ctx.lineTo(crisp(boxX1), crisp(entryY));
|
|
2450
|
+
ctx.stroke();
|
|
2451
|
+
ctx.restore();
|
|
2452
|
+
handleAt(leftX, targetY, drawing.color);
|
|
2453
|
+
handleAt(leftX, entryY, drawing.color);
|
|
2454
|
+
handleAt(leftX, stopY, drawing.color);
|
|
2455
|
+
handleAt(rightX, entryY, drawing.color);
|
|
2456
|
+
const tick = getConfiguredTickSize();
|
|
2457
|
+
const pctOf = (price) => {
|
|
2458
|
+
if (!Number.isFinite(entry.price) || entry.price === 0) return "0.00%";
|
|
2459
|
+
return `${((price - entry.price) / Math.abs(entry.price) * 100).toFixed(2)}%`;
|
|
2460
|
+
};
|
|
2461
|
+
const ticksOf = (price) => tick > 0 ? ` ${Math.round(Math.abs(price - entry.price) / tick)}t` : "";
|
|
2462
|
+
const targetDist = Math.abs(target.price - entry.price);
|
|
2463
|
+
const stopDist = Math.abs(entry.price - stop.price);
|
|
2464
|
+
const rr = stopDist > 0 ? targetDist / stopDist : 0;
|
|
2465
|
+
const cx = (boxX0 + boxX1) / 2;
|
|
2466
|
+
const drawPositionPill = (text, centerX, centerY, bg) => {
|
|
2467
|
+
const prevFont = ctx.font;
|
|
2468
|
+
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2469
|
+
const padding = 6;
|
|
2470
|
+
const textW = ctx.measureText(text).width;
|
|
2471
|
+
const pillW = textW + padding * 2;
|
|
2472
|
+
const pillH = 18;
|
|
2473
|
+
const pillX = centerX - pillW / 2;
|
|
2474
|
+
const pillY = centerY - pillH / 2;
|
|
2475
|
+
ctx.fillStyle = bg;
|
|
2476
|
+
fillRoundedRect(pillX, pillY, pillW, pillH, 4);
|
|
2477
|
+
ctx.fillStyle = labelTextColor;
|
|
2478
|
+
ctx.textAlign = "center";
|
|
2479
|
+
ctx.textBaseline = "middle";
|
|
2480
|
+
ctx.fillText(text, centerX, pillY + pillH / 2);
|
|
2481
|
+
ctx.font = prevFont;
|
|
2482
|
+
};
|
|
2483
|
+
drawPositionPill(`Target ${formatPrice(target.price)} (${pctOf(target.price)})${ticksOf(target.price)}`, cx, targetY, profitLine);
|
|
2484
|
+
drawPositionPill(`Stop ${formatPrice(stop.price)} (${pctOf(stop.price)})${ticksOf(stop.price)}`, cx, stopY, lossLine);
|
|
2485
|
+
drawPositionPill(`Entry ${formatPrice(entry.price)} RR ${rr.toFixed(2)}`, cx, entryY, hexToRgba(drawing.color, 0.92));
|
|
2486
|
+
if (drawing.label) {
|
|
2487
|
+
drawDrawingLabel(drawing.label, cx, Math.min(targetY, stopY) - 4, drawing.color);
|
|
2488
|
+
}
|
|
2480
2489
|
}
|
|
2481
2490
|
}
|
|
2482
2491
|
}
|
|
@@ -3934,6 +3943,13 @@ function createChart(element, options = {}) {
|
|
|
3934
3943
|
}
|
|
3935
3944
|
drawingToolDefaults.set(tool, { ...defaults });
|
|
3936
3945
|
};
|
|
3946
|
+
const setSelectedDrawing = (id) => {
|
|
3947
|
+
if (selectedDrawingId === id) {
|
|
3948
|
+
return;
|
|
3949
|
+
}
|
|
3950
|
+
selectedDrawingId = id;
|
|
3951
|
+
draw();
|
|
3952
|
+
};
|
|
3937
3953
|
const updateDrawingDrag = (x, y) => {
|
|
3938
3954
|
if (!drawingDragState) {
|
|
3939
3955
|
return false;
|
|
@@ -4115,6 +4131,7 @@ function createChart(element, options = {}) {
|
|
|
4115
4131
|
if (region === "plot" && !activeDrawingTool) {
|
|
4116
4132
|
const drawingHit = getDrawingHit(point.x, point.y);
|
|
4117
4133
|
if (drawingHit) {
|
|
4134
|
+
selectedDrawingId = drawingHit.drawing.id;
|
|
4118
4135
|
drawingSelectHandler?.({
|
|
4119
4136
|
drawing: serializeDrawing(drawingHit.drawing),
|
|
4120
4137
|
target: drawingHit.target,
|
|
@@ -4424,6 +4441,7 @@ function createChart(element, options = {}) {
|
|
|
4424
4441
|
emitCrosshairMove(point.x, point.y, "plot");
|
|
4425
4442
|
}
|
|
4426
4443
|
} else if (!pointerDownInfo.moved) {
|
|
4444
|
+
selectedDrawingId = null;
|
|
4427
4445
|
const clickPrice = pointerDownInfo.region === "plot" ? roundToPricePrecision(priceFromCanvasY(pointerDownInfo.y)) : void 0;
|
|
4428
4446
|
chartClickHandler?.({
|
|
4429
4447
|
x: pointerDownInfo.x,
|
|
@@ -4846,6 +4864,7 @@ function createChart(element, options = {}) {
|
|
|
4846
4864
|
clearDrawings,
|
|
4847
4865
|
onDrawingsChange,
|
|
4848
4866
|
onDrawingSelect,
|
|
4867
|
+
setSelectedDrawing,
|
|
4849
4868
|
onDrawingHover,
|
|
4850
4869
|
setDrawingDefaults,
|
|
4851
4870
|
setDoubleClickEnabled,
|
|
@@ -422,6 +422,7 @@ interface ChartInstance {
|
|
|
422
422
|
onDrawingSelect: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
|
|
423
423
|
onDrawingHover: (handler: ((event: DrawingHoverEvent) => void) | null) => void;
|
|
424
424
|
setDrawingDefaults: (tool: DrawingToolType, defaults: DrawingDefaults | null) => void;
|
|
425
|
+
setSelectedDrawing: (id: string | null) => void;
|
|
425
426
|
setDoubleClickEnabled: (enabled: boolean) => void;
|
|
426
427
|
setDoubleClickAction: (action: "reset" | "placeLimitOrder") => void;
|
|
427
428
|
registerIndicator: (plugin: IndicatorPlugin<any>) => void;
|
|
@@ -958,6 +958,7 @@ function createChart(element, options = {}) {
|
|
|
958
958
|
let drawingSelectHandler = null;
|
|
959
959
|
let drawingHoverHandler = null;
|
|
960
960
|
let lastHoveredDrawingId = null;
|
|
961
|
+
let selectedDrawingId = null;
|
|
961
962
|
const drawingToolDefaults = /* @__PURE__ */ new Map();
|
|
962
963
|
const getDrawingToolDefaults = (tool) => drawingToolDefaults.get(tool) ?? {};
|
|
963
964
|
const emitDrawingsChange = () => {
|
|
@@ -2136,6 +2137,12 @@ function createChart(element, options = {}) {
|
|
|
2136
2137
|
if (!drawing.visible) {
|
|
2137
2138
|
return;
|
|
2138
2139
|
}
|
|
2140
|
+
const isSelected = draft || drawing.id === selectedDrawingId;
|
|
2141
|
+
const handleAt = (hx, hy, _color) => {
|
|
2142
|
+
if (isSelected) {
|
|
2143
|
+
drawDrawingHandle(hx, hy, drawing.color);
|
|
2144
|
+
}
|
|
2145
|
+
};
|
|
2139
2146
|
ctx.save();
|
|
2140
2147
|
ctx.strokeStyle = drawing.color;
|
|
2141
2148
|
ctx.lineWidth = drawing.width;
|
|
@@ -2154,7 +2161,7 @@ function createChart(element, options = {}) {
|
|
|
2154
2161
|
ctx.moveTo(crisp(chartLeft), crisp(y));
|
|
2155
2162
|
ctx.lineTo(crisp(chartRight), crisp(y));
|
|
2156
2163
|
ctx.stroke();
|
|
2157
|
-
|
|
2164
|
+
handleAt(handleX, y, drawing.color);
|
|
2158
2165
|
if (drawing.label) {
|
|
2159
2166
|
const midX = (chartLeft + chartRight) / 2;
|
|
2160
2167
|
drawDrawingLabel(drawing.label, midX, y, drawing.color);
|
|
@@ -2169,7 +2176,7 @@ function createChart(element, options = {}) {
|
|
|
2169
2176
|
ctx.lineTo(crisp(x), crisp(chartBottom));
|
|
2170
2177
|
ctx.stroke();
|
|
2171
2178
|
const handleY = (chartTop + chartBottom) / 2;
|
|
2172
|
-
|
|
2179
|
+
handleAt(x, handleY, drawing.color);
|
|
2173
2180
|
if (drawing.label) {
|
|
2174
2181
|
drawDrawingLabel(drawing.label, x, chartTop + 16, drawing.color);
|
|
2175
2182
|
}
|
|
@@ -2186,8 +2193,8 @@ function createChart(element, options = {}) {
|
|
|
2186
2193
|
ctx.moveTo(firstX, firstY);
|
|
2187
2194
|
ctx.lineTo(secondX, secondY);
|
|
2188
2195
|
ctx.stroke();
|
|
2189
|
-
|
|
2190
|
-
|
|
2196
|
+
handleAt(firstX, firstY, drawing.color);
|
|
2197
|
+
handleAt(secondX, secondY, drawing.color);
|
|
2191
2198
|
if (drawing.label) {
|
|
2192
2199
|
const midX = (firstX + secondX) / 2;
|
|
2193
2200
|
const midY = (firstY + secondY) / 2;
|
|
@@ -2216,8 +2223,8 @@ function createChart(element, options = {}) {
|
|
|
2216
2223
|
ctx.moveTo(firstX, firstY);
|
|
2217
2224
|
ctx.lineTo(endX, endY);
|
|
2218
2225
|
ctx.stroke();
|
|
2219
|
-
|
|
2220
|
-
|
|
2226
|
+
handleAt(firstX, firstY, drawing.color);
|
|
2227
|
+
handleAt(secondX, secondY, drawing.color);
|
|
2221
2228
|
if (drawing.label) {
|
|
2222
2229
|
const midX = (firstX + secondX) / 2;
|
|
2223
2230
|
const midY = (firstY + secondY) / 2;
|
|
@@ -2271,8 +2278,8 @@ function createChart(element, options = {}) {
|
|
|
2271
2278
|
ctx.lineTo(secondX, secondY);
|
|
2272
2279
|
ctx.stroke();
|
|
2273
2280
|
ctx.restore();
|
|
2274
|
-
|
|
2275
|
-
|
|
2281
|
+
handleAt(firstX, firstY, drawing.color);
|
|
2282
|
+
handleAt(secondX, secondY, drawing.color);
|
|
2276
2283
|
const prevFont = ctx.font;
|
|
2277
2284
|
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2278
2285
|
levelLines.forEach((level, index) => {
|
|
@@ -2311,12 +2318,12 @@ function createChart(element, options = {}) {
|
|
|
2311
2318
|
}
|
|
2312
2319
|
ctx.stroke();
|
|
2313
2320
|
ctx.restore();
|
|
2314
|
-
|
|
2315
|
-
|
|
2321
|
+
handleAt(x0, y0, drawing.color);
|
|
2322
|
+
handleAt(x1, y1, drawing.color);
|
|
2316
2323
|
if (p2) {
|
|
2317
2324
|
const x2 = xFromDrawingPoint(p2);
|
|
2318
2325
|
const y2 = yFromPrice(p2.price);
|
|
2319
|
-
|
|
2326
|
+
handleAt(x2, y2, drawing.color);
|
|
2320
2327
|
const palette = drawing.colors.length > 0 ? drawing.colors : null;
|
|
2321
2328
|
const levelColorAt = (index) => palette ? palette[index % palette.length] : drawing.color;
|
|
2322
2329
|
const move = p1.price - p0.price;
|
|
@@ -2396,61 +2403,63 @@ function createChart(element, options = {}) {
|
|
|
2396
2403
|
ctx.fillStyle = lossFill;
|
|
2397
2404
|
ctx.fillRect(boxX0, Math.min(entryY, stopY), boxW, Math.abs(stopY - entryY));
|
|
2398
2405
|
ctx.restore();
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
const
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2406
|
+
if (isSelected) {
|
|
2407
|
+
ctx.save();
|
|
2408
|
+
ctx.setLineDash([]);
|
|
2409
|
+
ctx.lineWidth = Math.max(1, drawing.width);
|
|
2410
|
+
ctx.strokeStyle = profitLine;
|
|
2411
|
+
ctx.beginPath();
|
|
2412
|
+
ctx.moveTo(crisp(boxX0), crisp(targetY));
|
|
2413
|
+
ctx.lineTo(crisp(boxX1), crisp(targetY));
|
|
2414
|
+
ctx.stroke();
|
|
2415
|
+
ctx.strokeStyle = lossLine;
|
|
2416
|
+
ctx.beginPath();
|
|
2417
|
+
ctx.moveTo(crisp(boxX0), crisp(stopY));
|
|
2418
|
+
ctx.lineTo(crisp(boxX1), crisp(stopY));
|
|
2419
|
+
ctx.stroke();
|
|
2420
|
+
ctx.strokeStyle = drawing.color;
|
|
2421
|
+
ctx.beginPath();
|
|
2422
|
+
ctx.moveTo(crisp(boxX0), crisp(entryY));
|
|
2423
|
+
ctx.lineTo(crisp(boxX1), crisp(entryY));
|
|
2424
|
+
ctx.stroke();
|
|
2425
|
+
ctx.restore();
|
|
2426
|
+
handleAt(leftX, targetY, drawing.color);
|
|
2427
|
+
handleAt(leftX, entryY, drawing.color);
|
|
2428
|
+
handleAt(leftX, stopY, drawing.color);
|
|
2429
|
+
handleAt(rightX, entryY, drawing.color);
|
|
2430
|
+
const tick = getConfiguredTickSize();
|
|
2431
|
+
const pctOf = (price) => {
|
|
2432
|
+
if (!Number.isFinite(entry.price) || entry.price === 0) return "0.00%";
|
|
2433
|
+
return `${((price - entry.price) / Math.abs(entry.price) * 100).toFixed(2)}%`;
|
|
2434
|
+
};
|
|
2435
|
+
const ticksOf = (price) => tick > 0 ? ` ${Math.round(Math.abs(price - entry.price) / tick)}t` : "";
|
|
2436
|
+
const targetDist = Math.abs(target.price - entry.price);
|
|
2437
|
+
const stopDist = Math.abs(entry.price - stop.price);
|
|
2438
|
+
const rr = stopDist > 0 ? targetDist / stopDist : 0;
|
|
2439
|
+
const cx = (boxX0 + boxX1) / 2;
|
|
2440
|
+
const drawPositionPill = (text, centerX, centerY, bg) => {
|
|
2441
|
+
const prevFont = ctx.font;
|
|
2442
|
+
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2443
|
+
const padding = 6;
|
|
2444
|
+
const textW = ctx.measureText(text).width;
|
|
2445
|
+
const pillW = textW + padding * 2;
|
|
2446
|
+
const pillH = 18;
|
|
2447
|
+
const pillX = centerX - pillW / 2;
|
|
2448
|
+
const pillY = centerY - pillH / 2;
|
|
2449
|
+
ctx.fillStyle = bg;
|
|
2450
|
+
fillRoundedRect(pillX, pillY, pillW, pillH, 4);
|
|
2451
|
+
ctx.fillStyle = labelTextColor;
|
|
2452
|
+
ctx.textAlign = "center";
|
|
2453
|
+
ctx.textBaseline = "middle";
|
|
2454
|
+
ctx.fillText(text, centerX, pillY + pillH / 2);
|
|
2455
|
+
ctx.font = prevFont;
|
|
2456
|
+
};
|
|
2457
|
+
drawPositionPill(`Target ${formatPrice(target.price)} (${pctOf(target.price)})${ticksOf(target.price)}`, cx, targetY, profitLine);
|
|
2458
|
+
drawPositionPill(`Stop ${formatPrice(stop.price)} (${pctOf(stop.price)})${ticksOf(stop.price)}`, cx, stopY, lossLine);
|
|
2459
|
+
drawPositionPill(`Entry ${formatPrice(entry.price)} RR ${rr.toFixed(2)}`, cx, entryY, hexToRgba(drawing.color, 0.92));
|
|
2460
|
+
if (drawing.label) {
|
|
2461
|
+
drawDrawingLabel(drawing.label, cx, Math.min(targetY, stopY) - 4, drawing.color);
|
|
2462
|
+
}
|
|
2454
2463
|
}
|
|
2455
2464
|
}
|
|
2456
2465
|
}
|
|
@@ -3908,6 +3917,13 @@ function createChart(element, options = {}) {
|
|
|
3908
3917
|
}
|
|
3909
3918
|
drawingToolDefaults.set(tool, { ...defaults });
|
|
3910
3919
|
};
|
|
3920
|
+
const setSelectedDrawing = (id) => {
|
|
3921
|
+
if (selectedDrawingId === id) {
|
|
3922
|
+
return;
|
|
3923
|
+
}
|
|
3924
|
+
selectedDrawingId = id;
|
|
3925
|
+
draw();
|
|
3926
|
+
};
|
|
3911
3927
|
const updateDrawingDrag = (x, y) => {
|
|
3912
3928
|
if (!drawingDragState) {
|
|
3913
3929
|
return false;
|
|
@@ -4089,6 +4105,7 @@ function createChart(element, options = {}) {
|
|
|
4089
4105
|
if (region === "plot" && !activeDrawingTool) {
|
|
4090
4106
|
const drawingHit = getDrawingHit(point.x, point.y);
|
|
4091
4107
|
if (drawingHit) {
|
|
4108
|
+
selectedDrawingId = drawingHit.drawing.id;
|
|
4092
4109
|
drawingSelectHandler?.({
|
|
4093
4110
|
drawing: serializeDrawing(drawingHit.drawing),
|
|
4094
4111
|
target: drawingHit.target,
|
|
@@ -4398,6 +4415,7 @@ function createChart(element, options = {}) {
|
|
|
4398
4415
|
emitCrosshairMove(point.x, point.y, "plot");
|
|
4399
4416
|
}
|
|
4400
4417
|
} else if (!pointerDownInfo.moved) {
|
|
4418
|
+
selectedDrawingId = null;
|
|
4401
4419
|
const clickPrice = pointerDownInfo.region === "plot" ? roundToPricePrecision(priceFromCanvasY(pointerDownInfo.y)) : void 0;
|
|
4402
4420
|
chartClickHandler?.({
|
|
4403
4421
|
x: pointerDownInfo.x,
|
|
@@ -4820,6 +4838,7 @@ function createChart(element, options = {}) {
|
|
|
4820
4838
|
clearDrawings,
|
|
4821
4839
|
onDrawingsChange,
|
|
4822
4840
|
onDrawingSelect,
|
|
4841
|
+
setSelectedDrawing,
|
|
4823
4842
|
onDrawingHover,
|
|
4824
4843
|
setDrawingDefaults,
|
|
4825
4844
|
setDoubleClickEnabled,
|
package/dist/index.cjs
CHANGED
|
@@ -984,6 +984,7 @@ function createChart(element, options = {}) {
|
|
|
984
984
|
let drawingSelectHandler = null;
|
|
985
985
|
let drawingHoverHandler = null;
|
|
986
986
|
let lastHoveredDrawingId = null;
|
|
987
|
+
let selectedDrawingId = null;
|
|
987
988
|
const drawingToolDefaults = /* @__PURE__ */ new Map();
|
|
988
989
|
const getDrawingToolDefaults = (tool) => drawingToolDefaults.get(tool) ?? {};
|
|
989
990
|
const emitDrawingsChange = () => {
|
|
@@ -2162,6 +2163,12 @@ function createChart(element, options = {}) {
|
|
|
2162
2163
|
if (!drawing.visible) {
|
|
2163
2164
|
return;
|
|
2164
2165
|
}
|
|
2166
|
+
const isSelected = draft || drawing.id === selectedDrawingId;
|
|
2167
|
+
const handleAt = (hx, hy, _color) => {
|
|
2168
|
+
if (isSelected) {
|
|
2169
|
+
drawDrawingHandle(hx, hy, drawing.color);
|
|
2170
|
+
}
|
|
2171
|
+
};
|
|
2165
2172
|
ctx.save();
|
|
2166
2173
|
ctx.strokeStyle = drawing.color;
|
|
2167
2174
|
ctx.lineWidth = drawing.width;
|
|
@@ -2180,7 +2187,7 @@ function createChart(element, options = {}) {
|
|
|
2180
2187
|
ctx.moveTo(crisp(chartLeft), crisp(y));
|
|
2181
2188
|
ctx.lineTo(crisp(chartRight), crisp(y));
|
|
2182
2189
|
ctx.stroke();
|
|
2183
|
-
|
|
2190
|
+
handleAt(handleX, y, drawing.color);
|
|
2184
2191
|
if (drawing.label) {
|
|
2185
2192
|
const midX = (chartLeft + chartRight) / 2;
|
|
2186
2193
|
drawDrawingLabel(drawing.label, midX, y, drawing.color);
|
|
@@ -2195,7 +2202,7 @@ function createChart(element, options = {}) {
|
|
|
2195
2202
|
ctx.lineTo(crisp(x), crisp(chartBottom));
|
|
2196
2203
|
ctx.stroke();
|
|
2197
2204
|
const handleY = (chartTop + chartBottom) / 2;
|
|
2198
|
-
|
|
2205
|
+
handleAt(x, handleY, drawing.color);
|
|
2199
2206
|
if (drawing.label) {
|
|
2200
2207
|
drawDrawingLabel(drawing.label, x, chartTop + 16, drawing.color);
|
|
2201
2208
|
}
|
|
@@ -2212,8 +2219,8 @@ function createChart(element, options = {}) {
|
|
|
2212
2219
|
ctx.moveTo(firstX, firstY);
|
|
2213
2220
|
ctx.lineTo(secondX, secondY);
|
|
2214
2221
|
ctx.stroke();
|
|
2215
|
-
|
|
2216
|
-
|
|
2222
|
+
handleAt(firstX, firstY, drawing.color);
|
|
2223
|
+
handleAt(secondX, secondY, drawing.color);
|
|
2217
2224
|
if (drawing.label) {
|
|
2218
2225
|
const midX = (firstX + secondX) / 2;
|
|
2219
2226
|
const midY = (firstY + secondY) / 2;
|
|
@@ -2242,8 +2249,8 @@ function createChart(element, options = {}) {
|
|
|
2242
2249
|
ctx.moveTo(firstX, firstY);
|
|
2243
2250
|
ctx.lineTo(endX, endY);
|
|
2244
2251
|
ctx.stroke();
|
|
2245
|
-
|
|
2246
|
-
|
|
2252
|
+
handleAt(firstX, firstY, drawing.color);
|
|
2253
|
+
handleAt(secondX, secondY, drawing.color);
|
|
2247
2254
|
if (drawing.label) {
|
|
2248
2255
|
const midX = (firstX + secondX) / 2;
|
|
2249
2256
|
const midY = (firstY + secondY) / 2;
|
|
@@ -2297,8 +2304,8 @@ function createChart(element, options = {}) {
|
|
|
2297
2304
|
ctx.lineTo(secondX, secondY);
|
|
2298
2305
|
ctx.stroke();
|
|
2299
2306
|
ctx.restore();
|
|
2300
|
-
|
|
2301
|
-
|
|
2307
|
+
handleAt(firstX, firstY, drawing.color);
|
|
2308
|
+
handleAt(secondX, secondY, drawing.color);
|
|
2302
2309
|
const prevFont = ctx.font;
|
|
2303
2310
|
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2304
2311
|
levelLines.forEach((level, index) => {
|
|
@@ -2337,12 +2344,12 @@ function createChart(element, options = {}) {
|
|
|
2337
2344
|
}
|
|
2338
2345
|
ctx.stroke();
|
|
2339
2346
|
ctx.restore();
|
|
2340
|
-
|
|
2341
|
-
|
|
2347
|
+
handleAt(x0, y0, drawing.color);
|
|
2348
|
+
handleAt(x1, y1, drawing.color);
|
|
2342
2349
|
if (p2) {
|
|
2343
2350
|
const x2 = xFromDrawingPoint(p2);
|
|
2344
2351
|
const y2 = yFromPrice(p2.price);
|
|
2345
|
-
|
|
2352
|
+
handleAt(x2, y2, drawing.color);
|
|
2346
2353
|
const palette = drawing.colors.length > 0 ? drawing.colors : null;
|
|
2347
2354
|
const levelColorAt = (index) => palette ? palette[index % palette.length] : drawing.color;
|
|
2348
2355
|
const move = p1.price - p0.price;
|
|
@@ -2422,61 +2429,63 @@ function createChart(element, options = {}) {
|
|
|
2422
2429
|
ctx.fillStyle = lossFill;
|
|
2423
2430
|
ctx.fillRect(boxX0, Math.min(entryY, stopY), boxW, Math.abs(stopY - entryY));
|
|
2424
2431
|
ctx.restore();
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
const
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2432
|
+
if (isSelected) {
|
|
2433
|
+
ctx.save();
|
|
2434
|
+
ctx.setLineDash([]);
|
|
2435
|
+
ctx.lineWidth = Math.max(1, drawing.width);
|
|
2436
|
+
ctx.strokeStyle = profitLine;
|
|
2437
|
+
ctx.beginPath();
|
|
2438
|
+
ctx.moveTo(crisp(boxX0), crisp(targetY));
|
|
2439
|
+
ctx.lineTo(crisp(boxX1), crisp(targetY));
|
|
2440
|
+
ctx.stroke();
|
|
2441
|
+
ctx.strokeStyle = lossLine;
|
|
2442
|
+
ctx.beginPath();
|
|
2443
|
+
ctx.moveTo(crisp(boxX0), crisp(stopY));
|
|
2444
|
+
ctx.lineTo(crisp(boxX1), crisp(stopY));
|
|
2445
|
+
ctx.stroke();
|
|
2446
|
+
ctx.strokeStyle = drawing.color;
|
|
2447
|
+
ctx.beginPath();
|
|
2448
|
+
ctx.moveTo(crisp(boxX0), crisp(entryY));
|
|
2449
|
+
ctx.lineTo(crisp(boxX1), crisp(entryY));
|
|
2450
|
+
ctx.stroke();
|
|
2451
|
+
ctx.restore();
|
|
2452
|
+
handleAt(leftX, targetY, drawing.color);
|
|
2453
|
+
handleAt(leftX, entryY, drawing.color);
|
|
2454
|
+
handleAt(leftX, stopY, drawing.color);
|
|
2455
|
+
handleAt(rightX, entryY, drawing.color);
|
|
2456
|
+
const tick = getConfiguredTickSize();
|
|
2457
|
+
const pctOf = (price) => {
|
|
2458
|
+
if (!Number.isFinite(entry.price) || entry.price === 0) return "0.00%";
|
|
2459
|
+
return `${((price - entry.price) / Math.abs(entry.price) * 100).toFixed(2)}%`;
|
|
2460
|
+
};
|
|
2461
|
+
const ticksOf = (price) => tick > 0 ? ` ${Math.round(Math.abs(price - entry.price) / tick)}t` : "";
|
|
2462
|
+
const targetDist = Math.abs(target.price - entry.price);
|
|
2463
|
+
const stopDist = Math.abs(entry.price - stop.price);
|
|
2464
|
+
const rr = stopDist > 0 ? targetDist / stopDist : 0;
|
|
2465
|
+
const cx = (boxX0 + boxX1) / 2;
|
|
2466
|
+
const drawPositionPill = (text, centerX, centerY, bg) => {
|
|
2467
|
+
const prevFont = ctx.font;
|
|
2468
|
+
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2469
|
+
const padding = 6;
|
|
2470
|
+
const textW = ctx.measureText(text).width;
|
|
2471
|
+
const pillW = textW + padding * 2;
|
|
2472
|
+
const pillH = 18;
|
|
2473
|
+
const pillX = centerX - pillW / 2;
|
|
2474
|
+
const pillY = centerY - pillH / 2;
|
|
2475
|
+
ctx.fillStyle = bg;
|
|
2476
|
+
fillRoundedRect(pillX, pillY, pillW, pillH, 4);
|
|
2477
|
+
ctx.fillStyle = labelTextColor;
|
|
2478
|
+
ctx.textAlign = "center";
|
|
2479
|
+
ctx.textBaseline = "middle";
|
|
2480
|
+
ctx.fillText(text, centerX, pillY + pillH / 2);
|
|
2481
|
+
ctx.font = prevFont;
|
|
2482
|
+
};
|
|
2483
|
+
drawPositionPill(`Target ${formatPrice(target.price)} (${pctOf(target.price)})${ticksOf(target.price)}`, cx, targetY, profitLine);
|
|
2484
|
+
drawPositionPill(`Stop ${formatPrice(stop.price)} (${pctOf(stop.price)})${ticksOf(stop.price)}`, cx, stopY, lossLine);
|
|
2485
|
+
drawPositionPill(`Entry ${formatPrice(entry.price)} RR ${rr.toFixed(2)}`, cx, entryY, hexToRgba(drawing.color, 0.92));
|
|
2486
|
+
if (drawing.label) {
|
|
2487
|
+
drawDrawingLabel(drawing.label, cx, Math.min(targetY, stopY) - 4, drawing.color);
|
|
2488
|
+
}
|
|
2480
2489
|
}
|
|
2481
2490
|
}
|
|
2482
2491
|
}
|
|
@@ -3934,6 +3943,13 @@ function createChart(element, options = {}) {
|
|
|
3934
3943
|
}
|
|
3935
3944
|
drawingToolDefaults.set(tool, { ...defaults });
|
|
3936
3945
|
};
|
|
3946
|
+
const setSelectedDrawing = (id) => {
|
|
3947
|
+
if (selectedDrawingId === id) {
|
|
3948
|
+
return;
|
|
3949
|
+
}
|
|
3950
|
+
selectedDrawingId = id;
|
|
3951
|
+
draw();
|
|
3952
|
+
};
|
|
3937
3953
|
const updateDrawingDrag = (x, y) => {
|
|
3938
3954
|
if (!drawingDragState) {
|
|
3939
3955
|
return false;
|
|
@@ -4115,6 +4131,7 @@ function createChart(element, options = {}) {
|
|
|
4115
4131
|
if (region === "plot" && !activeDrawingTool) {
|
|
4116
4132
|
const drawingHit = getDrawingHit(point.x, point.y);
|
|
4117
4133
|
if (drawingHit) {
|
|
4134
|
+
selectedDrawingId = drawingHit.drawing.id;
|
|
4118
4135
|
drawingSelectHandler?.({
|
|
4119
4136
|
drawing: serializeDrawing(drawingHit.drawing),
|
|
4120
4137
|
target: drawingHit.target,
|
|
@@ -4424,6 +4441,7 @@ function createChart(element, options = {}) {
|
|
|
4424
4441
|
emitCrosshairMove(point.x, point.y, "plot");
|
|
4425
4442
|
}
|
|
4426
4443
|
} else if (!pointerDownInfo.moved) {
|
|
4444
|
+
selectedDrawingId = null;
|
|
4427
4445
|
const clickPrice = pointerDownInfo.region === "plot" ? roundToPricePrecision(priceFromCanvasY(pointerDownInfo.y)) : void 0;
|
|
4428
4446
|
chartClickHandler?.({
|
|
4429
4447
|
x: pointerDownInfo.x,
|
|
@@ -4846,6 +4864,7 @@ function createChart(element, options = {}) {
|
|
|
4846
4864
|
clearDrawings,
|
|
4847
4865
|
onDrawingsChange,
|
|
4848
4866
|
onDrawingSelect,
|
|
4867
|
+
setSelectedDrawing,
|
|
4849
4868
|
onDrawingHover,
|
|
4850
4869
|
setDrawingDefaults,
|
|
4851
4870
|
setDoubleClickEnabled,
|
package/dist/index.d.cts
CHANGED
|
@@ -422,6 +422,7 @@ interface ChartInstance {
|
|
|
422
422
|
onDrawingSelect: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
|
|
423
423
|
onDrawingHover: (handler: ((event: DrawingHoverEvent) => void) | null) => void;
|
|
424
424
|
setDrawingDefaults: (tool: DrawingToolType, defaults: DrawingDefaults | null) => void;
|
|
425
|
+
setSelectedDrawing: (id: string | null) => void;
|
|
425
426
|
setDoubleClickEnabled: (enabled: boolean) => void;
|
|
426
427
|
setDoubleClickAction: (action: "reset" | "placeLimitOrder") => void;
|
|
427
428
|
registerIndicator: (plugin: IndicatorPlugin<any>) => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -422,6 +422,7 @@ interface ChartInstance {
|
|
|
422
422
|
onDrawingSelect: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
|
|
423
423
|
onDrawingHover: (handler: ((event: DrawingHoverEvent) => void) | null) => void;
|
|
424
424
|
setDrawingDefaults: (tool: DrawingToolType, defaults: DrawingDefaults | null) => void;
|
|
425
|
+
setSelectedDrawing: (id: string | null) => void;
|
|
425
426
|
setDoubleClickEnabled: (enabled: boolean) => void;
|
|
426
427
|
setDoubleClickAction: (action: "reset" | "placeLimitOrder") => void;
|
|
427
428
|
registerIndicator: (plugin: IndicatorPlugin<any>) => void;
|
package/dist/index.js
CHANGED
|
@@ -958,6 +958,7 @@ function createChart(element, options = {}) {
|
|
|
958
958
|
let drawingSelectHandler = null;
|
|
959
959
|
let drawingHoverHandler = null;
|
|
960
960
|
let lastHoveredDrawingId = null;
|
|
961
|
+
let selectedDrawingId = null;
|
|
961
962
|
const drawingToolDefaults = /* @__PURE__ */ new Map();
|
|
962
963
|
const getDrawingToolDefaults = (tool) => drawingToolDefaults.get(tool) ?? {};
|
|
963
964
|
const emitDrawingsChange = () => {
|
|
@@ -2136,6 +2137,12 @@ function createChart(element, options = {}) {
|
|
|
2136
2137
|
if (!drawing.visible) {
|
|
2137
2138
|
return;
|
|
2138
2139
|
}
|
|
2140
|
+
const isSelected = draft || drawing.id === selectedDrawingId;
|
|
2141
|
+
const handleAt = (hx, hy, _color) => {
|
|
2142
|
+
if (isSelected) {
|
|
2143
|
+
drawDrawingHandle(hx, hy, drawing.color);
|
|
2144
|
+
}
|
|
2145
|
+
};
|
|
2139
2146
|
ctx.save();
|
|
2140
2147
|
ctx.strokeStyle = drawing.color;
|
|
2141
2148
|
ctx.lineWidth = drawing.width;
|
|
@@ -2154,7 +2161,7 @@ function createChart(element, options = {}) {
|
|
|
2154
2161
|
ctx.moveTo(crisp(chartLeft), crisp(y));
|
|
2155
2162
|
ctx.lineTo(crisp(chartRight), crisp(y));
|
|
2156
2163
|
ctx.stroke();
|
|
2157
|
-
|
|
2164
|
+
handleAt(handleX, y, drawing.color);
|
|
2158
2165
|
if (drawing.label) {
|
|
2159
2166
|
const midX = (chartLeft + chartRight) / 2;
|
|
2160
2167
|
drawDrawingLabel(drawing.label, midX, y, drawing.color);
|
|
@@ -2169,7 +2176,7 @@ function createChart(element, options = {}) {
|
|
|
2169
2176
|
ctx.lineTo(crisp(x), crisp(chartBottom));
|
|
2170
2177
|
ctx.stroke();
|
|
2171
2178
|
const handleY = (chartTop + chartBottom) / 2;
|
|
2172
|
-
|
|
2179
|
+
handleAt(x, handleY, drawing.color);
|
|
2173
2180
|
if (drawing.label) {
|
|
2174
2181
|
drawDrawingLabel(drawing.label, x, chartTop + 16, drawing.color);
|
|
2175
2182
|
}
|
|
@@ -2186,8 +2193,8 @@ function createChart(element, options = {}) {
|
|
|
2186
2193
|
ctx.moveTo(firstX, firstY);
|
|
2187
2194
|
ctx.lineTo(secondX, secondY);
|
|
2188
2195
|
ctx.stroke();
|
|
2189
|
-
|
|
2190
|
-
|
|
2196
|
+
handleAt(firstX, firstY, drawing.color);
|
|
2197
|
+
handleAt(secondX, secondY, drawing.color);
|
|
2191
2198
|
if (drawing.label) {
|
|
2192
2199
|
const midX = (firstX + secondX) / 2;
|
|
2193
2200
|
const midY = (firstY + secondY) / 2;
|
|
@@ -2216,8 +2223,8 @@ function createChart(element, options = {}) {
|
|
|
2216
2223
|
ctx.moveTo(firstX, firstY);
|
|
2217
2224
|
ctx.lineTo(endX, endY);
|
|
2218
2225
|
ctx.stroke();
|
|
2219
|
-
|
|
2220
|
-
|
|
2226
|
+
handleAt(firstX, firstY, drawing.color);
|
|
2227
|
+
handleAt(secondX, secondY, drawing.color);
|
|
2221
2228
|
if (drawing.label) {
|
|
2222
2229
|
const midX = (firstX + secondX) / 2;
|
|
2223
2230
|
const midY = (firstY + secondY) / 2;
|
|
@@ -2271,8 +2278,8 @@ function createChart(element, options = {}) {
|
|
|
2271
2278
|
ctx.lineTo(secondX, secondY);
|
|
2272
2279
|
ctx.stroke();
|
|
2273
2280
|
ctx.restore();
|
|
2274
|
-
|
|
2275
|
-
|
|
2281
|
+
handleAt(firstX, firstY, drawing.color);
|
|
2282
|
+
handleAt(secondX, secondY, drawing.color);
|
|
2276
2283
|
const prevFont = ctx.font;
|
|
2277
2284
|
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2278
2285
|
levelLines.forEach((level, index) => {
|
|
@@ -2311,12 +2318,12 @@ function createChart(element, options = {}) {
|
|
|
2311
2318
|
}
|
|
2312
2319
|
ctx.stroke();
|
|
2313
2320
|
ctx.restore();
|
|
2314
|
-
|
|
2315
|
-
|
|
2321
|
+
handleAt(x0, y0, drawing.color);
|
|
2322
|
+
handleAt(x1, y1, drawing.color);
|
|
2316
2323
|
if (p2) {
|
|
2317
2324
|
const x2 = xFromDrawingPoint(p2);
|
|
2318
2325
|
const y2 = yFromPrice(p2.price);
|
|
2319
|
-
|
|
2326
|
+
handleAt(x2, y2, drawing.color);
|
|
2320
2327
|
const palette = drawing.colors.length > 0 ? drawing.colors : null;
|
|
2321
2328
|
const levelColorAt = (index) => palette ? palette[index % palette.length] : drawing.color;
|
|
2322
2329
|
const move = p1.price - p0.price;
|
|
@@ -2396,61 +2403,63 @@ function createChart(element, options = {}) {
|
|
|
2396
2403
|
ctx.fillStyle = lossFill;
|
|
2397
2404
|
ctx.fillRect(boxX0, Math.min(entryY, stopY), boxW, Math.abs(stopY - entryY));
|
|
2398
2405
|
ctx.restore();
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
const
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2406
|
+
if (isSelected) {
|
|
2407
|
+
ctx.save();
|
|
2408
|
+
ctx.setLineDash([]);
|
|
2409
|
+
ctx.lineWidth = Math.max(1, drawing.width);
|
|
2410
|
+
ctx.strokeStyle = profitLine;
|
|
2411
|
+
ctx.beginPath();
|
|
2412
|
+
ctx.moveTo(crisp(boxX0), crisp(targetY));
|
|
2413
|
+
ctx.lineTo(crisp(boxX1), crisp(targetY));
|
|
2414
|
+
ctx.stroke();
|
|
2415
|
+
ctx.strokeStyle = lossLine;
|
|
2416
|
+
ctx.beginPath();
|
|
2417
|
+
ctx.moveTo(crisp(boxX0), crisp(stopY));
|
|
2418
|
+
ctx.lineTo(crisp(boxX1), crisp(stopY));
|
|
2419
|
+
ctx.stroke();
|
|
2420
|
+
ctx.strokeStyle = drawing.color;
|
|
2421
|
+
ctx.beginPath();
|
|
2422
|
+
ctx.moveTo(crisp(boxX0), crisp(entryY));
|
|
2423
|
+
ctx.lineTo(crisp(boxX1), crisp(entryY));
|
|
2424
|
+
ctx.stroke();
|
|
2425
|
+
ctx.restore();
|
|
2426
|
+
handleAt(leftX, targetY, drawing.color);
|
|
2427
|
+
handleAt(leftX, entryY, drawing.color);
|
|
2428
|
+
handleAt(leftX, stopY, drawing.color);
|
|
2429
|
+
handleAt(rightX, entryY, drawing.color);
|
|
2430
|
+
const tick = getConfiguredTickSize();
|
|
2431
|
+
const pctOf = (price) => {
|
|
2432
|
+
if (!Number.isFinite(entry.price) || entry.price === 0) return "0.00%";
|
|
2433
|
+
return `${((price - entry.price) / Math.abs(entry.price) * 100).toFixed(2)}%`;
|
|
2434
|
+
};
|
|
2435
|
+
const ticksOf = (price) => tick > 0 ? ` ${Math.round(Math.abs(price - entry.price) / tick)}t` : "";
|
|
2436
|
+
const targetDist = Math.abs(target.price - entry.price);
|
|
2437
|
+
const stopDist = Math.abs(entry.price - stop.price);
|
|
2438
|
+
const rr = stopDist > 0 ? targetDist / stopDist : 0;
|
|
2439
|
+
const cx = (boxX0 + boxX1) / 2;
|
|
2440
|
+
const drawPositionPill = (text, centerX, centerY, bg) => {
|
|
2441
|
+
const prevFont = ctx.font;
|
|
2442
|
+
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2443
|
+
const padding = 6;
|
|
2444
|
+
const textW = ctx.measureText(text).width;
|
|
2445
|
+
const pillW = textW + padding * 2;
|
|
2446
|
+
const pillH = 18;
|
|
2447
|
+
const pillX = centerX - pillW / 2;
|
|
2448
|
+
const pillY = centerY - pillH / 2;
|
|
2449
|
+
ctx.fillStyle = bg;
|
|
2450
|
+
fillRoundedRect(pillX, pillY, pillW, pillH, 4);
|
|
2451
|
+
ctx.fillStyle = labelTextColor;
|
|
2452
|
+
ctx.textAlign = "center";
|
|
2453
|
+
ctx.textBaseline = "middle";
|
|
2454
|
+
ctx.fillText(text, centerX, pillY + pillH / 2);
|
|
2455
|
+
ctx.font = prevFont;
|
|
2456
|
+
};
|
|
2457
|
+
drawPositionPill(`Target ${formatPrice(target.price)} (${pctOf(target.price)})${ticksOf(target.price)}`, cx, targetY, profitLine);
|
|
2458
|
+
drawPositionPill(`Stop ${formatPrice(stop.price)} (${pctOf(stop.price)})${ticksOf(stop.price)}`, cx, stopY, lossLine);
|
|
2459
|
+
drawPositionPill(`Entry ${formatPrice(entry.price)} RR ${rr.toFixed(2)}`, cx, entryY, hexToRgba(drawing.color, 0.92));
|
|
2460
|
+
if (drawing.label) {
|
|
2461
|
+
drawDrawingLabel(drawing.label, cx, Math.min(targetY, stopY) - 4, drawing.color);
|
|
2462
|
+
}
|
|
2454
2463
|
}
|
|
2455
2464
|
}
|
|
2456
2465
|
}
|
|
@@ -3908,6 +3917,13 @@ function createChart(element, options = {}) {
|
|
|
3908
3917
|
}
|
|
3909
3918
|
drawingToolDefaults.set(tool, { ...defaults });
|
|
3910
3919
|
};
|
|
3920
|
+
const setSelectedDrawing = (id) => {
|
|
3921
|
+
if (selectedDrawingId === id) {
|
|
3922
|
+
return;
|
|
3923
|
+
}
|
|
3924
|
+
selectedDrawingId = id;
|
|
3925
|
+
draw();
|
|
3926
|
+
};
|
|
3911
3927
|
const updateDrawingDrag = (x, y) => {
|
|
3912
3928
|
if (!drawingDragState) {
|
|
3913
3929
|
return false;
|
|
@@ -4089,6 +4105,7 @@ function createChart(element, options = {}) {
|
|
|
4089
4105
|
if (region === "plot" && !activeDrawingTool) {
|
|
4090
4106
|
const drawingHit = getDrawingHit(point.x, point.y);
|
|
4091
4107
|
if (drawingHit) {
|
|
4108
|
+
selectedDrawingId = drawingHit.drawing.id;
|
|
4092
4109
|
drawingSelectHandler?.({
|
|
4093
4110
|
drawing: serializeDrawing(drawingHit.drawing),
|
|
4094
4111
|
target: drawingHit.target,
|
|
@@ -4398,6 +4415,7 @@ function createChart(element, options = {}) {
|
|
|
4398
4415
|
emitCrosshairMove(point.x, point.y, "plot");
|
|
4399
4416
|
}
|
|
4400
4417
|
} else if (!pointerDownInfo.moved) {
|
|
4418
|
+
selectedDrawingId = null;
|
|
4401
4419
|
const clickPrice = pointerDownInfo.region === "plot" ? roundToPricePrecision(priceFromCanvasY(pointerDownInfo.y)) : void 0;
|
|
4402
4420
|
chartClickHandler?.({
|
|
4403
4421
|
x: pointerDownInfo.x,
|
|
@@ -4820,6 +4838,7 @@ function createChart(element, options = {}) {
|
|
|
4820
4838
|
clearDrawings,
|
|
4821
4839
|
onDrawingsChange,
|
|
4822
4840
|
onDrawingSelect,
|
|
4841
|
+
setSelectedDrawing,
|
|
4823
4842
|
onDrawingHover,
|
|
4824
4843
|
setDrawingDefaults,
|
|
4825
4844
|
setDoubleClickEnabled,
|
package/docs/API.md
CHANGED
|
@@ -481,6 +481,7 @@ Use `getDrawings()` / `setDrawings()` for persistence.
|
|
|
481
481
|
- `panY(priceDelta: number): void` (positive = move viewport up)
|
|
482
482
|
- `fitContent(): void` (x-only fit, keeps y zoom)
|
|
483
483
|
- `resetViewport(): void` (fit x + reset y auto-scale)
|
|
484
|
+
- `setSelectedDrawing(id: string | null): void` (marks a drawing selected; only the selected/drafted drawing renders its handles, and position tools render their lines/labels only while selected)
|
|
484
485
|
- `setActiveDrawingTool(tool: DrawingToolType | null): void` (`DrawingToolType` = `"horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement"`)
|
|
485
486
|
- `getActiveDrawingTool(): DrawingToolType | null`
|
|
486
487
|
- `setDrawings(drawings: DrawingObjectOptions[]): void`
|