hyperprop-charting-library 0.1.10 → 0.1.12
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 +102 -27
- package/dist/hyperprop-charting-library.d.ts +11 -0
- package/dist/hyperprop-charting-library.js +102 -27
- package/dist/index.cjs +102 -27
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +102 -27
- package/docs/API.md +31 -8
- package/docs/assets/logo-white.svg +4 -0
- package/package.json +1 -1
|
@@ -118,6 +118,9 @@ var DEFAULT_OPTIONS = {
|
|
|
118
118
|
priceDecimals: 2,
|
|
119
119
|
initialViewport: "latest",
|
|
120
120
|
initialVisibleBars: 60,
|
|
121
|
+
minVisibleBars: 5,
|
|
122
|
+
maxVisibleBars: 2e4,
|
|
123
|
+
maxPanBars: 1e6,
|
|
121
124
|
rightEdgePaddingBars: 2,
|
|
122
125
|
preserveViewportOnDataUpdate: true,
|
|
123
126
|
upColor: "#2fb171",
|
|
@@ -146,6 +149,10 @@ var DEFAULT_OPTIONS = {
|
|
|
146
149
|
labelBorderRadius: 3
|
|
147
150
|
}
|
|
148
151
|
};
|
|
152
|
+
var BRAND_LOGO_VIEWBOX_WIDTH = 190;
|
|
153
|
+
var BRAND_LOGO_VIEWBOX_HEIGHT = 186;
|
|
154
|
+
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";
|
|
155
|
+
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";
|
|
149
156
|
function createChart(element, options = {}) {
|
|
150
157
|
const mergedOptions = {
|
|
151
158
|
...DEFAULT_OPTIONS,
|
|
@@ -198,6 +205,8 @@ function createChart(element, options = {}) {
|
|
|
198
205
|
let yMaxOverride = null;
|
|
199
206
|
let autoYMin = null;
|
|
200
207
|
let autoYMax = null;
|
|
208
|
+
const brandLogoPathA = new Path2D(BRAND_LOGO_PATH_A);
|
|
209
|
+
const brandLogoPathB = new Path2D(BRAND_LOGO_PATH_B);
|
|
201
210
|
let watermarkImageSrc = null;
|
|
202
211
|
let watermarkImage = null;
|
|
203
212
|
let watermarkImageReady = false;
|
|
@@ -218,7 +227,9 @@ function createChart(element, options = {}) {
|
|
|
218
227
|
element.innerHTML = "";
|
|
219
228
|
element.appendChild(canvas);
|
|
220
229
|
const margin = { top: 16, right: 72, bottom: 34, left: 12 };
|
|
221
|
-
const
|
|
230
|
+
const minVisibleBars = Math.max(1, Math.floor(mergedOptions.minVisibleBars));
|
|
231
|
+
const maxVisibleBars = Math.max(minVisibleBars, Math.floor(mergedOptions.maxVisibleBars));
|
|
232
|
+
const maxPanBars = Math.max(0, Math.floor(mergedOptions.maxPanBars));
|
|
222
233
|
const rightEdgePaddingBars = Math.max(0, mergedOptions.rightEdgePaddingBars);
|
|
223
234
|
const getPixelRatio = () => {
|
|
224
235
|
if (typeof window === "undefined") {
|
|
@@ -260,8 +271,8 @@ function createChart(element, options = {}) {
|
|
|
260
271
|
xSpan = 60;
|
|
261
272
|
return;
|
|
262
273
|
}
|
|
263
|
-
const minSpan =
|
|
264
|
-
const maxSpan = Math.max(minSpan, count + maxPanBars * 2);
|
|
274
|
+
const minSpan = minVisibleBars;
|
|
275
|
+
const maxSpan = Math.min(maxVisibleBars, Math.max(minSpan, count + maxPanBars * 2));
|
|
265
276
|
xSpan = clamp(xSpan, minSpan, maxSpan);
|
|
266
277
|
xCenter = clamp(xCenter, -maxPanBars, count + maxPanBars);
|
|
267
278
|
};
|
|
@@ -272,8 +283,9 @@ function createChart(element, options = {}) {
|
|
|
272
283
|
xSpan = 60;
|
|
273
284
|
return;
|
|
274
285
|
}
|
|
275
|
-
const
|
|
276
|
-
|
|
286
|
+
const maxSpan = Math.min(maxVisibleBars, Math.max(minVisibleBars, count + maxPanBars * 2));
|
|
287
|
+
const requestedVisibleBars = clamp(Math.floor(mergedOptions.initialVisibleBars), minVisibleBars, maxSpan);
|
|
288
|
+
xSpan = requestedVisibleBars;
|
|
277
289
|
if (mergedOptions.initialViewport === "center") {
|
|
278
290
|
xCenter = count / 2;
|
|
279
291
|
} else {
|
|
@@ -1055,6 +1067,17 @@ function createChart(element, options = {}) {
|
|
|
1055
1067
|
});
|
|
1056
1068
|
drawText(timeLabel, x, chartBottom + 8, "center", "top", axis.textColor);
|
|
1057
1069
|
}
|
|
1070
|
+
const brandLogoWidth = 34;
|
|
1071
|
+
const brandLogoHeight = BRAND_LOGO_VIEWBOX_HEIGHT / BRAND_LOGO_VIEWBOX_WIDTH * brandLogoWidth;
|
|
1072
|
+
const brandLogoX = chartLeft + 16;
|
|
1073
|
+
const brandLogoY = chartBottom - brandLogoHeight - 10;
|
|
1074
|
+
ctx.save();
|
|
1075
|
+
ctx.translate(brandLogoX, brandLogoY);
|
|
1076
|
+
ctx.scale(brandLogoWidth / BRAND_LOGO_VIEWBOX_WIDTH, brandLogoHeight / BRAND_LOGO_VIEWBOX_HEIGHT);
|
|
1077
|
+
ctx.fillStyle = "#ffffff";
|
|
1078
|
+
ctx.fill(brandLogoPathA);
|
|
1079
|
+
ctx.fill(brandLogoPathB);
|
|
1080
|
+
ctx.restore();
|
|
1058
1081
|
if (crosshair.visible && crosshairPoint) {
|
|
1059
1082
|
const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
|
|
1060
1083
|
const cy = clamp(crosshairPoint.y, chartTop, chartBottom);
|
|
@@ -1115,8 +1138,8 @@ function createChart(element, options = {}) {
|
|
|
1115
1138
|
if (!drawState || data.length === 0) {
|
|
1116
1139
|
return;
|
|
1117
1140
|
}
|
|
1118
|
-
const minSpan =
|
|
1119
|
-
const maxSpan = Math.max(minSpan, data.length + maxPanBars * 2);
|
|
1141
|
+
const minSpan = minVisibleBars;
|
|
1142
|
+
const maxSpan = Math.min(maxVisibleBars, Math.max(minSpan, data.length + maxPanBars * 2));
|
|
1120
1143
|
const nextSpan = clamp(xSpan * factor, minSpan, maxSpan);
|
|
1121
1144
|
const anchorRatio = clamp((anchorX - drawState.chartLeft) / drawState.chartWidth, 0, 1);
|
|
1122
1145
|
const anchorIndex = drawState.xStart + anchorRatio * xSpan;
|
|
@@ -1130,8 +1153,8 @@ function createChart(element, options = {}) {
|
|
|
1130
1153
|
if (!drawState || data.length === 0) {
|
|
1131
1154
|
return;
|
|
1132
1155
|
}
|
|
1133
|
-
const minSpan =
|
|
1134
|
-
const maxSpan = Math.max(minSpan, data.length + maxPanBars * 2);
|
|
1156
|
+
const minSpan = minVisibleBars;
|
|
1157
|
+
const maxSpan = Math.min(maxVisibleBars, Math.max(minSpan, data.length + maxPanBars * 2));
|
|
1135
1158
|
const nextSpan = clamp(xSpan * factor, minSpan, maxSpan);
|
|
1136
1159
|
const rightEdgeIndex = data.length + rightEdgePaddingBars;
|
|
1137
1160
|
const nextStart = rightEdgeIndex - nextSpan;
|
|
@@ -1180,6 +1203,64 @@ function createChart(element, options = {}) {
|
|
|
1180
1203
|
}
|
|
1181
1204
|
draw();
|
|
1182
1205
|
};
|
|
1206
|
+
const resetYViewport = () => {
|
|
1207
|
+
yMinOverride = null;
|
|
1208
|
+
yMaxOverride = null;
|
|
1209
|
+
autoYMin = null;
|
|
1210
|
+
autoYMax = null;
|
|
1211
|
+
};
|
|
1212
|
+
const zoomInX = (factor = 1.25) => {
|
|
1213
|
+
if (!drawState) {
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
zoomX(1 / Math.max(1.01, factor), drawState.chartLeft + drawState.chartWidth / 2);
|
|
1217
|
+
};
|
|
1218
|
+
const zoomOutX = (factor = 1.25) => {
|
|
1219
|
+
if (!drawState) {
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
zoomX(Math.max(1.01, factor), drawState.chartLeft + drawState.chartWidth / 2);
|
|
1223
|
+
};
|
|
1224
|
+
const zoomInY = (factor = 1.25) => {
|
|
1225
|
+
if (!drawState) {
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
zoomY(1 / Math.max(1.01, factor), drawState.chartTop + drawState.chartHeight / 2);
|
|
1229
|
+
};
|
|
1230
|
+
const zoomOutY = (factor = 1.25) => {
|
|
1231
|
+
if (!drawState) {
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
zoomY(Math.max(1.01, factor), drawState.chartTop + drawState.chartHeight / 2);
|
|
1235
|
+
};
|
|
1236
|
+
const panX = (bars) => {
|
|
1237
|
+
if (!Number.isFinite(bars) || bars === 0) {
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
xCenter += bars;
|
|
1241
|
+
clampXViewport();
|
|
1242
|
+
draw();
|
|
1243
|
+
};
|
|
1244
|
+
const panY = (priceDelta) => {
|
|
1245
|
+
if (!drawState || !Number.isFinite(priceDelta) || priceDelta === 0) {
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
const currentMin = yMinOverride ?? drawState.yMin;
|
|
1249
|
+
const currentMax = yMaxOverride ?? drawState.yMax;
|
|
1250
|
+
const clamped = clampYRange(currentMin + priceDelta, currentMax + priceDelta);
|
|
1251
|
+
yMinOverride = clamped.min;
|
|
1252
|
+
yMaxOverride = clamped.max;
|
|
1253
|
+
draw();
|
|
1254
|
+
};
|
|
1255
|
+
const fitContent = () => {
|
|
1256
|
+
fitXViewport();
|
|
1257
|
+
draw();
|
|
1258
|
+
};
|
|
1259
|
+
const resetViewport = () => {
|
|
1260
|
+
fitXViewport();
|
|
1261
|
+
resetYViewport();
|
|
1262
|
+
draw();
|
|
1263
|
+
};
|
|
1183
1264
|
const getCanvasPoint = (event) => {
|
|
1184
1265
|
const rect = canvas.getBoundingClientRect();
|
|
1185
1266
|
return {
|
|
@@ -1523,19 +1604,11 @@ function createChart(element, options = {}) {
|
|
|
1523
1604
|
return;
|
|
1524
1605
|
}
|
|
1525
1606
|
if (region === "y-axis") {
|
|
1526
|
-
|
|
1527
|
-
yMaxOverride = null;
|
|
1528
|
-
autoYMin = null;
|
|
1529
|
-
autoYMax = null;
|
|
1607
|
+
resetYViewport();
|
|
1530
1608
|
draw();
|
|
1531
1609
|
return;
|
|
1532
1610
|
}
|
|
1533
|
-
|
|
1534
|
-
yMinOverride = null;
|
|
1535
|
-
yMaxOverride = null;
|
|
1536
|
-
autoYMin = null;
|
|
1537
|
-
autoYMax = null;
|
|
1538
|
-
draw();
|
|
1611
|
+
resetViewport();
|
|
1539
1612
|
};
|
|
1540
1613
|
canvas.addEventListener("pointerdown", onPointerDown);
|
|
1541
1614
|
canvas.addEventListener("pointermove", onPointerMove);
|
|
@@ -1559,19 +1632,13 @@ function createChart(element, options = {}) {
|
|
|
1559
1632
|
if (data.length === 0) {
|
|
1560
1633
|
xCenter = 0;
|
|
1561
1634
|
xSpan = 60;
|
|
1562
|
-
|
|
1563
|
-
yMaxOverride = null;
|
|
1564
|
-
autoYMin = null;
|
|
1565
|
-
autoYMax = null;
|
|
1635
|
+
resetYViewport();
|
|
1566
1636
|
draw();
|
|
1567
1637
|
return;
|
|
1568
1638
|
}
|
|
1569
1639
|
if (!hadData) {
|
|
1570
1640
|
fitXViewport();
|
|
1571
|
-
|
|
1572
|
-
yMaxOverride = null;
|
|
1573
|
-
autoYMin = null;
|
|
1574
|
-
autoYMax = null;
|
|
1641
|
+
resetYViewport();
|
|
1575
1642
|
} else {
|
|
1576
1643
|
if (mergedOptions.preserveViewportOnDataUpdate) {
|
|
1577
1644
|
clampXViewport();
|
|
@@ -1670,6 +1737,14 @@ function createChart(element, options = {}) {
|
|
|
1670
1737
|
onOrderAction,
|
|
1671
1738
|
onChartClick,
|
|
1672
1739
|
onCrosshairMove,
|
|
1740
|
+
zoomInX,
|
|
1741
|
+
zoomOutX,
|
|
1742
|
+
zoomInY,
|
|
1743
|
+
zoomOutY,
|
|
1744
|
+
panX,
|
|
1745
|
+
panY,
|
|
1746
|
+
resetViewport,
|
|
1747
|
+
fitContent,
|
|
1673
1748
|
setDoubleClickEnabled,
|
|
1674
1749
|
setDoubleClickAction,
|
|
1675
1750
|
resize,
|
|
@@ -7,6 +7,9 @@ interface ChartOptions {
|
|
|
7
7
|
priceDecimals?: number;
|
|
8
8
|
initialViewport?: "latest" | "center";
|
|
9
9
|
initialVisibleBars?: number;
|
|
10
|
+
minVisibleBars?: number;
|
|
11
|
+
maxVisibleBars?: number;
|
|
12
|
+
maxPanBars?: number;
|
|
10
13
|
rightEdgePaddingBars?: number;
|
|
11
14
|
preserveViewportOnDataUpdate?: boolean;
|
|
12
15
|
upColor?: string;
|
|
@@ -182,6 +185,14 @@ interface ChartInstance {
|
|
|
182
185
|
onOrderAction: (handler: ((event: OrderActionEvent) => void) | null) => void;
|
|
183
186
|
onChartClick: (handler: ((event: ChartClickEvent) => void) | null) => void;
|
|
184
187
|
onCrosshairMove: (handler: ((event: CrosshairMoveEvent) => void) | null) => void;
|
|
188
|
+
zoomInX: (factor?: number) => void;
|
|
189
|
+
zoomOutX: (factor?: number) => void;
|
|
190
|
+
zoomInY: (factor?: number) => void;
|
|
191
|
+
zoomOutY: (factor?: number) => void;
|
|
192
|
+
panX: (bars: number) => void;
|
|
193
|
+
panY: (priceDelta: number) => void;
|
|
194
|
+
resetViewport: () => void;
|
|
195
|
+
fitContent: () => void;
|
|
185
196
|
setDoubleClickEnabled: (enabled: boolean) => void;
|
|
186
197
|
setDoubleClickAction: (action: "reset" | "placeLimitOrder") => void;
|
|
187
198
|
resize: (width?: number, height?: number) => void;
|
|
@@ -94,6 +94,9 @@ var DEFAULT_OPTIONS = {
|
|
|
94
94
|
priceDecimals: 2,
|
|
95
95
|
initialViewport: "latest",
|
|
96
96
|
initialVisibleBars: 60,
|
|
97
|
+
minVisibleBars: 5,
|
|
98
|
+
maxVisibleBars: 2e4,
|
|
99
|
+
maxPanBars: 1e6,
|
|
97
100
|
rightEdgePaddingBars: 2,
|
|
98
101
|
preserveViewportOnDataUpdate: true,
|
|
99
102
|
upColor: "#2fb171",
|
|
@@ -122,6 +125,10 @@ var DEFAULT_OPTIONS = {
|
|
|
122
125
|
labelBorderRadius: 3
|
|
123
126
|
}
|
|
124
127
|
};
|
|
128
|
+
var BRAND_LOGO_VIEWBOX_WIDTH = 190;
|
|
129
|
+
var BRAND_LOGO_VIEWBOX_HEIGHT = 186;
|
|
130
|
+
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";
|
|
131
|
+
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";
|
|
125
132
|
function createChart(element, options = {}) {
|
|
126
133
|
const mergedOptions = {
|
|
127
134
|
...DEFAULT_OPTIONS,
|
|
@@ -174,6 +181,8 @@ function createChart(element, options = {}) {
|
|
|
174
181
|
let yMaxOverride = null;
|
|
175
182
|
let autoYMin = null;
|
|
176
183
|
let autoYMax = null;
|
|
184
|
+
const brandLogoPathA = new Path2D(BRAND_LOGO_PATH_A);
|
|
185
|
+
const brandLogoPathB = new Path2D(BRAND_LOGO_PATH_B);
|
|
177
186
|
let watermarkImageSrc = null;
|
|
178
187
|
let watermarkImage = null;
|
|
179
188
|
let watermarkImageReady = false;
|
|
@@ -194,7 +203,9 @@ function createChart(element, options = {}) {
|
|
|
194
203
|
element.innerHTML = "";
|
|
195
204
|
element.appendChild(canvas);
|
|
196
205
|
const margin = { top: 16, right: 72, bottom: 34, left: 12 };
|
|
197
|
-
const
|
|
206
|
+
const minVisibleBars = Math.max(1, Math.floor(mergedOptions.minVisibleBars));
|
|
207
|
+
const maxVisibleBars = Math.max(minVisibleBars, Math.floor(mergedOptions.maxVisibleBars));
|
|
208
|
+
const maxPanBars = Math.max(0, Math.floor(mergedOptions.maxPanBars));
|
|
198
209
|
const rightEdgePaddingBars = Math.max(0, mergedOptions.rightEdgePaddingBars);
|
|
199
210
|
const getPixelRatio = () => {
|
|
200
211
|
if (typeof window === "undefined") {
|
|
@@ -236,8 +247,8 @@ function createChart(element, options = {}) {
|
|
|
236
247
|
xSpan = 60;
|
|
237
248
|
return;
|
|
238
249
|
}
|
|
239
|
-
const minSpan =
|
|
240
|
-
const maxSpan = Math.max(minSpan, count + maxPanBars * 2);
|
|
250
|
+
const minSpan = minVisibleBars;
|
|
251
|
+
const maxSpan = Math.min(maxVisibleBars, Math.max(minSpan, count + maxPanBars * 2));
|
|
241
252
|
xSpan = clamp(xSpan, minSpan, maxSpan);
|
|
242
253
|
xCenter = clamp(xCenter, -maxPanBars, count + maxPanBars);
|
|
243
254
|
};
|
|
@@ -248,8 +259,9 @@ function createChart(element, options = {}) {
|
|
|
248
259
|
xSpan = 60;
|
|
249
260
|
return;
|
|
250
261
|
}
|
|
251
|
-
const
|
|
252
|
-
|
|
262
|
+
const maxSpan = Math.min(maxVisibleBars, Math.max(minVisibleBars, count + maxPanBars * 2));
|
|
263
|
+
const requestedVisibleBars = clamp(Math.floor(mergedOptions.initialVisibleBars), minVisibleBars, maxSpan);
|
|
264
|
+
xSpan = requestedVisibleBars;
|
|
253
265
|
if (mergedOptions.initialViewport === "center") {
|
|
254
266
|
xCenter = count / 2;
|
|
255
267
|
} else {
|
|
@@ -1031,6 +1043,17 @@ function createChart(element, options = {}) {
|
|
|
1031
1043
|
});
|
|
1032
1044
|
drawText(timeLabel, x, chartBottom + 8, "center", "top", axis.textColor);
|
|
1033
1045
|
}
|
|
1046
|
+
const brandLogoWidth = 34;
|
|
1047
|
+
const brandLogoHeight = BRAND_LOGO_VIEWBOX_HEIGHT / BRAND_LOGO_VIEWBOX_WIDTH * brandLogoWidth;
|
|
1048
|
+
const brandLogoX = chartLeft + 16;
|
|
1049
|
+
const brandLogoY = chartBottom - brandLogoHeight - 10;
|
|
1050
|
+
ctx.save();
|
|
1051
|
+
ctx.translate(brandLogoX, brandLogoY);
|
|
1052
|
+
ctx.scale(brandLogoWidth / BRAND_LOGO_VIEWBOX_WIDTH, brandLogoHeight / BRAND_LOGO_VIEWBOX_HEIGHT);
|
|
1053
|
+
ctx.fillStyle = "#ffffff";
|
|
1054
|
+
ctx.fill(brandLogoPathA);
|
|
1055
|
+
ctx.fill(brandLogoPathB);
|
|
1056
|
+
ctx.restore();
|
|
1034
1057
|
if (crosshair.visible && crosshairPoint) {
|
|
1035
1058
|
const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
|
|
1036
1059
|
const cy = clamp(crosshairPoint.y, chartTop, chartBottom);
|
|
@@ -1091,8 +1114,8 @@ function createChart(element, options = {}) {
|
|
|
1091
1114
|
if (!drawState || data.length === 0) {
|
|
1092
1115
|
return;
|
|
1093
1116
|
}
|
|
1094
|
-
const minSpan =
|
|
1095
|
-
const maxSpan = Math.max(minSpan, data.length + maxPanBars * 2);
|
|
1117
|
+
const minSpan = minVisibleBars;
|
|
1118
|
+
const maxSpan = Math.min(maxVisibleBars, Math.max(minSpan, data.length + maxPanBars * 2));
|
|
1096
1119
|
const nextSpan = clamp(xSpan * factor, minSpan, maxSpan);
|
|
1097
1120
|
const anchorRatio = clamp((anchorX - drawState.chartLeft) / drawState.chartWidth, 0, 1);
|
|
1098
1121
|
const anchorIndex = drawState.xStart + anchorRatio * xSpan;
|
|
@@ -1106,8 +1129,8 @@ function createChart(element, options = {}) {
|
|
|
1106
1129
|
if (!drawState || data.length === 0) {
|
|
1107
1130
|
return;
|
|
1108
1131
|
}
|
|
1109
|
-
const minSpan =
|
|
1110
|
-
const maxSpan = Math.max(minSpan, data.length + maxPanBars * 2);
|
|
1132
|
+
const minSpan = minVisibleBars;
|
|
1133
|
+
const maxSpan = Math.min(maxVisibleBars, Math.max(minSpan, data.length + maxPanBars * 2));
|
|
1111
1134
|
const nextSpan = clamp(xSpan * factor, minSpan, maxSpan);
|
|
1112
1135
|
const rightEdgeIndex = data.length + rightEdgePaddingBars;
|
|
1113
1136
|
const nextStart = rightEdgeIndex - nextSpan;
|
|
@@ -1156,6 +1179,64 @@ function createChart(element, options = {}) {
|
|
|
1156
1179
|
}
|
|
1157
1180
|
draw();
|
|
1158
1181
|
};
|
|
1182
|
+
const resetYViewport = () => {
|
|
1183
|
+
yMinOverride = null;
|
|
1184
|
+
yMaxOverride = null;
|
|
1185
|
+
autoYMin = null;
|
|
1186
|
+
autoYMax = null;
|
|
1187
|
+
};
|
|
1188
|
+
const zoomInX = (factor = 1.25) => {
|
|
1189
|
+
if (!drawState) {
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
zoomX(1 / Math.max(1.01, factor), drawState.chartLeft + drawState.chartWidth / 2);
|
|
1193
|
+
};
|
|
1194
|
+
const zoomOutX = (factor = 1.25) => {
|
|
1195
|
+
if (!drawState) {
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
zoomX(Math.max(1.01, factor), drawState.chartLeft + drawState.chartWidth / 2);
|
|
1199
|
+
};
|
|
1200
|
+
const zoomInY = (factor = 1.25) => {
|
|
1201
|
+
if (!drawState) {
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
zoomY(1 / Math.max(1.01, factor), drawState.chartTop + drawState.chartHeight / 2);
|
|
1205
|
+
};
|
|
1206
|
+
const zoomOutY = (factor = 1.25) => {
|
|
1207
|
+
if (!drawState) {
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
zoomY(Math.max(1.01, factor), drawState.chartTop + drawState.chartHeight / 2);
|
|
1211
|
+
};
|
|
1212
|
+
const panX = (bars) => {
|
|
1213
|
+
if (!Number.isFinite(bars) || bars === 0) {
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
xCenter += bars;
|
|
1217
|
+
clampXViewport();
|
|
1218
|
+
draw();
|
|
1219
|
+
};
|
|
1220
|
+
const panY = (priceDelta) => {
|
|
1221
|
+
if (!drawState || !Number.isFinite(priceDelta) || priceDelta === 0) {
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
const currentMin = yMinOverride ?? drawState.yMin;
|
|
1225
|
+
const currentMax = yMaxOverride ?? drawState.yMax;
|
|
1226
|
+
const clamped = clampYRange(currentMin + priceDelta, currentMax + priceDelta);
|
|
1227
|
+
yMinOverride = clamped.min;
|
|
1228
|
+
yMaxOverride = clamped.max;
|
|
1229
|
+
draw();
|
|
1230
|
+
};
|
|
1231
|
+
const fitContent = () => {
|
|
1232
|
+
fitXViewport();
|
|
1233
|
+
draw();
|
|
1234
|
+
};
|
|
1235
|
+
const resetViewport = () => {
|
|
1236
|
+
fitXViewport();
|
|
1237
|
+
resetYViewport();
|
|
1238
|
+
draw();
|
|
1239
|
+
};
|
|
1159
1240
|
const getCanvasPoint = (event) => {
|
|
1160
1241
|
const rect = canvas.getBoundingClientRect();
|
|
1161
1242
|
return {
|
|
@@ -1499,19 +1580,11 @@ function createChart(element, options = {}) {
|
|
|
1499
1580
|
return;
|
|
1500
1581
|
}
|
|
1501
1582
|
if (region === "y-axis") {
|
|
1502
|
-
|
|
1503
|
-
yMaxOverride = null;
|
|
1504
|
-
autoYMin = null;
|
|
1505
|
-
autoYMax = null;
|
|
1583
|
+
resetYViewport();
|
|
1506
1584
|
draw();
|
|
1507
1585
|
return;
|
|
1508
1586
|
}
|
|
1509
|
-
|
|
1510
|
-
yMinOverride = null;
|
|
1511
|
-
yMaxOverride = null;
|
|
1512
|
-
autoYMin = null;
|
|
1513
|
-
autoYMax = null;
|
|
1514
|
-
draw();
|
|
1587
|
+
resetViewport();
|
|
1515
1588
|
};
|
|
1516
1589
|
canvas.addEventListener("pointerdown", onPointerDown);
|
|
1517
1590
|
canvas.addEventListener("pointermove", onPointerMove);
|
|
@@ -1535,19 +1608,13 @@ function createChart(element, options = {}) {
|
|
|
1535
1608
|
if (data.length === 0) {
|
|
1536
1609
|
xCenter = 0;
|
|
1537
1610
|
xSpan = 60;
|
|
1538
|
-
|
|
1539
|
-
yMaxOverride = null;
|
|
1540
|
-
autoYMin = null;
|
|
1541
|
-
autoYMax = null;
|
|
1611
|
+
resetYViewport();
|
|
1542
1612
|
draw();
|
|
1543
1613
|
return;
|
|
1544
1614
|
}
|
|
1545
1615
|
if (!hadData) {
|
|
1546
1616
|
fitXViewport();
|
|
1547
|
-
|
|
1548
|
-
yMaxOverride = null;
|
|
1549
|
-
autoYMin = null;
|
|
1550
|
-
autoYMax = null;
|
|
1617
|
+
resetYViewport();
|
|
1551
1618
|
} else {
|
|
1552
1619
|
if (mergedOptions.preserveViewportOnDataUpdate) {
|
|
1553
1620
|
clampXViewport();
|
|
@@ -1646,6 +1713,14 @@ function createChart(element, options = {}) {
|
|
|
1646
1713
|
onOrderAction,
|
|
1647
1714
|
onChartClick,
|
|
1648
1715
|
onCrosshairMove,
|
|
1716
|
+
zoomInX,
|
|
1717
|
+
zoomOutX,
|
|
1718
|
+
zoomInY,
|
|
1719
|
+
zoomOutY,
|
|
1720
|
+
panX,
|
|
1721
|
+
panY,
|
|
1722
|
+
resetViewport,
|
|
1723
|
+
fitContent,
|
|
1649
1724
|
setDoubleClickEnabled,
|
|
1650
1725
|
setDoubleClickAction,
|
|
1651
1726
|
resize,
|
package/dist/index.cjs
CHANGED
|
@@ -118,6 +118,9 @@ var DEFAULT_OPTIONS = {
|
|
|
118
118
|
priceDecimals: 2,
|
|
119
119
|
initialViewport: "latest",
|
|
120
120
|
initialVisibleBars: 60,
|
|
121
|
+
minVisibleBars: 5,
|
|
122
|
+
maxVisibleBars: 2e4,
|
|
123
|
+
maxPanBars: 1e6,
|
|
121
124
|
rightEdgePaddingBars: 2,
|
|
122
125
|
preserveViewportOnDataUpdate: true,
|
|
123
126
|
upColor: "#2fb171",
|
|
@@ -146,6 +149,10 @@ var DEFAULT_OPTIONS = {
|
|
|
146
149
|
labelBorderRadius: 3
|
|
147
150
|
}
|
|
148
151
|
};
|
|
152
|
+
var BRAND_LOGO_VIEWBOX_WIDTH = 190;
|
|
153
|
+
var BRAND_LOGO_VIEWBOX_HEIGHT = 186;
|
|
154
|
+
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";
|
|
155
|
+
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";
|
|
149
156
|
function createChart(element, options = {}) {
|
|
150
157
|
const mergedOptions = {
|
|
151
158
|
...DEFAULT_OPTIONS,
|
|
@@ -198,6 +205,8 @@ function createChart(element, options = {}) {
|
|
|
198
205
|
let yMaxOverride = null;
|
|
199
206
|
let autoYMin = null;
|
|
200
207
|
let autoYMax = null;
|
|
208
|
+
const brandLogoPathA = new Path2D(BRAND_LOGO_PATH_A);
|
|
209
|
+
const brandLogoPathB = new Path2D(BRAND_LOGO_PATH_B);
|
|
201
210
|
let watermarkImageSrc = null;
|
|
202
211
|
let watermarkImage = null;
|
|
203
212
|
let watermarkImageReady = false;
|
|
@@ -218,7 +227,9 @@ function createChart(element, options = {}) {
|
|
|
218
227
|
element.innerHTML = "";
|
|
219
228
|
element.appendChild(canvas);
|
|
220
229
|
const margin = { top: 16, right: 72, bottom: 34, left: 12 };
|
|
221
|
-
const
|
|
230
|
+
const minVisibleBars = Math.max(1, Math.floor(mergedOptions.minVisibleBars));
|
|
231
|
+
const maxVisibleBars = Math.max(minVisibleBars, Math.floor(mergedOptions.maxVisibleBars));
|
|
232
|
+
const maxPanBars = Math.max(0, Math.floor(mergedOptions.maxPanBars));
|
|
222
233
|
const rightEdgePaddingBars = Math.max(0, mergedOptions.rightEdgePaddingBars);
|
|
223
234
|
const getPixelRatio = () => {
|
|
224
235
|
if (typeof window === "undefined") {
|
|
@@ -260,8 +271,8 @@ function createChart(element, options = {}) {
|
|
|
260
271
|
xSpan = 60;
|
|
261
272
|
return;
|
|
262
273
|
}
|
|
263
|
-
const minSpan =
|
|
264
|
-
const maxSpan = Math.max(minSpan, count + maxPanBars * 2);
|
|
274
|
+
const minSpan = minVisibleBars;
|
|
275
|
+
const maxSpan = Math.min(maxVisibleBars, Math.max(minSpan, count + maxPanBars * 2));
|
|
265
276
|
xSpan = clamp(xSpan, minSpan, maxSpan);
|
|
266
277
|
xCenter = clamp(xCenter, -maxPanBars, count + maxPanBars);
|
|
267
278
|
};
|
|
@@ -272,8 +283,9 @@ function createChart(element, options = {}) {
|
|
|
272
283
|
xSpan = 60;
|
|
273
284
|
return;
|
|
274
285
|
}
|
|
275
|
-
const
|
|
276
|
-
|
|
286
|
+
const maxSpan = Math.min(maxVisibleBars, Math.max(minVisibleBars, count + maxPanBars * 2));
|
|
287
|
+
const requestedVisibleBars = clamp(Math.floor(mergedOptions.initialVisibleBars), minVisibleBars, maxSpan);
|
|
288
|
+
xSpan = requestedVisibleBars;
|
|
277
289
|
if (mergedOptions.initialViewport === "center") {
|
|
278
290
|
xCenter = count / 2;
|
|
279
291
|
} else {
|
|
@@ -1055,6 +1067,17 @@ function createChart(element, options = {}) {
|
|
|
1055
1067
|
});
|
|
1056
1068
|
drawText(timeLabel, x, chartBottom + 8, "center", "top", axis.textColor);
|
|
1057
1069
|
}
|
|
1070
|
+
const brandLogoWidth = 34;
|
|
1071
|
+
const brandLogoHeight = BRAND_LOGO_VIEWBOX_HEIGHT / BRAND_LOGO_VIEWBOX_WIDTH * brandLogoWidth;
|
|
1072
|
+
const brandLogoX = chartLeft + 16;
|
|
1073
|
+
const brandLogoY = chartBottom - brandLogoHeight - 10;
|
|
1074
|
+
ctx.save();
|
|
1075
|
+
ctx.translate(brandLogoX, brandLogoY);
|
|
1076
|
+
ctx.scale(brandLogoWidth / BRAND_LOGO_VIEWBOX_WIDTH, brandLogoHeight / BRAND_LOGO_VIEWBOX_HEIGHT);
|
|
1077
|
+
ctx.fillStyle = "#ffffff";
|
|
1078
|
+
ctx.fill(brandLogoPathA);
|
|
1079
|
+
ctx.fill(brandLogoPathB);
|
|
1080
|
+
ctx.restore();
|
|
1058
1081
|
if (crosshair.visible && crosshairPoint) {
|
|
1059
1082
|
const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
|
|
1060
1083
|
const cy = clamp(crosshairPoint.y, chartTop, chartBottom);
|
|
@@ -1115,8 +1138,8 @@ function createChart(element, options = {}) {
|
|
|
1115
1138
|
if (!drawState || data.length === 0) {
|
|
1116
1139
|
return;
|
|
1117
1140
|
}
|
|
1118
|
-
const minSpan =
|
|
1119
|
-
const maxSpan = Math.max(minSpan, data.length + maxPanBars * 2);
|
|
1141
|
+
const minSpan = minVisibleBars;
|
|
1142
|
+
const maxSpan = Math.min(maxVisibleBars, Math.max(minSpan, data.length + maxPanBars * 2));
|
|
1120
1143
|
const nextSpan = clamp(xSpan * factor, minSpan, maxSpan);
|
|
1121
1144
|
const anchorRatio = clamp((anchorX - drawState.chartLeft) / drawState.chartWidth, 0, 1);
|
|
1122
1145
|
const anchorIndex = drawState.xStart + anchorRatio * xSpan;
|
|
@@ -1130,8 +1153,8 @@ function createChart(element, options = {}) {
|
|
|
1130
1153
|
if (!drawState || data.length === 0) {
|
|
1131
1154
|
return;
|
|
1132
1155
|
}
|
|
1133
|
-
const minSpan =
|
|
1134
|
-
const maxSpan = Math.max(minSpan, data.length + maxPanBars * 2);
|
|
1156
|
+
const minSpan = minVisibleBars;
|
|
1157
|
+
const maxSpan = Math.min(maxVisibleBars, Math.max(minSpan, data.length + maxPanBars * 2));
|
|
1135
1158
|
const nextSpan = clamp(xSpan * factor, minSpan, maxSpan);
|
|
1136
1159
|
const rightEdgeIndex = data.length + rightEdgePaddingBars;
|
|
1137
1160
|
const nextStart = rightEdgeIndex - nextSpan;
|
|
@@ -1180,6 +1203,64 @@ function createChart(element, options = {}) {
|
|
|
1180
1203
|
}
|
|
1181
1204
|
draw();
|
|
1182
1205
|
};
|
|
1206
|
+
const resetYViewport = () => {
|
|
1207
|
+
yMinOverride = null;
|
|
1208
|
+
yMaxOverride = null;
|
|
1209
|
+
autoYMin = null;
|
|
1210
|
+
autoYMax = null;
|
|
1211
|
+
};
|
|
1212
|
+
const zoomInX = (factor = 1.25) => {
|
|
1213
|
+
if (!drawState) {
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
zoomX(1 / Math.max(1.01, factor), drawState.chartLeft + drawState.chartWidth / 2);
|
|
1217
|
+
};
|
|
1218
|
+
const zoomOutX = (factor = 1.25) => {
|
|
1219
|
+
if (!drawState) {
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
zoomX(Math.max(1.01, factor), drawState.chartLeft + drawState.chartWidth / 2);
|
|
1223
|
+
};
|
|
1224
|
+
const zoomInY = (factor = 1.25) => {
|
|
1225
|
+
if (!drawState) {
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
zoomY(1 / Math.max(1.01, factor), drawState.chartTop + drawState.chartHeight / 2);
|
|
1229
|
+
};
|
|
1230
|
+
const zoomOutY = (factor = 1.25) => {
|
|
1231
|
+
if (!drawState) {
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
zoomY(Math.max(1.01, factor), drawState.chartTop + drawState.chartHeight / 2);
|
|
1235
|
+
};
|
|
1236
|
+
const panX = (bars) => {
|
|
1237
|
+
if (!Number.isFinite(bars) || bars === 0) {
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
xCenter += bars;
|
|
1241
|
+
clampXViewport();
|
|
1242
|
+
draw();
|
|
1243
|
+
};
|
|
1244
|
+
const panY = (priceDelta) => {
|
|
1245
|
+
if (!drawState || !Number.isFinite(priceDelta) || priceDelta === 0) {
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
const currentMin = yMinOverride ?? drawState.yMin;
|
|
1249
|
+
const currentMax = yMaxOverride ?? drawState.yMax;
|
|
1250
|
+
const clamped = clampYRange(currentMin + priceDelta, currentMax + priceDelta);
|
|
1251
|
+
yMinOverride = clamped.min;
|
|
1252
|
+
yMaxOverride = clamped.max;
|
|
1253
|
+
draw();
|
|
1254
|
+
};
|
|
1255
|
+
const fitContent = () => {
|
|
1256
|
+
fitXViewport();
|
|
1257
|
+
draw();
|
|
1258
|
+
};
|
|
1259
|
+
const resetViewport = () => {
|
|
1260
|
+
fitXViewport();
|
|
1261
|
+
resetYViewport();
|
|
1262
|
+
draw();
|
|
1263
|
+
};
|
|
1183
1264
|
const getCanvasPoint = (event) => {
|
|
1184
1265
|
const rect = canvas.getBoundingClientRect();
|
|
1185
1266
|
return {
|
|
@@ -1523,19 +1604,11 @@ function createChart(element, options = {}) {
|
|
|
1523
1604
|
return;
|
|
1524
1605
|
}
|
|
1525
1606
|
if (region === "y-axis") {
|
|
1526
|
-
|
|
1527
|
-
yMaxOverride = null;
|
|
1528
|
-
autoYMin = null;
|
|
1529
|
-
autoYMax = null;
|
|
1607
|
+
resetYViewport();
|
|
1530
1608
|
draw();
|
|
1531
1609
|
return;
|
|
1532
1610
|
}
|
|
1533
|
-
|
|
1534
|
-
yMinOverride = null;
|
|
1535
|
-
yMaxOverride = null;
|
|
1536
|
-
autoYMin = null;
|
|
1537
|
-
autoYMax = null;
|
|
1538
|
-
draw();
|
|
1611
|
+
resetViewport();
|
|
1539
1612
|
};
|
|
1540
1613
|
canvas.addEventListener("pointerdown", onPointerDown);
|
|
1541
1614
|
canvas.addEventListener("pointermove", onPointerMove);
|
|
@@ -1559,19 +1632,13 @@ function createChart(element, options = {}) {
|
|
|
1559
1632
|
if (data.length === 0) {
|
|
1560
1633
|
xCenter = 0;
|
|
1561
1634
|
xSpan = 60;
|
|
1562
|
-
|
|
1563
|
-
yMaxOverride = null;
|
|
1564
|
-
autoYMin = null;
|
|
1565
|
-
autoYMax = null;
|
|
1635
|
+
resetYViewport();
|
|
1566
1636
|
draw();
|
|
1567
1637
|
return;
|
|
1568
1638
|
}
|
|
1569
1639
|
if (!hadData) {
|
|
1570
1640
|
fitXViewport();
|
|
1571
|
-
|
|
1572
|
-
yMaxOverride = null;
|
|
1573
|
-
autoYMin = null;
|
|
1574
|
-
autoYMax = null;
|
|
1641
|
+
resetYViewport();
|
|
1575
1642
|
} else {
|
|
1576
1643
|
if (mergedOptions.preserveViewportOnDataUpdate) {
|
|
1577
1644
|
clampXViewport();
|
|
@@ -1670,6 +1737,14 @@ function createChart(element, options = {}) {
|
|
|
1670
1737
|
onOrderAction,
|
|
1671
1738
|
onChartClick,
|
|
1672
1739
|
onCrosshairMove,
|
|
1740
|
+
zoomInX,
|
|
1741
|
+
zoomOutX,
|
|
1742
|
+
zoomInY,
|
|
1743
|
+
zoomOutY,
|
|
1744
|
+
panX,
|
|
1745
|
+
panY,
|
|
1746
|
+
resetViewport,
|
|
1747
|
+
fitContent,
|
|
1673
1748
|
setDoubleClickEnabled,
|
|
1674
1749
|
setDoubleClickAction,
|
|
1675
1750
|
resize,
|
package/dist/index.d.cts
CHANGED
|
@@ -7,6 +7,9 @@ interface ChartOptions {
|
|
|
7
7
|
priceDecimals?: number;
|
|
8
8
|
initialViewport?: "latest" | "center";
|
|
9
9
|
initialVisibleBars?: number;
|
|
10
|
+
minVisibleBars?: number;
|
|
11
|
+
maxVisibleBars?: number;
|
|
12
|
+
maxPanBars?: number;
|
|
10
13
|
rightEdgePaddingBars?: number;
|
|
11
14
|
preserveViewportOnDataUpdate?: boolean;
|
|
12
15
|
upColor?: string;
|
|
@@ -182,6 +185,14 @@ interface ChartInstance {
|
|
|
182
185
|
onOrderAction: (handler: ((event: OrderActionEvent) => void) | null) => void;
|
|
183
186
|
onChartClick: (handler: ((event: ChartClickEvent) => void) | null) => void;
|
|
184
187
|
onCrosshairMove: (handler: ((event: CrosshairMoveEvent) => void) | null) => void;
|
|
188
|
+
zoomInX: (factor?: number) => void;
|
|
189
|
+
zoomOutX: (factor?: number) => void;
|
|
190
|
+
zoomInY: (factor?: number) => void;
|
|
191
|
+
zoomOutY: (factor?: number) => void;
|
|
192
|
+
panX: (bars: number) => void;
|
|
193
|
+
panY: (priceDelta: number) => void;
|
|
194
|
+
resetViewport: () => void;
|
|
195
|
+
fitContent: () => void;
|
|
185
196
|
setDoubleClickEnabled: (enabled: boolean) => void;
|
|
186
197
|
setDoubleClickAction: (action: "reset" | "placeLimitOrder") => void;
|
|
187
198
|
resize: (width?: number, height?: number) => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,9 @@ interface ChartOptions {
|
|
|
7
7
|
priceDecimals?: number;
|
|
8
8
|
initialViewport?: "latest" | "center";
|
|
9
9
|
initialVisibleBars?: number;
|
|
10
|
+
minVisibleBars?: number;
|
|
11
|
+
maxVisibleBars?: number;
|
|
12
|
+
maxPanBars?: number;
|
|
10
13
|
rightEdgePaddingBars?: number;
|
|
11
14
|
preserveViewportOnDataUpdate?: boolean;
|
|
12
15
|
upColor?: string;
|
|
@@ -182,6 +185,14 @@ interface ChartInstance {
|
|
|
182
185
|
onOrderAction: (handler: ((event: OrderActionEvent) => void) | null) => void;
|
|
183
186
|
onChartClick: (handler: ((event: ChartClickEvent) => void) | null) => void;
|
|
184
187
|
onCrosshairMove: (handler: ((event: CrosshairMoveEvent) => void) | null) => void;
|
|
188
|
+
zoomInX: (factor?: number) => void;
|
|
189
|
+
zoomOutX: (factor?: number) => void;
|
|
190
|
+
zoomInY: (factor?: number) => void;
|
|
191
|
+
zoomOutY: (factor?: number) => void;
|
|
192
|
+
panX: (bars: number) => void;
|
|
193
|
+
panY: (priceDelta: number) => void;
|
|
194
|
+
resetViewport: () => void;
|
|
195
|
+
fitContent: () => void;
|
|
185
196
|
setDoubleClickEnabled: (enabled: boolean) => void;
|
|
186
197
|
setDoubleClickAction: (action: "reset" | "placeLimitOrder") => void;
|
|
187
198
|
resize: (width?: number, height?: number) => void;
|
package/dist/index.js
CHANGED
|
@@ -94,6 +94,9 @@ var DEFAULT_OPTIONS = {
|
|
|
94
94
|
priceDecimals: 2,
|
|
95
95
|
initialViewport: "latest",
|
|
96
96
|
initialVisibleBars: 60,
|
|
97
|
+
minVisibleBars: 5,
|
|
98
|
+
maxVisibleBars: 2e4,
|
|
99
|
+
maxPanBars: 1e6,
|
|
97
100
|
rightEdgePaddingBars: 2,
|
|
98
101
|
preserveViewportOnDataUpdate: true,
|
|
99
102
|
upColor: "#2fb171",
|
|
@@ -122,6 +125,10 @@ var DEFAULT_OPTIONS = {
|
|
|
122
125
|
labelBorderRadius: 3
|
|
123
126
|
}
|
|
124
127
|
};
|
|
128
|
+
var BRAND_LOGO_VIEWBOX_WIDTH = 190;
|
|
129
|
+
var BRAND_LOGO_VIEWBOX_HEIGHT = 186;
|
|
130
|
+
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";
|
|
131
|
+
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";
|
|
125
132
|
function createChart(element, options = {}) {
|
|
126
133
|
const mergedOptions = {
|
|
127
134
|
...DEFAULT_OPTIONS,
|
|
@@ -174,6 +181,8 @@ function createChart(element, options = {}) {
|
|
|
174
181
|
let yMaxOverride = null;
|
|
175
182
|
let autoYMin = null;
|
|
176
183
|
let autoYMax = null;
|
|
184
|
+
const brandLogoPathA = new Path2D(BRAND_LOGO_PATH_A);
|
|
185
|
+
const brandLogoPathB = new Path2D(BRAND_LOGO_PATH_B);
|
|
177
186
|
let watermarkImageSrc = null;
|
|
178
187
|
let watermarkImage = null;
|
|
179
188
|
let watermarkImageReady = false;
|
|
@@ -194,7 +203,9 @@ function createChart(element, options = {}) {
|
|
|
194
203
|
element.innerHTML = "";
|
|
195
204
|
element.appendChild(canvas);
|
|
196
205
|
const margin = { top: 16, right: 72, bottom: 34, left: 12 };
|
|
197
|
-
const
|
|
206
|
+
const minVisibleBars = Math.max(1, Math.floor(mergedOptions.minVisibleBars));
|
|
207
|
+
const maxVisibleBars = Math.max(minVisibleBars, Math.floor(mergedOptions.maxVisibleBars));
|
|
208
|
+
const maxPanBars = Math.max(0, Math.floor(mergedOptions.maxPanBars));
|
|
198
209
|
const rightEdgePaddingBars = Math.max(0, mergedOptions.rightEdgePaddingBars);
|
|
199
210
|
const getPixelRatio = () => {
|
|
200
211
|
if (typeof window === "undefined") {
|
|
@@ -236,8 +247,8 @@ function createChart(element, options = {}) {
|
|
|
236
247
|
xSpan = 60;
|
|
237
248
|
return;
|
|
238
249
|
}
|
|
239
|
-
const minSpan =
|
|
240
|
-
const maxSpan = Math.max(minSpan, count + maxPanBars * 2);
|
|
250
|
+
const minSpan = minVisibleBars;
|
|
251
|
+
const maxSpan = Math.min(maxVisibleBars, Math.max(minSpan, count + maxPanBars * 2));
|
|
241
252
|
xSpan = clamp(xSpan, minSpan, maxSpan);
|
|
242
253
|
xCenter = clamp(xCenter, -maxPanBars, count + maxPanBars);
|
|
243
254
|
};
|
|
@@ -248,8 +259,9 @@ function createChart(element, options = {}) {
|
|
|
248
259
|
xSpan = 60;
|
|
249
260
|
return;
|
|
250
261
|
}
|
|
251
|
-
const
|
|
252
|
-
|
|
262
|
+
const maxSpan = Math.min(maxVisibleBars, Math.max(minVisibleBars, count + maxPanBars * 2));
|
|
263
|
+
const requestedVisibleBars = clamp(Math.floor(mergedOptions.initialVisibleBars), minVisibleBars, maxSpan);
|
|
264
|
+
xSpan = requestedVisibleBars;
|
|
253
265
|
if (mergedOptions.initialViewport === "center") {
|
|
254
266
|
xCenter = count / 2;
|
|
255
267
|
} else {
|
|
@@ -1031,6 +1043,17 @@ function createChart(element, options = {}) {
|
|
|
1031
1043
|
});
|
|
1032
1044
|
drawText(timeLabel, x, chartBottom + 8, "center", "top", axis.textColor);
|
|
1033
1045
|
}
|
|
1046
|
+
const brandLogoWidth = 34;
|
|
1047
|
+
const brandLogoHeight = BRAND_LOGO_VIEWBOX_HEIGHT / BRAND_LOGO_VIEWBOX_WIDTH * brandLogoWidth;
|
|
1048
|
+
const brandLogoX = chartLeft + 16;
|
|
1049
|
+
const brandLogoY = chartBottom - brandLogoHeight - 10;
|
|
1050
|
+
ctx.save();
|
|
1051
|
+
ctx.translate(brandLogoX, brandLogoY);
|
|
1052
|
+
ctx.scale(brandLogoWidth / BRAND_LOGO_VIEWBOX_WIDTH, brandLogoHeight / BRAND_LOGO_VIEWBOX_HEIGHT);
|
|
1053
|
+
ctx.fillStyle = "#ffffff";
|
|
1054
|
+
ctx.fill(brandLogoPathA);
|
|
1055
|
+
ctx.fill(brandLogoPathB);
|
|
1056
|
+
ctx.restore();
|
|
1034
1057
|
if (crosshair.visible && crosshairPoint) {
|
|
1035
1058
|
const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
|
|
1036
1059
|
const cy = clamp(crosshairPoint.y, chartTop, chartBottom);
|
|
@@ -1091,8 +1114,8 @@ function createChart(element, options = {}) {
|
|
|
1091
1114
|
if (!drawState || data.length === 0) {
|
|
1092
1115
|
return;
|
|
1093
1116
|
}
|
|
1094
|
-
const minSpan =
|
|
1095
|
-
const maxSpan = Math.max(minSpan, data.length + maxPanBars * 2);
|
|
1117
|
+
const minSpan = minVisibleBars;
|
|
1118
|
+
const maxSpan = Math.min(maxVisibleBars, Math.max(minSpan, data.length + maxPanBars * 2));
|
|
1096
1119
|
const nextSpan = clamp(xSpan * factor, minSpan, maxSpan);
|
|
1097
1120
|
const anchorRatio = clamp((anchorX - drawState.chartLeft) / drawState.chartWidth, 0, 1);
|
|
1098
1121
|
const anchorIndex = drawState.xStart + anchorRatio * xSpan;
|
|
@@ -1106,8 +1129,8 @@ function createChart(element, options = {}) {
|
|
|
1106
1129
|
if (!drawState || data.length === 0) {
|
|
1107
1130
|
return;
|
|
1108
1131
|
}
|
|
1109
|
-
const minSpan =
|
|
1110
|
-
const maxSpan = Math.max(minSpan, data.length + maxPanBars * 2);
|
|
1132
|
+
const minSpan = minVisibleBars;
|
|
1133
|
+
const maxSpan = Math.min(maxVisibleBars, Math.max(minSpan, data.length + maxPanBars * 2));
|
|
1111
1134
|
const nextSpan = clamp(xSpan * factor, minSpan, maxSpan);
|
|
1112
1135
|
const rightEdgeIndex = data.length + rightEdgePaddingBars;
|
|
1113
1136
|
const nextStart = rightEdgeIndex - nextSpan;
|
|
@@ -1156,6 +1179,64 @@ function createChart(element, options = {}) {
|
|
|
1156
1179
|
}
|
|
1157
1180
|
draw();
|
|
1158
1181
|
};
|
|
1182
|
+
const resetYViewport = () => {
|
|
1183
|
+
yMinOverride = null;
|
|
1184
|
+
yMaxOverride = null;
|
|
1185
|
+
autoYMin = null;
|
|
1186
|
+
autoYMax = null;
|
|
1187
|
+
};
|
|
1188
|
+
const zoomInX = (factor = 1.25) => {
|
|
1189
|
+
if (!drawState) {
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
zoomX(1 / Math.max(1.01, factor), drawState.chartLeft + drawState.chartWidth / 2);
|
|
1193
|
+
};
|
|
1194
|
+
const zoomOutX = (factor = 1.25) => {
|
|
1195
|
+
if (!drawState) {
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
zoomX(Math.max(1.01, factor), drawState.chartLeft + drawState.chartWidth / 2);
|
|
1199
|
+
};
|
|
1200
|
+
const zoomInY = (factor = 1.25) => {
|
|
1201
|
+
if (!drawState) {
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
zoomY(1 / Math.max(1.01, factor), drawState.chartTop + drawState.chartHeight / 2);
|
|
1205
|
+
};
|
|
1206
|
+
const zoomOutY = (factor = 1.25) => {
|
|
1207
|
+
if (!drawState) {
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
zoomY(Math.max(1.01, factor), drawState.chartTop + drawState.chartHeight / 2);
|
|
1211
|
+
};
|
|
1212
|
+
const panX = (bars) => {
|
|
1213
|
+
if (!Number.isFinite(bars) || bars === 0) {
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
xCenter += bars;
|
|
1217
|
+
clampXViewport();
|
|
1218
|
+
draw();
|
|
1219
|
+
};
|
|
1220
|
+
const panY = (priceDelta) => {
|
|
1221
|
+
if (!drawState || !Number.isFinite(priceDelta) || priceDelta === 0) {
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
const currentMin = yMinOverride ?? drawState.yMin;
|
|
1225
|
+
const currentMax = yMaxOverride ?? drawState.yMax;
|
|
1226
|
+
const clamped = clampYRange(currentMin + priceDelta, currentMax + priceDelta);
|
|
1227
|
+
yMinOverride = clamped.min;
|
|
1228
|
+
yMaxOverride = clamped.max;
|
|
1229
|
+
draw();
|
|
1230
|
+
};
|
|
1231
|
+
const fitContent = () => {
|
|
1232
|
+
fitXViewport();
|
|
1233
|
+
draw();
|
|
1234
|
+
};
|
|
1235
|
+
const resetViewport = () => {
|
|
1236
|
+
fitXViewport();
|
|
1237
|
+
resetYViewport();
|
|
1238
|
+
draw();
|
|
1239
|
+
};
|
|
1159
1240
|
const getCanvasPoint = (event) => {
|
|
1160
1241
|
const rect = canvas.getBoundingClientRect();
|
|
1161
1242
|
return {
|
|
@@ -1499,19 +1580,11 @@ function createChart(element, options = {}) {
|
|
|
1499
1580
|
return;
|
|
1500
1581
|
}
|
|
1501
1582
|
if (region === "y-axis") {
|
|
1502
|
-
|
|
1503
|
-
yMaxOverride = null;
|
|
1504
|
-
autoYMin = null;
|
|
1505
|
-
autoYMax = null;
|
|
1583
|
+
resetYViewport();
|
|
1506
1584
|
draw();
|
|
1507
1585
|
return;
|
|
1508
1586
|
}
|
|
1509
|
-
|
|
1510
|
-
yMinOverride = null;
|
|
1511
|
-
yMaxOverride = null;
|
|
1512
|
-
autoYMin = null;
|
|
1513
|
-
autoYMax = null;
|
|
1514
|
-
draw();
|
|
1587
|
+
resetViewport();
|
|
1515
1588
|
};
|
|
1516
1589
|
canvas.addEventListener("pointerdown", onPointerDown);
|
|
1517
1590
|
canvas.addEventListener("pointermove", onPointerMove);
|
|
@@ -1535,19 +1608,13 @@ function createChart(element, options = {}) {
|
|
|
1535
1608
|
if (data.length === 0) {
|
|
1536
1609
|
xCenter = 0;
|
|
1537
1610
|
xSpan = 60;
|
|
1538
|
-
|
|
1539
|
-
yMaxOverride = null;
|
|
1540
|
-
autoYMin = null;
|
|
1541
|
-
autoYMax = null;
|
|
1611
|
+
resetYViewport();
|
|
1542
1612
|
draw();
|
|
1543
1613
|
return;
|
|
1544
1614
|
}
|
|
1545
1615
|
if (!hadData) {
|
|
1546
1616
|
fitXViewport();
|
|
1547
|
-
|
|
1548
|
-
yMaxOverride = null;
|
|
1549
|
-
autoYMin = null;
|
|
1550
|
-
autoYMax = null;
|
|
1617
|
+
resetYViewport();
|
|
1551
1618
|
} else {
|
|
1552
1619
|
if (mergedOptions.preserveViewportOnDataUpdate) {
|
|
1553
1620
|
clampXViewport();
|
|
@@ -1646,6 +1713,14 @@ function createChart(element, options = {}) {
|
|
|
1646
1713
|
onOrderAction,
|
|
1647
1714
|
onChartClick,
|
|
1648
1715
|
onCrosshairMove,
|
|
1716
|
+
zoomInX,
|
|
1717
|
+
zoomOutX,
|
|
1718
|
+
zoomInY,
|
|
1719
|
+
zoomOutY,
|
|
1720
|
+
panX,
|
|
1721
|
+
panY,
|
|
1722
|
+
resetViewport,
|
|
1723
|
+
fitContent,
|
|
1649
1724
|
setDoubleClickEnabled,
|
|
1650
1725
|
setDoubleClickAction,
|
|
1651
1726
|
resize,
|
package/docs/API.md
CHANGED
|
@@ -29,17 +29,20 @@ Top-level options:
|
|
|
29
29
|
|
|
30
30
|
- `width` (default `720`)
|
|
31
31
|
- `height` (default `360`)
|
|
32
|
-
- `backgroundColor` (default `#
|
|
32
|
+
- `backgroundColor` (default `#101114`)
|
|
33
33
|
- `axisColor` (legacy shorthand for axis line/text color)
|
|
34
34
|
- `axis?: AxisOptions`
|
|
35
35
|
- `priceDecimals` (default `2`, used for axis/ticker/line price labels)
|
|
36
36
|
- `initialViewport` (`"latest"` | `"center"`, default `"latest"`)
|
|
37
37
|
- `initialVisibleBars` (default `60`)
|
|
38
|
+
- `minVisibleBars` (default `5`, lower clamp for x zoom)
|
|
39
|
+
- `maxVisibleBars` (default `20000`, upper clamp for x zoom)
|
|
40
|
+
- `maxPanBars` (default `1000000`, max bars allowed to pan beyond data)
|
|
38
41
|
- `rightEdgePaddingBars` (default `2`, used by latest-anchored viewport)
|
|
39
42
|
- `preserveViewportOnDataUpdate` (default `true`; set `false` to auto-fit on each `setData`)
|
|
40
|
-
- `upColor` (default `#
|
|
41
|
-
- `downColor` (default `#
|
|
42
|
-
- `gridColor` (default `#
|
|
43
|
+
- `upColor` (default `#2fb171`)
|
|
44
|
+
- `downColor` (default `#d35a5a`)
|
|
45
|
+
- `gridColor` (default `#252932`)
|
|
43
46
|
- `fontFamily`
|
|
44
47
|
- `candleBodyWidthRatio` (default `0.7`)
|
|
45
48
|
- `candleMinWidth` (default `0.5`)
|
|
@@ -57,15 +60,15 @@ Top-level options:
|
|
|
57
60
|
|
|
58
61
|
### `AxisOptions`
|
|
59
62
|
|
|
60
|
-
- `lineColor` (default `#
|
|
61
|
-
- `textColor` (default `#
|
|
63
|
+
- `lineColor` (default `#3b3f47`)
|
|
64
|
+
- `textColor` (default `#a9adb6`)
|
|
62
65
|
- `fontSize` (default `12`)
|
|
63
66
|
- `lineWidth` (default `1`)
|
|
64
67
|
|
|
65
68
|
### `GridOptions`
|
|
66
69
|
|
|
67
|
-
- `color` (default `#
|
|
68
|
-
- `opacity` (default `0.
|
|
70
|
+
- `color` (default `#2b2f38`)
|
|
71
|
+
- `opacity` (default `0.38`)
|
|
69
72
|
- `horizontalLines` (default `true`)
|
|
70
73
|
- `verticalLines` (default `true`)
|
|
71
74
|
- `horizontalTickCount` (default `5`)
|
|
@@ -104,6 +107,18 @@ Top-level options:
|
|
|
104
107
|
- `imageTintColor` (default `""`, set `"#ffffff"` for white logo tint)
|
|
105
108
|
- `imageTintOpacity` (default `1`)
|
|
106
109
|
|
|
110
|
+
Example:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
watermark: {
|
|
114
|
+
visible: true,
|
|
115
|
+
imageSrc: "/logo-white.svg",
|
|
116
|
+
opacity: 0.14,
|
|
117
|
+
imageTintColor: "#ffffff",
|
|
118
|
+
imageTintOpacity: 1
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
107
122
|
### `TickerLineOptions`
|
|
108
123
|
|
|
109
124
|
- `visible` (default `true`)
|
|
@@ -213,6 +228,14 @@ Connector/fill visuals:
|
|
|
213
228
|
- `onOrderAction(handler: ((event: OrderActionEvent) => void) | null): void`
|
|
214
229
|
- `onChartClick(handler: ((event: ChartClickEvent) => void) | null): void`
|
|
215
230
|
- `onCrosshairMove(handler: ((event: CrosshairMoveEvent) => void) | null): void`
|
|
231
|
+
- `zoomInX(factor?: number): void` (default factor `1.25`)
|
|
232
|
+
- `zoomOutX(factor?: number): void` (default factor `1.25`)
|
|
233
|
+
- `zoomInY(factor?: number): void` (default factor `1.25`)
|
|
234
|
+
- `zoomOutY(factor?: number): void` (default factor `1.25`)
|
|
235
|
+
- `panX(bars: number): void` (negative = left, positive = right)
|
|
236
|
+
- `panY(priceDelta: number): void` (positive = move viewport up)
|
|
237
|
+
- `fitContent(): void` (x-only fit, keeps y zoom)
|
|
238
|
+
- `resetViewport(): void` (fit x + reset y auto-scale)
|
|
216
239
|
- `setDoubleClickEnabled(enabled: boolean): void`
|
|
217
240
|
- `setDoubleClickAction(action: "reset" | "placeLimitOrder"): void`
|
|
218
241
|
- `resize(width?: number, height?: number): void`
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg width="190" height="186" viewBox="0 0 190 186" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="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" fill="white"/>
|
|
3
|
+
<path d="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" fill="white"/>
|
|
4
|
+
</svg>
|