hyperprop-charting-library 0.1.16 → 0.1.17
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/README.md +17 -1
- package/dist/hyperprop-charting-library.cjs +75 -18
- package/dist/hyperprop-charting-library.d.ts +17 -1
- package/dist/hyperprop-charting-library.js +75 -18
- package/dist/index.cjs +75 -18
- package/dist/index.d.cts +17 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +75 -18
- package/docs/API.md +11 -0
- package/docs/EVENTS.md +27 -0
- package/docs/RECIPES.md +29 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,6 +60,22 @@ const chart = createChart(root, {
|
|
|
60
60
|
});
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
## Crosshair "+" Button
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const chart = createChart(root, {
|
|
67
|
+
crosshair: {
|
|
68
|
+
showPriceActionButton: true,
|
|
69
|
+
priceActionButtonRounded: false // square button
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
chart.onCrosshairPriceAction((event) => {
|
|
74
|
+
// Frontend decides what to do
|
|
75
|
+
console.log(event.price);
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
63
79
|
## Full Documentation
|
|
64
80
|
|
|
65
81
|
- API reference: `docs/API.md`
|
|
@@ -74,7 +90,7 @@ const chart = createChart(root, {
|
|
|
74
90
|
- `chart.setData(data)`
|
|
75
91
|
- `chart.setPriceLines(lines)` / `chart.addPriceLine(line)` / `chart.removePriceLine(id)`
|
|
76
92
|
- `chart.setOrderLines(lines)` / `chart.addOrderLine(line)` / `chart.updateOrderLine(id, patch)` / `chart.removeOrderLine(id)`
|
|
77
|
-
- `chart.onOrderAction(handler)` / `chart.onChartClick(handler)` / `chart.onCrosshairMove(handler)`
|
|
93
|
+
- `chart.onOrderAction(handler)` / `chart.onChartClick(handler)` / `chart.onCrosshairMove(handler)` / `chart.onCrosshairPriceAction(handler)`
|
|
78
94
|
- `chart.setDoubleClickEnabled(enabled)` / `chart.setDoubleClickAction(action)`
|
|
79
95
|
- `chart.zoomInX()` / `chart.zoomOutX()` / `chart.panX(bars)` / `chart.resetViewport()`
|
|
80
96
|
- `chart.resize(width, height)` / `chart.destroy()`
|
|
@@ -51,7 +51,17 @@ var DEFAULT_CROSSHAIR_OPTIONS = {
|
|
|
51
51
|
labelBorderRadius: 3,
|
|
52
52
|
labelBorderColor: "#94a3b8",
|
|
53
53
|
labelBorderWidth: 1,
|
|
54
|
-
labelBorderStyle: "solid"
|
|
54
|
+
labelBorderStyle: "solid",
|
|
55
|
+
showPriceActionButton: false,
|
|
56
|
+
priceActionButtonText: "+",
|
|
57
|
+
priceActionButtonSize: 20,
|
|
58
|
+
priceActionButtonGap: 6,
|
|
59
|
+
priceActionButtonBackgroundColor: "#1f2937",
|
|
60
|
+
priceActionButtonTextColor: "#e2e8f0",
|
|
61
|
+
priceActionButtonBorderColor: "#475569",
|
|
62
|
+
priceActionButtonBorderWidth: 1,
|
|
63
|
+
priceActionButtonRounded: true,
|
|
64
|
+
priceActionButtonBorderRadius: 3
|
|
55
65
|
};
|
|
56
66
|
var DEFAULT_WATERMARK_OPTIONS = {
|
|
57
67
|
visible: false,
|
|
@@ -163,10 +173,6 @@ var DEFAULT_OPTIONS = {
|
|
|
163
173
|
},
|
|
164
174
|
dashPatterns: DEFAULT_DASH_PATTERNS
|
|
165
175
|
};
|
|
166
|
-
var BRAND_LOGO_VIEWBOX_WIDTH = 190;
|
|
167
|
-
var BRAND_LOGO_VIEWBOX_HEIGHT = 186;
|
|
168
|
-
var BRAND_LOGO_PATH_A = "M0 93.0171V45.2271H48.9851V75.0332C48.9851 84.9545 57.0416 93.0001 66.9763 93.0001H94.9957V186H49.0531V110.984C49.0531 101.063 40.9965 93.0171 31.0619 93.0171H0Z";
|
|
169
|
-
var BRAND_LOGO_PATH_B = "M190 92.9915V140.782H141.015V110.975C141.015 101.054 132.958 93.0085 123.023 93.0085H95.0039V0H140.955V75.0162C140.955 84.9374 149.012 92.9831 158.946 92.9831H190V92.9915Z";
|
|
170
176
|
function createChart(element, options = {}) {
|
|
171
177
|
const mergedOptions = {
|
|
172
178
|
...DEFAULT_OPTIONS,
|
|
@@ -211,6 +217,8 @@ function createChart(element, options = {}) {
|
|
|
211
217
|
let orderActionHandler = null;
|
|
212
218
|
let chartClickHandler = null;
|
|
213
219
|
let crosshairMoveHandler = null;
|
|
220
|
+
let crosshairPriceActionHandler = null;
|
|
221
|
+
let crosshairPriceActionRegion = null;
|
|
214
222
|
let orderActionRegions = [];
|
|
215
223
|
let orderDragRegions = [];
|
|
216
224
|
let generatedPriceLineId = 1;
|
|
@@ -223,8 +231,6 @@ function createChart(element, options = {}) {
|
|
|
223
231
|
let yMaxOverride = null;
|
|
224
232
|
let autoYMin = null;
|
|
225
233
|
let autoYMax = null;
|
|
226
|
-
const brandLogoPathA = new Path2D(BRAND_LOGO_PATH_A);
|
|
227
|
-
const brandLogoPathB = new Path2D(BRAND_LOGO_PATH_B);
|
|
228
234
|
let watermarkImageSrc = null;
|
|
229
235
|
let watermarkImage = null;
|
|
230
236
|
let watermarkImageReady = false;
|
|
@@ -802,6 +808,7 @@ function createChart(element, options = {}) {
|
|
|
802
808
|
const draw = () => {
|
|
803
809
|
orderActionRegions = [];
|
|
804
810
|
orderDragRegions = [];
|
|
811
|
+
crosshairPriceActionRegion = null;
|
|
805
812
|
const pixelRatio = getPixelRatio();
|
|
806
813
|
canvas.style.width = `${width}px`;
|
|
807
814
|
canvas.style.height = `${height}px`;
|
|
@@ -1107,17 +1114,6 @@ function createChart(element, options = {}) {
|
|
|
1107
1114
|
});
|
|
1108
1115
|
drawText(timeLabel, x, chartBottom + 8, "center", "top", axis.textColor);
|
|
1109
1116
|
}
|
|
1110
|
-
const brandLogoWidth = 34;
|
|
1111
|
-
const brandLogoHeight = BRAND_LOGO_VIEWBOX_HEIGHT / BRAND_LOGO_VIEWBOX_WIDTH * brandLogoWidth;
|
|
1112
|
-
const brandLogoX = chartLeft + 16;
|
|
1113
|
-
const brandLogoY = chartBottom - brandLogoHeight - 10;
|
|
1114
|
-
ctx.save();
|
|
1115
|
-
ctx.translate(brandLogoX, brandLogoY);
|
|
1116
|
-
ctx.scale(brandLogoWidth / BRAND_LOGO_VIEWBOX_WIDTH, brandLogoHeight / BRAND_LOGO_VIEWBOX_HEIGHT);
|
|
1117
|
-
ctx.fillStyle = "#ffffff";
|
|
1118
|
-
ctx.fill(brandLogoPathA);
|
|
1119
|
-
ctx.fill(brandLogoPathB);
|
|
1120
|
-
ctx.restore();
|
|
1121
1117
|
if (crosshair.visible && crosshairPoint) {
|
|
1122
1118
|
const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
|
|
1123
1119
|
const cy = clamp(crosshairPoint.y, chartTop, chartBottom);
|
|
@@ -1154,6 +1150,39 @@ function createChart(element, options = {}) {
|
|
|
1154
1150
|
fillRoundedRect(Math.round(priceX), Math.round(priceY), priceWidth, labelHeight, labelRadius);
|
|
1155
1151
|
strokeCrosshairLabel(priceX, priceY, priceWidth);
|
|
1156
1152
|
drawText(priceText, priceX + labelPaddingX, priceY + labelHeight / 2, "left", "middle", labelTextColor);
|
|
1153
|
+
if (crosshair.showPriceActionButton) {
|
|
1154
|
+
const buttonSize = clamp(Math.round(crosshair.priceActionButtonSize), 12, 30);
|
|
1155
|
+
const buttonGap = Math.max(0, Math.round(crosshair.priceActionButtonGap));
|
|
1156
|
+
const buttonX = priceX - buttonGap - buttonSize;
|
|
1157
|
+
const buttonY = priceY + (labelHeight - buttonSize) / 2;
|
|
1158
|
+
const buttonRadius = crosshair.priceActionButtonRounded ? clamp(Math.round(crosshair.priceActionButtonBorderRadius), 0, buttonSize / 2) : 0;
|
|
1159
|
+
const buttonBorderWidth = Math.max(0, Math.round(crosshair.priceActionButtonBorderWidth));
|
|
1160
|
+
ctx.fillStyle = crosshair.priceActionButtonBackgroundColor;
|
|
1161
|
+
fillRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
|
|
1162
|
+
if (buttonBorderWidth > 0) {
|
|
1163
|
+
ctx.save();
|
|
1164
|
+
ctx.strokeStyle = crosshair.priceActionButtonBorderColor;
|
|
1165
|
+
ctx.lineWidth = buttonBorderWidth;
|
|
1166
|
+
ctx.setLineDash([]);
|
|
1167
|
+
strokeRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
|
|
1168
|
+
ctx.restore();
|
|
1169
|
+
}
|
|
1170
|
+
drawText(
|
|
1171
|
+
crosshair.priceActionButtonText,
|
|
1172
|
+
buttonX + buttonSize / 2,
|
|
1173
|
+
buttonY + buttonSize / 2,
|
|
1174
|
+
"center",
|
|
1175
|
+
"middle",
|
|
1176
|
+
crosshair.priceActionButtonTextColor
|
|
1177
|
+
);
|
|
1178
|
+
crosshairPriceActionRegion = {
|
|
1179
|
+
x: buttonX,
|
|
1180
|
+
y: buttonY,
|
|
1181
|
+
width: buttonSize,
|
|
1182
|
+
height: buttonSize,
|
|
1183
|
+
price: Number(hoverPrice.toFixed(mergedOptions.priceDecimals))
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1157
1186
|
}
|
|
1158
1187
|
if (crosshair.showTimeLabel) {
|
|
1159
1188
|
const ratio = clamp((cx - chartLeft) / chartWidth, 0, 1);
|
|
@@ -1316,6 +1345,16 @@ function createChart(element, options = {}) {
|
|
|
1316
1345
|
(region) => x >= region.x && x <= region.x + region.width && y >= region.y && y <= region.y + region.height
|
|
1317
1346
|
);
|
|
1318
1347
|
};
|
|
1348
|
+
const getCrosshairPriceActionRegion = (x, y) => {
|
|
1349
|
+
if (!crosshairPriceActionRegion) {
|
|
1350
|
+
return null;
|
|
1351
|
+
}
|
|
1352
|
+
const region = crosshairPriceActionRegion;
|
|
1353
|
+
if (x >= region.x && x <= region.x + region.width && y >= region.y && y <= region.y + region.height) {
|
|
1354
|
+
return region;
|
|
1355
|
+
}
|
|
1356
|
+
return null;
|
|
1357
|
+
};
|
|
1319
1358
|
const priceFromCanvasY = (y) => {
|
|
1320
1359
|
if (!drawState) {
|
|
1321
1360
|
return 0;
|
|
@@ -1381,6 +1420,15 @@ function createChart(element, options = {}) {
|
|
|
1381
1420
|
let activePointerId = null;
|
|
1382
1421
|
const onPointerDown = (event) => {
|
|
1383
1422
|
const point = getCanvasPoint(event);
|
|
1423
|
+
const crosshairButtonRegion = getCrosshairPriceActionRegion(point.x, point.y);
|
|
1424
|
+
if (crosshairButtonRegion) {
|
|
1425
|
+
crosshairPriceActionHandler?.({
|
|
1426
|
+
x: point.x,
|
|
1427
|
+
y: point.y,
|
|
1428
|
+
price: crosshairButtonRegion.price
|
|
1429
|
+
});
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1384
1432
|
const orderRegion = getOrderActionRegion(point.x, point.y);
|
|
1385
1433
|
if (orderRegion) {
|
|
1386
1434
|
if (orderRegion.draggable) {
|
|
@@ -1493,6 +1541,11 @@ function createChart(element, options = {}) {
|
|
|
1493
1541
|
return;
|
|
1494
1542
|
}
|
|
1495
1543
|
if (!isDragging || !dragMode) {
|
|
1544
|
+
const crosshairButtonRegion = getCrosshairPriceActionRegion(point.x, point.y);
|
|
1545
|
+
if (crosshairButtonRegion) {
|
|
1546
|
+
canvas.style.cursor = "pointer";
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1496
1549
|
const orderRegion = getOrderActionRegion(point.x, point.y);
|
|
1497
1550
|
if (orderRegion) {
|
|
1498
1551
|
canvas.style.cursor = orderRegion.draggable ? "ns-resize" : "pointer";
|
|
@@ -1746,6 +1799,9 @@ function createChart(element, options = {}) {
|
|
|
1746
1799
|
const onCrosshairMove = (handler) => {
|
|
1747
1800
|
crosshairMoveHandler = handler;
|
|
1748
1801
|
};
|
|
1802
|
+
const onCrosshairPriceAction = (handler) => {
|
|
1803
|
+
crosshairPriceActionHandler = handler;
|
|
1804
|
+
};
|
|
1749
1805
|
const setDoubleClickEnabled = (enabled) => {
|
|
1750
1806
|
doubleClickEnabled = enabled;
|
|
1751
1807
|
};
|
|
@@ -1775,6 +1831,7 @@ function createChart(element, options = {}) {
|
|
|
1775
1831
|
onOrderAction,
|
|
1776
1832
|
onChartClick,
|
|
1777
1833
|
onCrosshairMove,
|
|
1834
|
+
onCrosshairPriceAction,
|
|
1778
1835
|
zoomInX,
|
|
1779
1836
|
zoomOutX,
|
|
1780
1837
|
zoomInY,
|
|
@@ -71,6 +71,16 @@ interface CrosshairOptions {
|
|
|
71
71
|
labelBorderColor?: string;
|
|
72
72
|
labelBorderWidth?: number;
|
|
73
73
|
labelBorderStyle?: "solid" | "dotted" | "dashed";
|
|
74
|
+
showPriceActionButton?: boolean;
|
|
75
|
+
priceActionButtonText?: string;
|
|
76
|
+
priceActionButtonSize?: number;
|
|
77
|
+
priceActionButtonGap?: number;
|
|
78
|
+
priceActionButtonBackgroundColor?: string;
|
|
79
|
+
priceActionButtonTextColor?: string;
|
|
80
|
+
priceActionButtonBorderColor?: string;
|
|
81
|
+
priceActionButtonBorderWidth?: number;
|
|
82
|
+
priceActionButtonRounded?: boolean;
|
|
83
|
+
priceActionButtonBorderRadius?: number;
|
|
74
84
|
}
|
|
75
85
|
interface WatermarkOptions {
|
|
76
86
|
visible?: boolean;
|
|
@@ -178,6 +188,11 @@ interface CrosshairMoveEvent {
|
|
|
178
188
|
time?: string;
|
|
179
189
|
point?: OhlcDataPoint;
|
|
180
190
|
}
|
|
191
|
+
interface CrosshairPriceActionEvent {
|
|
192
|
+
x: number;
|
|
193
|
+
y: number;
|
|
194
|
+
price: number;
|
|
195
|
+
}
|
|
181
196
|
interface TickerLineOptions {
|
|
182
197
|
visible?: boolean;
|
|
183
198
|
style?: "solid" | "dotted" | "dashed";
|
|
@@ -199,6 +214,7 @@ interface ChartInstance {
|
|
|
199
214
|
onOrderAction: (handler: ((event: OrderActionEvent) => void) | null) => void;
|
|
200
215
|
onChartClick: (handler: ((event: ChartClickEvent) => void) | null) => void;
|
|
201
216
|
onCrosshairMove: (handler: ((event: CrosshairMoveEvent) => void) | null) => void;
|
|
217
|
+
onCrosshairPriceAction: (handler: ((event: CrosshairPriceActionEvent) => void) | null) => void;
|
|
202
218
|
zoomInX: (factor?: number) => void;
|
|
203
219
|
zoomOutX: (factor?: number) => void;
|
|
204
220
|
zoomInY: (factor?: number) => void;
|
|
@@ -222,4 +238,4 @@ interface OhlcDataPoint {
|
|
|
222
238
|
}
|
|
223
239
|
declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
|
|
224
240
|
|
|
225
|
-
export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type DashPatternOptions, type GridOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
|
|
241
|
+
export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
|
|
@@ -27,7 +27,17 @@ var DEFAULT_CROSSHAIR_OPTIONS = {
|
|
|
27
27
|
labelBorderRadius: 3,
|
|
28
28
|
labelBorderColor: "#94a3b8",
|
|
29
29
|
labelBorderWidth: 1,
|
|
30
|
-
labelBorderStyle: "solid"
|
|
30
|
+
labelBorderStyle: "solid",
|
|
31
|
+
showPriceActionButton: false,
|
|
32
|
+
priceActionButtonText: "+",
|
|
33
|
+
priceActionButtonSize: 20,
|
|
34
|
+
priceActionButtonGap: 6,
|
|
35
|
+
priceActionButtonBackgroundColor: "#1f2937",
|
|
36
|
+
priceActionButtonTextColor: "#e2e8f0",
|
|
37
|
+
priceActionButtonBorderColor: "#475569",
|
|
38
|
+
priceActionButtonBorderWidth: 1,
|
|
39
|
+
priceActionButtonRounded: true,
|
|
40
|
+
priceActionButtonBorderRadius: 3
|
|
31
41
|
};
|
|
32
42
|
var DEFAULT_WATERMARK_OPTIONS = {
|
|
33
43
|
visible: false,
|
|
@@ -139,10 +149,6 @@ var DEFAULT_OPTIONS = {
|
|
|
139
149
|
},
|
|
140
150
|
dashPatterns: DEFAULT_DASH_PATTERNS
|
|
141
151
|
};
|
|
142
|
-
var BRAND_LOGO_VIEWBOX_WIDTH = 190;
|
|
143
|
-
var BRAND_LOGO_VIEWBOX_HEIGHT = 186;
|
|
144
|
-
var BRAND_LOGO_PATH_A = "M0 93.0171V45.2271H48.9851V75.0332C48.9851 84.9545 57.0416 93.0001 66.9763 93.0001H94.9957V186H49.0531V110.984C49.0531 101.063 40.9965 93.0171 31.0619 93.0171H0Z";
|
|
145
|
-
var BRAND_LOGO_PATH_B = "M190 92.9915V140.782H141.015V110.975C141.015 101.054 132.958 93.0085 123.023 93.0085H95.0039V0H140.955V75.0162C140.955 84.9374 149.012 92.9831 158.946 92.9831H190V92.9915Z";
|
|
146
152
|
function createChart(element, options = {}) {
|
|
147
153
|
const mergedOptions = {
|
|
148
154
|
...DEFAULT_OPTIONS,
|
|
@@ -187,6 +193,8 @@ function createChart(element, options = {}) {
|
|
|
187
193
|
let orderActionHandler = null;
|
|
188
194
|
let chartClickHandler = null;
|
|
189
195
|
let crosshairMoveHandler = null;
|
|
196
|
+
let crosshairPriceActionHandler = null;
|
|
197
|
+
let crosshairPriceActionRegion = null;
|
|
190
198
|
let orderActionRegions = [];
|
|
191
199
|
let orderDragRegions = [];
|
|
192
200
|
let generatedPriceLineId = 1;
|
|
@@ -199,8 +207,6 @@ function createChart(element, options = {}) {
|
|
|
199
207
|
let yMaxOverride = null;
|
|
200
208
|
let autoYMin = null;
|
|
201
209
|
let autoYMax = null;
|
|
202
|
-
const brandLogoPathA = new Path2D(BRAND_LOGO_PATH_A);
|
|
203
|
-
const brandLogoPathB = new Path2D(BRAND_LOGO_PATH_B);
|
|
204
210
|
let watermarkImageSrc = null;
|
|
205
211
|
let watermarkImage = null;
|
|
206
212
|
let watermarkImageReady = false;
|
|
@@ -778,6 +784,7 @@ function createChart(element, options = {}) {
|
|
|
778
784
|
const draw = () => {
|
|
779
785
|
orderActionRegions = [];
|
|
780
786
|
orderDragRegions = [];
|
|
787
|
+
crosshairPriceActionRegion = null;
|
|
781
788
|
const pixelRatio = getPixelRatio();
|
|
782
789
|
canvas.style.width = `${width}px`;
|
|
783
790
|
canvas.style.height = `${height}px`;
|
|
@@ -1083,17 +1090,6 @@ function createChart(element, options = {}) {
|
|
|
1083
1090
|
});
|
|
1084
1091
|
drawText(timeLabel, x, chartBottom + 8, "center", "top", axis.textColor);
|
|
1085
1092
|
}
|
|
1086
|
-
const brandLogoWidth = 34;
|
|
1087
|
-
const brandLogoHeight = BRAND_LOGO_VIEWBOX_HEIGHT / BRAND_LOGO_VIEWBOX_WIDTH * brandLogoWidth;
|
|
1088
|
-
const brandLogoX = chartLeft + 16;
|
|
1089
|
-
const brandLogoY = chartBottom - brandLogoHeight - 10;
|
|
1090
|
-
ctx.save();
|
|
1091
|
-
ctx.translate(brandLogoX, brandLogoY);
|
|
1092
|
-
ctx.scale(brandLogoWidth / BRAND_LOGO_VIEWBOX_WIDTH, brandLogoHeight / BRAND_LOGO_VIEWBOX_HEIGHT);
|
|
1093
|
-
ctx.fillStyle = "#ffffff";
|
|
1094
|
-
ctx.fill(brandLogoPathA);
|
|
1095
|
-
ctx.fill(brandLogoPathB);
|
|
1096
|
-
ctx.restore();
|
|
1097
1093
|
if (crosshair.visible && crosshairPoint) {
|
|
1098
1094
|
const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
|
|
1099
1095
|
const cy = clamp(crosshairPoint.y, chartTop, chartBottom);
|
|
@@ -1130,6 +1126,39 @@ function createChart(element, options = {}) {
|
|
|
1130
1126
|
fillRoundedRect(Math.round(priceX), Math.round(priceY), priceWidth, labelHeight, labelRadius);
|
|
1131
1127
|
strokeCrosshairLabel(priceX, priceY, priceWidth);
|
|
1132
1128
|
drawText(priceText, priceX + labelPaddingX, priceY + labelHeight / 2, "left", "middle", labelTextColor);
|
|
1129
|
+
if (crosshair.showPriceActionButton) {
|
|
1130
|
+
const buttonSize = clamp(Math.round(crosshair.priceActionButtonSize), 12, 30);
|
|
1131
|
+
const buttonGap = Math.max(0, Math.round(crosshair.priceActionButtonGap));
|
|
1132
|
+
const buttonX = priceX - buttonGap - buttonSize;
|
|
1133
|
+
const buttonY = priceY + (labelHeight - buttonSize) / 2;
|
|
1134
|
+
const buttonRadius = crosshair.priceActionButtonRounded ? clamp(Math.round(crosshair.priceActionButtonBorderRadius), 0, buttonSize / 2) : 0;
|
|
1135
|
+
const buttonBorderWidth = Math.max(0, Math.round(crosshair.priceActionButtonBorderWidth));
|
|
1136
|
+
ctx.fillStyle = crosshair.priceActionButtonBackgroundColor;
|
|
1137
|
+
fillRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
|
|
1138
|
+
if (buttonBorderWidth > 0) {
|
|
1139
|
+
ctx.save();
|
|
1140
|
+
ctx.strokeStyle = crosshair.priceActionButtonBorderColor;
|
|
1141
|
+
ctx.lineWidth = buttonBorderWidth;
|
|
1142
|
+
ctx.setLineDash([]);
|
|
1143
|
+
strokeRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
|
|
1144
|
+
ctx.restore();
|
|
1145
|
+
}
|
|
1146
|
+
drawText(
|
|
1147
|
+
crosshair.priceActionButtonText,
|
|
1148
|
+
buttonX + buttonSize / 2,
|
|
1149
|
+
buttonY + buttonSize / 2,
|
|
1150
|
+
"center",
|
|
1151
|
+
"middle",
|
|
1152
|
+
crosshair.priceActionButtonTextColor
|
|
1153
|
+
);
|
|
1154
|
+
crosshairPriceActionRegion = {
|
|
1155
|
+
x: buttonX,
|
|
1156
|
+
y: buttonY,
|
|
1157
|
+
width: buttonSize,
|
|
1158
|
+
height: buttonSize,
|
|
1159
|
+
price: Number(hoverPrice.toFixed(mergedOptions.priceDecimals))
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1133
1162
|
}
|
|
1134
1163
|
if (crosshair.showTimeLabel) {
|
|
1135
1164
|
const ratio = clamp((cx - chartLeft) / chartWidth, 0, 1);
|
|
@@ -1292,6 +1321,16 @@ function createChart(element, options = {}) {
|
|
|
1292
1321
|
(region) => x >= region.x && x <= region.x + region.width && y >= region.y && y <= region.y + region.height
|
|
1293
1322
|
);
|
|
1294
1323
|
};
|
|
1324
|
+
const getCrosshairPriceActionRegion = (x, y) => {
|
|
1325
|
+
if (!crosshairPriceActionRegion) {
|
|
1326
|
+
return null;
|
|
1327
|
+
}
|
|
1328
|
+
const region = crosshairPriceActionRegion;
|
|
1329
|
+
if (x >= region.x && x <= region.x + region.width && y >= region.y && y <= region.y + region.height) {
|
|
1330
|
+
return region;
|
|
1331
|
+
}
|
|
1332
|
+
return null;
|
|
1333
|
+
};
|
|
1295
1334
|
const priceFromCanvasY = (y) => {
|
|
1296
1335
|
if (!drawState) {
|
|
1297
1336
|
return 0;
|
|
@@ -1357,6 +1396,15 @@ function createChart(element, options = {}) {
|
|
|
1357
1396
|
let activePointerId = null;
|
|
1358
1397
|
const onPointerDown = (event) => {
|
|
1359
1398
|
const point = getCanvasPoint(event);
|
|
1399
|
+
const crosshairButtonRegion = getCrosshairPriceActionRegion(point.x, point.y);
|
|
1400
|
+
if (crosshairButtonRegion) {
|
|
1401
|
+
crosshairPriceActionHandler?.({
|
|
1402
|
+
x: point.x,
|
|
1403
|
+
y: point.y,
|
|
1404
|
+
price: crosshairButtonRegion.price
|
|
1405
|
+
});
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1360
1408
|
const orderRegion = getOrderActionRegion(point.x, point.y);
|
|
1361
1409
|
if (orderRegion) {
|
|
1362
1410
|
if (orderRegion.draggable) {
|
|
@@ -1469,6 +1517,11 @@ function createChart(element, options = {}) {
|
|
|
1469
1517
|
return;
|
|
1470
1518
|
}
|
|
1471
1519
|
if (!isDragging || !dragMode) {
|
|
1520
|
+
const crosshairButtonRegion = getCrosshairPriceActionRegion(point.x, point.y);
|
|
1521
|
+
if (crosshairButtonRegion) {
|
|
1522
|
+
canvas.style.cursor = "pointer";
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1472
1525
|
const orderRegion = getOrderActionRegion(point.x, point.y);
|
|
1473
1526
|
if (orderRegion) {
|
|
1474
1527
|
canvas.style.cursor = orderRegion.draggable ? "ns-resize" : "pointer";
|
|
@@ -1722,6 +1775,9 @@ function createChart(element, options = {}) {
|
|
|
1722
1775
|
const onCrosshairMove = (handler) => {
|
|
1723
1776
|
crosshairMoveHandler = handler;
|
|
1724
1777
|
};
|
|
1778
|
+
const onCrosshairPriceAction = (handler) => {
|
|
1779
|
+
crosshairPriceActionHandler = handler;
|
|
1780
|
+
};
|
|
1725
1781
|
const setDoubleClickEnabled = (enabled) => {
|
|
1726
1782
|
doubleClickEnabled = enabled;
|
|
1727
1783
|
};
|
|
@@ -1751,6 +1807,7 @@ function createChart(element, options = {}) {
|
|
|
1751
1807
|
onOrderAction,
|
|
1752
1808
|
onChartClick,
|
|
1753
1809
|
onCrosshairMove,
|
|
1810
|
+
onCrosshairPriceAction,
|
|
1754
1811
|
zoomInX,
|
|
1755
1812
|
zoomOutX,
|
|
1756
1813
|
zoomInY,
|
package/dist/index.cjs
CHANGED
|
@@ -51,7 +51,17 @@ var DEFAULT_CROSSHAIR_OPTIONS = {
|
|
|
51
51
|
labelBorderRadius: 3,
|
|
52
52
|
labelBorderColor: "#94a3b8",
|
|
53
53
|
labelBorderWidth: 1,
|
|
54
|
-
labelBorderStyle: "solid"
|
|
54
|
+
labelBorderStyle: "solid",
|
|
55
|
+
showPriceActionButton: false,
|
|
56
|
+
priceActionButtonText: "+",
|
|
57
|
+
priceActionButtonSize: 20,
|
|
58
|
+
priceActionButtonGap: 6,
|
|
59
|
+
priceActionButtonBackgroundColor: "#1f2937",
|
|
60
|
+
priceActionButtonTextColor: "#e2e8f0",
|
|
61
|
+
priceActionButtonBorderColor: "#475569",
|
|
62
|
+
priceActionButtonBorderWidth: 1,
|
|
63
|
+
priceActionButtonRounded: true,
|
|
64
|
+
priceActionButtonBorderRadius: 3
|
|
55
65
|
};
|
|
56
66
|
var DEFAULT_WATERMARK_OPTIONS = {
|
|
57
67
|
visible: false,
|
|
@@ -163,10 +173,6 @@ var DEFAULT_OPTIONS = {
|
|
|
163
173
|
},
|
|
164
174
|
dashPatterns: DEFAULT_DASH_PATTERNS
|
|
165
175
|
};
|
|
166
|
-
var BRAND_LOGO_VIEWBOX_WIDTH = 190;
|
|
167
|
-
var BRAND_LOGO_VIEWBOX_HEIGHT = 186;
|
|
168
|
-
var BRAND_LOGO_PATH_A = "M0 93.0171V45.2271H48.9851V75.0332C48.9851 84.9545 57.0416 93.0001 66.9763 93.0001H94.9957V186H49.0531V110.984C49.0531 101.063 40.9965 93.0171 31.0619 93.0171H0Z";
|
|
169
|
-
var BRAND_LOGO_PATH_B = "M190 92.9915V140.782H141.015V110.975C141.015 101.054 132.958 93.0085 123.023 93.0085H95.0039V0H140.955V75.0162C140.955 84.9374 149.012 92.9831 158.946 92.9831H190V92.9915Z";
|
|
170
176
|
function createChart(element, options = {}) {
|
|
171
177
|
const mergedOptions = {
|
|
172
178
|
...DEFAULT_OPTIONS,
|
|
@@ -211,6 +217,8 @@ function createChart(element, options = {}) {
|
|
|
211
217
|
let orderActionHandler = null;
|
|
212
218
|
let chartClickHandler = null;
|
|
213
219
|
let crosshairMoveHandler = null;
|
|
220
|
+
let crosshairPriceActionHandler = null;
|
|
221
|
+
let crosshairPriceActionRegion = null;
|
|
214
222
|
let orderActionRegions = [];
|
|
215
223
|
let orderDragRegions = [];
|
|
216
224
|
let generatedPriceLineId = 1;
|
|
@@ -223,8 +231,6 @@ function createChart(element, options = {}) {
|
|
|
223
231
|
let yMaxOverride = null;
|
|
224
232
|
let autoYMin = null;
|
|
225
233
|
let autoYMax = null;
|
|
226
|
-
const brandLogoPathA = new Path2D(BRAND_LOGO_PATH_A);
|
|
227
|
-
const brandLogoPathB = new Path2D(BRAND_LOGO_PATH_B);
|
|
228
234
|
let watermarkImageSrc = null;
|
|
229
235
|
let watermarkImage = null;
|
|
230
236
|
let watermarkImageReady = false;
|
|
@@ -802,6 +808,7 @@ function createChart(element, options = {}) {
|
|
|
802
808
|
const draw = () => {
|
|
803
809
|
orderActionRegions = [];
|
|
804
810
|
orderDragRegions = [];
|
|
811
|
+
crosshairPriceActionRegion = null;
|
|
805
812
|
const pixelRatio = getPixelRatio();
|
|
806
813
|
canvas.style.width = `${width}px`;
|
|
807
814
|
canvas.style.height = `${height}px`;
|
|
@@ -1107,17 +1114,6 @@ function createChart(element, options = {}) {
|
|
|
1107
1114
|
});
|
|
1108
1115
|
drawText(timeLabel, x, chartBottom + 8, "center", "top", axis.textColor);
|
|
1109
1116
|
}
|
|
1110
|
-
const brandLogoWidth = 34;
|
|
1111
|
-
const brandLogoHeight = BRAND_LOGO_VIEWBOX_HEIGHT / BRAND_LOGO_VIEWBOX_WIDTH * brandLogoWidth;
|
|
1112
|
-
const brandLogoX = chartLeft + 16;
|
|
1113
|
-
const brandLogoY = chartBottom - brandLogoHeight - 10;
|
|
1114
|
-
ctx.save();
|
|
1115
|
-
ctx.translate(brandLogoX, brandLogoY);
|
|
1116
|
-
ctx.scale(brandLogoWidth / BRAND_LOGO_VIEWBOX_WIDTH, brandLogoHeight / BRAND_LOGO_VIEWBOX_HEIGHT);
|
|
1117
|
-
ctx.fillStyle = "#ffffff";
|
|
1118
|
-
ctx.fill(brandLogoPathA);
|
|
1119
|
-
ctx.fill(brandLogoPathB);
|
|
1120
|
-
ctx.restore();
|
|
1121
1117
|
if (crosshair.visible && crosshairPoint) {
|
|
1122
1118
|
const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
|
|
1123
1119
|
const cy = clamp(crosshairPoint.y, chartTop, chartBottom);
|
|
@@ -1154,6 +1150,39 @@ function createChart(element, options = {}) {
|
|
|
1154
1150
|
fillRoundedRect(Math.round(priceX), Math.round(priceY), priceWidth, labelHeight, labelRadius);
|
|
1155
1151
|
strokeCrosshairLabel(priceX, priceY, priceWidth);
|
|
1156
1152
|
drawText(priceText, priceX + labelPaddingX, priceY + labelHeight / 2, "left", "middle", labelTextColor);
|
|
1153
|
+
if (crosshair.showPriceActionButton) {
|
|
1154
|
+
const buttonSize = clamp(Math.round(crosshair.priceActionButtonSize), 12, 30);
|
|
1155
|
+
const buttonGap = Math.max(0, Math.round(crosshair.priceActionButtonGap));
|
|
1156
|
+
const buttonX = priceX - buttonGap - buttonSize;
|
|
1157
|
+
const buttonY = priceY + (labelHeight - buttonSize) / 2;
|
|
1158
|
+
const buttonRadius = crosshair.priceActionButtonRounded ? clamp(Math.round(crosshair.priceActionButtonBorderRadius), 0, buttonSize / 2) : 0;
|
|
1159
|
+
const buttonBorderWidth = Math.max(0, Math.round(crosshair.priceActionButtonBorderWidth));
|
|
1160
|
+
ctx.fillStyle = crosshair.priceActionButtonBackgroundColor;
|
|
1161
|
+
fillRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
|
|
1162
|
+
if (buttonBorderWidth > 0) {
|
|
1163
|
+
ctx.save();
|
|
1164
|
+
ctx.strokeStyle = crosshair.priceActionButtonBorderColor;
|
|
1165
|
+
ctx.lineWidth = buttonBorderWidth;
|
|
1166
|
+
ctx.setLineDash([]);
|
|
1167
|
+
strokeRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
|
|
1168
|
+
ctx.restore();
|
|
1169
|
+
}
|
|
1170
|
+
drawText(
|
|
1171
|
+
crosshair.priceActionButtonText,
|
|
1172
|
+
buttonX + buttonSize / 2,
|
|
1173
|
+
buttonY + buttonSize / 2,
|
|
1174
|
+
"center",
|
|
1175
|
+
"middle",
|
|
1176
|
+
crosshair.priceActionButtonTextColor
|
|
1177
|
+
);
|
|
1178
|
+
crosshairPriceActionRegion = {
|
|
1179
|
+
x: buttonX,
|
|
1180
|
+
y: buttonY,
|
|
1181
|
+
width: buttonSize,
|
|
1182
|
+
height: buttonSize,
|
|
1183
|
+
price: Number(hoverPrice.toFixed(mergedOptions.priceDecimals))
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1157
1186
|
}
|
|
1158
1187
|
if (crosshair.showTimeLabel) {
|
|
1159
1188
|
const ratio = clamp((cx - chartLeft) / chartWidth, 0, 1);
|
|
@@ -1316,6 +1345,16 @@ function createChart(element, options = {}) {
|
|
|
1316
1345
|
(region) => x >= region.x && x <= region.x + region.width && y >= region.y && y <= region.y + region.height
|
|
1317
1346
|
);
|
|
1318
1347
|
};
|
|
1348
|
+
const getCrosshairPriceActionRegion = (x, y) => {
|
|
1349
|
+
if (!crosshairPriceActionRegion) {
|
|
1350
|
+
return null;
|
|
1351
|
+
}
|
|
1352
|
+
const region = crosshairPriceActionRegion;
|
|
1353
|
+
if (x >= region.x && x <= region.x + region.width && y >= region.y && y <= region.y + region.height) {
|
|
1354
|
+
return region;
|
|
1355
|
+
}
|
|
1356
|
+
return null;
|
|
1357
|
+
};
|
|
1319
1358
|
const priceFromCanvasY = (y) => {
|
|
1320
1359
|
if (!drawState) {
|
|
1321
1360
|
return 0;
|
|
@@ -1381,6 +1420,15 @@ function createChart(element, options = {}) {
|
|
|
1381
1420
|
let activePointerId = null;
|
|
1382
1421
|
const onPointerDown = (event) => {
|
|
1383
1422
|
const point = getCanvasPoint(event);
|
|
1423
|
+
const crosshairButtonRegion = getCrosshairPriceActionRegion(point.x, point.y);
|
|
1424
|
+
if (crosshairButtonRegion) {
|
|
1425
|
+
crosshairPriceActionHandler?.({
|
|
1426
|
+
x: point.x,
|
|
1427
|
+
y: point.y,
|
|
1428
|
+
price: crosshairButtonRegion.price
|
|
1429
|
+
});
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1384
1432
|
const orderRegion = getOrderActionRegion(point.x, point.y);
|
|
1385
1433
|
if (orderRegion) {
|
|
1386
1434
|
if (orderRegion.draggable) {
|
|
@@ -1493,6 +1541,11 @@ function createChart(element, options = {}) {
|
|
|
1493
1541
|
return;
|
|
1494
1542
|
}
|
|
1495
1543
|
if (!isDragging || !dragMode) {
|
|
1544
|
+
const crosshairButtonRegion = getCrosshairPriceActionRegion(point.x, point.y);
|
|
1545
|
+
if (crosshairButtonRegion) {
|
|
1546
|
+
canvas.style.cursor = "pointer";
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1496
1549
|
const orderRegion = getOrderActionRegion(point.x, point.y);
|
|
1497
1550
|
if (orderRegion) {
|
|
1498
1551
|
canvas.style.cursor = orderRegion.draggable ? "ns-resize" : "pointer";
|
|
@@ -1746,6 +1799,9 @@ function createChart(element, options = {}) {
|
|
|
1746
1799
|
const onCrosshairMove = (handler) => {
|
|
1747
1800
|
crosshairMoveHandler = handler;
|
|
1748
1801
|
};
|
|
1802
|
+
const onCrosshairPriceAction = (handler) => {
|
|
1803
|
+
crosshairPriceActionHandler = handler;
|
|
1804
|
+
};
|
|
1749
1805
|
const setDoubleClickEnabled = (enabled) => {
|
|
1750
1806
|
doubleClickEnabled = enabled;
|
|
1751
1807
|
};
|
|
@@ -1775,6 +1831,7 @@ function createChart(element, options = {}) {
|
|
|
1775
1831
|
onOrderAction,
|
|
1776
1832
|
onChartClick,
|
|
1777
1833
|
onCrosshairMove,
|
|
1834
|
+
onCrosshairPriceAction,
|
|
1778
1835
|
zoomInX,
|
|
1779
1836
|
zoomOutX,
|
|
1780
1837
|
zoomInY,
|
package/dist/index.d.cts
CHANGED
|
@@ -71,6 +71,16 @@ interface CrosshairOptions {
|
|
|
71
71
|
labelBorderColor?: string;
|
|
72
72
|
labelBorderWidth?: number;
|
|
73
73
|
labelBorderStyle?: "solid" | "dotted" | "dashed";
|
|
74
|
+
showPriceActionButton?: boolean;
|
|
75
|
+
priceActionButtonText?: string;
|
|
76
|
+
priceActionButtonSize?: number;
|
|
77
|
+
priceActionButtonGap?: number;
|
|
78
|
+
priceActionButtonBackgroundColor?: string;
|
|
79
|
+
priceActionButtonTextColor?: string;
|
|
80
|
+
priceActionButtonBorderColor?: string;
|
|
81
|
+
priceActionButtonBorderWidth?: number;
|
|
82
|
+
priceActionButtonRounded?: boolean;
|
|
83
|
+
priceActionButtonBorderRadius?: number;
|
|
74
84
|
}
|
|
75
85
|
interface WatermarkOptions {
|
|
76
86
|
visible?: boolean;
|
|
@@ -178,6 +188,11 @@ interface CrosshairMoveEvent {
|
|
|
178
188
|
time?: string;
|
|
179
189
|
point?: OhlcDataPoint;
|
|
180
190
|
}
|
|
191
|
+
interface CrosshairPriceActionEvent {
|
|
192
|
+
x: number;
|
|
193
|
+
y: number;
|
|
194
|
+
price: number;
|
|
195
|
+
}
|
|
181
196
|
interface TickerLineOptions {
|
|
182
197
|
visible?: boolean;
|
|
183
198
|
style?: "solid" | "dotted" | "dashed";
|
|
@@ -199,6 +214,7 @@ interface ChartInstance {
|
|
|
199
214
|
onOrderAction: (handler: ((event: OrderActionEvent) => void) | null) => void;
|
|
200
215
|
onChartClick: (handler: ((event: ChartClickEvent) => void) | null) => void;
|
|
201
216
|
onCrosshairMove: (handler: ((event: CrosshairMoveEvent) => void) | null) => void;
|
|
217
|
+
onCrosshairPriceAction: (handler: ((event: CrosshairPriceActionEvent) => void) | null) => void;
|
|
202
218
|
zoomInX: (factor?: number) => void;
|
|
203
219
|
zoomOutX: (factor?: number) => void;
|
|
204
220
|
zoomInY: (factor?: number) => void;
|
|
@@ -222,4 +238,4 @@ interface OhlcDataPoint {
|
|
|
222
238
|
}
|
|
223
239
|
declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
|
|
224
240
|
|
|
225
|
-
export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type DashPatternOptions, type GridOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
|
|
241
|
+
export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
|
package/dist/index.d.ts
CHANGED
|
@@ -71,6 +71,16 @@ interface CrosshairOptions {
|
|
|
71
71
|
labelBorderColor?: string;
|
|
72
72
|
labelBorderWidth?: number;
|
|
73
73
|
labelBorderStyle?: "solid" | "dotted" | "dashed";
|
|
74
|
+
showPriceActionButton?: boolean;
|
|
75
|
+
priceActionButtonText?: string;
|
|
76
|
+
priceActionButtonSize?: number;
|
|
77
|
+
priceActionButtonGap?: number;
|
|
78
|
+
priceActionButtonBackgroundColor?: string;
|
|
79
|
+
priceActionButtonTextColor?: string;
|
|
80
|
+
priceActionButtonBorderColor?: string;
|
|
81
|
+
priceActionButtonBorderWidth?: number;
|
|
82
|
+
priceActionButtonRounded?: boolean;
|
|
83
|
+
priceActionButtonBorderRadius?: number;
|
|
74
84
|
}
|
|
75
85
|
interface WatermarkOptions {
|
|
76
86
|
visible?: boolean;
|
|
@@ -178,6 +188,11 @@ interface CrosshairMoveEvent {
|
|
|
178
188
|
time?: string;
|
|
179
189
|
point?: OhlcDataPoint;
|
|
180
190
|
}
|
|
191
|
+
interface CrosshairPriceActionEvent {
|
|
192
|
+
x: number;
|
|
193
|
+
y: number;
|
|
194
|
+
price: number;
|
|
195
|
+
}
|
|
181
196
|
interface TickerLineOptions {
|
|
182
197
|
visible?: boolean;
|
|
183
198
|
style?: "solid" | "dotted" | "dashed";
|
|
@@ -199,6 +214,7 @@ interface ChartInstance {
|
|
|
199
214
|
onOrderAction: (handler: ((event: OrderActionEvent) => void) | null) => void;
|
|
200
215
|
onChartClick: (handler: ((event: ChartClickEvent) => void) | null) => void;
|
|
201
216
|
onCrosshairMove: (handler: ((event: CrosshairMoveEvent) => void) | null) => void;
|
|
217
|
+
onCrosshairPriceAction: (handler: ((event: CrosshairPriceActionEvent) => void) | null) => void;
|
|
202
218
|
zoomInX: (factor?: number) => void;
|
|
203
219
|
zoomOutX: (factor?: number) => void;
|
|
204
220
|
zoomInY: (factor?: number) => void;
|
|
@@ -222,4 +238,4 @@ interface OhlcDataPoint {
|
|
|
222
238
|
}
|
|
223
239
|
declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
|
|
224
240
|
|
|
225
|
-
export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type DashPatternOptions, type GridOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
|
|
241
|
+
export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
|
package/dist/index.js
CHANGED
|
@@ -27,7 +27,17 @@ var DEFAULT_CROSSHAIR_OPTIONS = {
|
|
|
27
27
|
labelBorderRadius: 3,
|
|
28
28
|
labelBorderColor: "#94a3b8",
|
|
29
29
|
labelBorderWidth: 1,
|
|
30
|
-
labelBorderStyle: "solid"
|
|
30
|
+
labelBorderStyle: "solid",
|
|
31
|
+
showPriceActionButton: false,
|
|
32
|
+
priceActionButtonText: "+",
|
|
33
|
+
priceActionButtonSize: 20,
|
|
34
|
+
priceActionButtonGap: 6,
|
|
35
|
+
priceActionButtonBackgroundColor: "#1f2937",
|
|
36
|
+
priceActionButtonTextColor: "#e2e8f0",
|
|
37
|
+
priceActionButtonBorderColor: "#475569",
|
|
38
|
+
priceActionButtonBorderWidth: 1,
|
|
39
|
+
priceActionButtonRounded: true,
|
|
40
|
+
priceActionButtonBorderRadius: 3
|
|
31
41
|
};
|
|
32
42
|
var DEFAULT_WATERMARK_OPTIONS = {
|
|
33
43
|
visible: false,
|
|
@@ -139,10 +149,6 @@ var DEFAULT_OPTIONS = {
|
|
|
139
149
|
},
|
|
140
150
|
dashPatterns: DEFAULT_DASH_PATTERNS
|
|
141
151
|
};
|
|
142
|
-
var BRAND_LOGO_VIEWBOX_WIDTH = 190;
|
|
143
|
-
var BRAND_LOGO_VIEWBOX_HEIGHT = 186;
|
|
144
|
-
var BRAND_LOGO_PATH_A = "M0 93.0171V45.2271H48.9851V75.0332C48.9851 84.9545 57.0416 93.0001 66.9763 93.0001H94.9957V186H49.0531V110.984C49.0531 101.063 40.9965 93.0171 31.0619 93.0171H0Z";
|
|
145
|
-
var BRAND_LOGO_PATH_B = "M190 92.9915V140.782H141.015V110.975C141.015 101.054 132.958 93.0085 123.023 93.0085H95.0039V0H140.955V75.0162C140.955 84.9374 149.012 92.9831 158.946 92.9831H190V92.9915Z";
|
|
146
152
|
function createChart(element, options = {}) {
|
|
147
153
|
const mergedOptions = {
|
|
148
154
|
...DEFAULT_OPTIONS,
|
|
@@ -187,6 +193,8 @@ function createChart(element, options = {}) {
|
|
|
187
193
|
let orderActionHandler = null;
|
|
188
194
|
let chartClickHandler = null;
|
|
189
195
|
let crosshairMoveHandler = null;
|
|
196
|
+
let crosshairPriceActionHandler = null;
|
|
197
|
+
let crosshairPriceActionRegion = null;
|
|
190
198
|
let orderActionRegions = [];
|
|
191
199
|
let orderDragRegions = [];
|
|
192
200
|
let generatedPriceLineId = 1;
|
|
@@ -199,8 +207,6 @@ function createChart(element, options = {}) {
|
|
|
199
207
|
let yMaxOverride = null;
|
|
200
208
|
let autoYMin = null;
|
|
201
209
|
let autoYMax = null;
|
|
202
|
-
const brandLogoPathA = new Path2D(BRAND_LOGO_PATH_A);
|
|
203
|
-
const brandLogoPathB = new Path2D(BRAND_LOGO_PATH_B);
|
|
204
210
|
let watermarkImageSrc = null;
|
|
205
211
|
let watermarkImage = null;
|
|
206
212
|
let watermarkImageReady = false;
|
|
@@ -778,6 +784,7 @@ function createChart(element, options = {}) {
|
|
|
778
784
|
const draw = () => {
|
|
779
785
|
orderActionRegions = [];
|
|
780
786
|
orderDragRegions = [];
|
|
787
|
+
crosshairPriceActionRegion = null;
|
|
781
788
|
const pixelRatio = getPixelRatio();
|
|
782
789
|
canvas.style.width = `${width}px`;
|
|
783
790
|
canvas.style.height = `${height}px`;
|
|
@@ -1083,17 +1090,6 @@ function createChart(element, options = {}) {
|
|
|
1083
1090
|
});
|
|
1084
1091
|
drawText(timeLabel, x, chartBottom + 8, "center", "top", axis.textColor);
|
|
1085
1092
|
}
|
|
1086
|
-
const brandLogoWidth = 34;
|
|
1087
|
-
const brandLogoHeight = BRAND_LOGO_VIEWBOX_HEIGHT / BRAND_LOGO_VIEWBOX_WIDTH * brandLogoWidth;
|
|
1088
|
-
const brandLogoX = chartLeft + 16;
|
|
1089
|
-
const brandLogoY = chartBottom - brandLogoHeight - 10;
|
|
1090
|
-
ctx.save();
|
|
1091
|
-
ctx.translate(brandLogoX, brandLogoY);
|
|
1092
|
-
ctx.scale(brandLogoWidth / BRAND_LOGO_VIEWBOX_WIDTH, brandLogoHeight / BRAND_LOGO_VIEWBOX_HEIGHT);
|
|
1093
|
-
ctx.fillStyle = "#ffffff";
|
|
1094
|
-
ctx.fill(brandLogoPathA);
|
|
1095
|
-
ctx.fill(brandLogoPathB);
|
|
1096
|
-
ctx.restore();
|
|
1097
1093
|
if (crosshair.visible && crosshairPoint) {
|
|
1098
1094
|
const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
|
|
1099
1095
|
const cy = clamp(crosshairPoint.y, chartTop, chartBottom);
|
|
@@ -1130,6 +1126,39 @@ function createChart(element, options = {}) {
|
|
|
1130
1126
|
fillRoundedRect(Math.round(priceX), Math.round(priceY), priceWidth, labelHeight, labelRadius);
|
|
1131
1127
|
strokeCrosshairLabel(priceX, priceY, priceWidth);
|
|
1132
1128
|
drawText(priceText, priceX + labelPaddingX, priceY + labelHeight / 2, "left", "middle", labelTextColor);
|
|
1129
|
+
if (crosshair.showPriceActionButton) {
|
|
1130
|
+
const buttonSize = clamp(Math.round(crosshair.priceActionButtonSize), 12, 30);
|
|
1131
|
+
const buttonGap = Math.max(0, Math.round(crosshair.priceActionButtonGap));
|
|
1132
|
+
const buttonX = priceX - buttonGap - buttonSize;
|
|
1133
|
+
const buttonY = priceY + (labelHeight - buttonSize) / 2;
|
|
1134
|
+
const buttonRadius = crosshair.priceActionButtonRounded ? clamp(Math.round(crosshair.priceActionButtonBorderRadius), 0, buttonSize / 2) : 0;
|
|
1135
|
+
const buttonBorderWidth = Math.max(0, Math.round(crosshair.priceActionButtonBorderWidth));
|
|
1136
|
+
ctx.fillStyle = crosshair.priceActionButtonBackgroundColor;
|
|
1137
|
+
fillRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
|
|
1138
|
+
if (buttonBorderWidth > 0) {
|
|
1139
|
+
ctx.save();
|
|
1140
|
+
ctx.strokeStyle = crosshair.priceActionButtonBorderColor;
|
|
1141
|
+
ctx.lineWidth = buttonBorderWidth;
|
|
1142
|
+
ctx.setLineDash([]);
|
|
1143
|
+
strokeRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
|
|
1144
|
+
ctx.restore();
|
|
1145
|
+
}
|
|
1146
|
+
drawText(
|
|
1147
|
+
crosshair.priceActionButtonText,
|
|
1148
|
+
buttonX + buttonSize / 2,
|
|
1149
|
+
buttonY + buttonSize / 2,
|
|
1150
|
+
"center",
|
|
1151
|
+
"middle",
|
|
1152
|
+
crosshair.priceActionButtonTextColor
|
|
1153
|
+
);
|
|
1154
|
+
crosshairPriceActionRegion = {
|
|
1155
|
+
x: buttonX,
|
|
1156
|
+
y: buttonY,
|
|
1157
|
+
width: buttonSize,
|
|
1158
|
+
height: buttonSize,
|
|
1159
|
+
price: Number(hoverPrice.toFixed(mergedOptions.priceDecimals))
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1133
1162
|
}
|
|
1134
1163
|
if (crosshair.showTimeLabel) {
|
|
1135
1164
|
const ratio = clamp((cx - chartLeft) / chartWidth, 0, 1);
|
|
@@ -1292,6 +1321,16 @@ function createChart(element, options = {}) {
|
|
|
1292
1321
|
(region) => x >= region.x && x <= region.x + region.width && y >= region.y && y <= region.y + region.height
|
|
1293
1322
|
);
|
|
1294
1323
|
};
|
|
1324
|
+
const getCrosshairPriceActionRegion = (x, y) => {
|
|
1325
|
+
if (!crosshairPriceActionRegion) {
|
|
1326
|
+
return null;
|
|
1327
|
+
}
|
|
1328
|
+
const region = crosshairPriceActionRegion;
|
|
1329
|
+
if (x >= region.x && x <= region.x + region.width && y >= region.y && y <= region.y + region.height) {
|
|
1330
|
+
return region;
|
|
1331
|
+
}
|
|
1332
|
+
return null;
|
|
1333
|
+
};
|
|
1295
1334
|
const priceFromCanvasY = (y) => {
|
|
1296
1335
|
if (!drawState) {
|
|
1297
1336
|
return 0;
|
|
@@ -1357,6 +1396,15 @@ function createChart(element, options = {}) {
|
|
|
1357
1396
|
let activePointerId = null;
|
|
1358
1397
|
const onPointerDown = (event) => {
|
|
1359
1398
|
const point = getCanvasPoint(event);
|
|
1399
|
+
const crosshairButtonRegion = getCrosshairPriceActionRegion(point.x, point.y);
|
|
1400
|
+
if (crosshairButtonRegion) {
|
|
1401
|
+
crosshairPriceActionHandler?.({
|
|
1402
|
+
x: point.x,
|
|
1403
|
+
y: point.y,
|
|
1404
|
+
price: crosshairButtonRegion.price
|
|
1405
|
+
});
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1360
1408
|
const orderRegion = getOrderActionRegion(point.x, point.y);
|
|
1361
1409
|
if (orderRegion) {
|
|
1362
1410
|
if (orderRegion.draggable) {
|
|
@@ -1469,6 +1517,11 @@ function createChart(element, options = {}) {
|
|
|
1469
1517
|
return;
|
|
1470
1518
|
}
|
|
1471
1519
|
if (!isDragging || !dragMode) {
|
|
1520
|
+
const crosshairButtonRegion = getCrosshairPriceActionRegion(point.x, point.y);
|
|
1521
|
+
if (crosshairButtonRegion) {
|
|
1522
|
+
canvas.style.cursor = "pointer";
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1472
1525
|
const orderRegion = getOrderActionRegion(point.x, point.y);
|
|
1473
1526
|
if (orderRegion) {
|
|
1474
1527
|
canvas.style.cursor = orderRegion.draggable ? "ns-resize" : "pointer";
|
|
@@ -1722,6 +1775,9 @@ function createChart(element, options = {}) {
|
|
|
1722
1775
|
const onCrosshairMove = (handler) => {
|
|
1723
1776
|
crosshairMoveHandler = handler;
|
|
1724
1777
|
};
|
|
1778
|
+
const onCrosshairPriceAction = (handler) => {
|
|
1779
|
+
crosshairPriceActionHandler = handler;
|
|
1780
|
+
};
|
|
1725
1781
|
const setDoubleClickEnabled = (enabled) => {
|
|
1726
1782
|
doubleClickEnabled = enabled;
|
|
1727
1783
|
};
|
|
@@ -1751,6 +1807,7 @@ function createChart(element, options = {}) {
|
|
|
1751
1807
|
onOrderAction,
|
|
1752
1808
|
onChartClick,
|
|
1753
1809
|
onCrosshairMove,
|
|
1810
|
+
onCrosshairPriceAction,
|
|
1754
1811
|
zoomInX,
|
|
1755
1812
|
zoomOutX,
|
|
1756
1813
|
zoomInY,
|
package/docs/API.md
CHANGED
|
@@ -94,6 +94,16 @@ Top-level options:
|
|
|
94
94
|
- `labelBorderColor` (default `#94a3b8`)
|
|
95
95
|
- `labelBorderWidth` (default `1`)
|
|
96
96
|
- `labelBorderStyle` (`"solid" | "dotted" | "dashed"`, default `"solid"`)
|
|
97
|
+
- `showPriceActionButton` (default `false`)
|
|
98
|
+
- `priceActionButtonText` (default `"+"`)
|
|
99
|
+
- `priceActionButtonSize` (default `20`)
|
|
100
|
+
- `priceActionButtonGap` (default `6`)
|
|
101
|
+
- `priceActionButtonBackgroundColor` (default `#1f2937`)
|
|
102
|
+
- `priceActionButtonTextColor` (default `#e2e8f0`)
|
|
103
|
+
- `priceActionButtonBorderColor` (default `#475569`)
|
|
104
|
+
- `priceActionButtonBorderWidth` (default `1`)
|
|
105
|
+
- `priceActionButtonRounded` (default `true`; set `false` for square corners)
|
|
106
|
+
- `priceActionButtonBorderRadius` (default `3`)
|
|
97
107
|
|
|
98
108
|
### `WatermarkOptions`
|
|
99
109
|
|
|
@@ -243,6 +253,7 @@ Connector/fill visuals:
|
|
|
243
253
|
- `onOrderAction(handler: ((event: OrderActionEvent) => void) | null): void`
|
|
244
254
|
- `onChartClick(handler: ((event: ChartClickEvent) => void) | null): void`
|
|
245
255
|
- `onCrosshairMove(handler: ((event: CrosshairMoveEvent) => void) | null): void`
|
|
256
|
+
- `onCrosshairPriceAction(handler: ((event: CrosshairPriceActionEvent) => void) | null): void`
|
|
246
257
|
- `zoomInX(factor?: number): void` (default factor `1.25`)
|
|
247
258
|
- `zoomOutX(factor?: number): void` (default factor `1.25`)
|
|
248
259
|
- `zoomInY(factor?: number): void` (default factor `1.25`)
|
package/docs/EVENTS.md
CHANGED
|
@@ -109,6 +109,33 @@ type CrosshairMoveEvent = {
|
|
|
109
109
|
|
|
110
110
|
---
|
|
111
111
|
|
|
112
|
+
## `onCrosshairPriceAction`
|
|
113
|
+
|
|
114
|
+
Register:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
chart.onCrosshairPriceAction((event) => {
|
|
118
|
+
// frontend action (open order ticket, quick action, etc)
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Payload type:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
type CrosshairPriceActionEvent = {
|
|
126
|
+
x: number;
|
|
127
|
+
y: number;
|
|
128
|
+
price: number;
|
|
129
|
+
};
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Notes
|
|
133
|
+
|
|
134
|
+
- Fires when the optional crosshair price action button (default text `"+"`) is clicked.
|
|
135
|
+
- Enable button rendering via `crosshair.showPriceActionButton: true`.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
112
139
|
## Double-click integration
|
|
113
140
|
|
|
114
141
|
Set behavior:
|
package/docs/RECIPES.md
CHANGED
|
@@ -63,6 +63,35 @@ const chart = createChart(root, {
|
|
|
63
63
|
});
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
## Add crosshair "+" action button
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
const chart = createChart(root, {
|
|
70
|
+
crosshair: {
|
|
71
|
+
showPriceActionButton: true,
|
|
72
|
+
priceActionButtonText: "+",
|
|
73
|
+
priceActionButtonGap: 6
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
chart.onCrosshairPriceAction((event) => {
|
|
78
|
+
// Handle in app/frontend (place order modal, quick ticket, etc.)
|
|
79
|
+
console.log("crosshair + clicked at price", event.price);
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Square style example:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
const chart = createChart(root, {
|
|
87
|
+
crosshair: {
|
|
88
|
+
showPriceActionButton: true,
|
|
89
|
+
priceActionButtonRounded: false,
|
|
90
|
+
priceActionButtonBorderRadius: 0
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
66
95
|
## Add a draggable pending limit line
|
|
67
96
|
|
|
68
97
|
```ts
|