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.
@@ -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 maxPanBars = 1e6;
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 = 5;
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 requestedVisibleBars = Math.max(5, Math.floor(mergedOptions.initialVisibleBars));
276
- xSpan = Math.min(requestedVisibleBars, Math.max(5, count));
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 = 5;
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 = 5;
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
- yMinOverride = null;
1527
- yMaxOverride = null;
1528
- autoYMin = null;
1529
- autoYMax = null;
1607
+ resetYViewport();
1530
1608
  draw();
1531
1609
  return;
1532
1610
  }
1533
- fitXViewport();
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
- yMinOverride = null;
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
- yMinOverride = null;
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 maxPanBars = 1e6;
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 = 5;
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 requestedVisibleBars = Math.max(5, Math.floor(mergedOptions.initialVisibleBars));
252
- xSpan = Math.min(requestedVisibleBars, Math.max(5, count));
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 = 5;
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 = 5;
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
- yMinOverride = null;
1503
- yMaxOverride = null;
1504
- autoYMin = null;
1505
- autoYMax = null;
1583
+ resetYViewport();
1506
1584
  draw();
1507
1585
  return;
1508
1586
  }
1509
- fitXViewport();
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
- yMinOverride = null;
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
- yMinOverride = null;
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 maxPanBars = 1e6;
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 = 5;
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 requestedVisibleBars = Math.max(5, Math.floor(mergedOptions.initialVisibleBars));
276
- xSpan = Math.min(requestedVisibleBars, Math.max(5, count));
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 = 5;
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 = 5;
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
- yMinOverride = null;
1527
- yMaxOverride = null;
1528
- autoYMin = null;
1529
- autoYMax = null;
1607
+ resetYViewport();
1530
1608
  draw();
1531
1609
  return;
1532
1610
  }
1533
- fitXViewport();
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
- yMinOverride = null;
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
- yMinOverride = null;
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 maxPanBars = 1e6;
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 = 5;
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 requestedVisibleBars = Math.max(5, Math.floor(mergedOptions.initialVisibleBars));
252
- xSpan = Math.min(requestedVisibleBars, Math.max(5, count));
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 = 5;
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 = 5;
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
- yMinOverride = null;
1503
- yMaxOverride = null;
1504
- autoYMin = null;
1505
- autoYMax = null;
1583
+ resetYViewport();
1506
1584
  draw();
1507
1585
  return;
1508
1586
  }
1509
- fitXViewport();
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
- yMinOverride = null;
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
- yMinOverride = null;
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 `#ffffff`)
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 `#16a34a`)
41
- - `downColor` (default `#dc2626`)
42
- - `gridColor` (default `#e2e8f0`)
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 `#94a3b8`)
61
- - `textColor` (default `#94a3b8`)
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 `#e2e8f0`)
68
- - `opacity` (default `0.9`)
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>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",