hyperprop-charting-library 0.1.11 → 0.1.13

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/index.js CHANGED
@@ -44,6 +44,14 @@ var DEFAULT_WATERMARK_OPTIONS = {
44
44
  imageTintColor: "",
45
45
  imageTintOpacity: 1
46
46
  };
47
+ var DEFAULT_DASH_PATTERNS = {
48
+ dotted: [2, 2],
49
+ dashed: [8, 6],
50
+ connectorDotted: [2, 3],
51
+ connectorDashed: [6, 5],
52
+ borderDotted: [2, 2],
53
+ borderDashed: [6, 4]
54
+ };
47
55
  var DEFAULT_PRICE_LINE_OPTIONS = {
48
56
  visible: true,
49
57
  style: "solid",
@@ -94,6 +102,9 @@ var DEFAULT_OPTIONS = {
94
102
  priceDecimals: 2,
95
103
  initialViewport: "latest",
96
104
  initialVisibleBars: 60,
105
+ minVisibleBars: 5,
106
+ maxVisibleBars: 2e4,
107
+ maxPanBars: 1e6,
97
108
  rightEdgePaddingBars: 2,
98
109
  preserveViewportOnDataUpdate: true,
99
110
  upColor: "#2fb171",
@@ -120,7 +131,8 @@ var DEFAULT_OPTIONS = {
120
131
  labelBackgroundColor: "#38bdf8",
121
132
  labelTextColor: "#0b1220",
122
133
  labelBorderRadius: 3
123
- }
134
+ },
135
+ dashPatterns: DEFAULT_DASH_PATTERNS
124
136
  };
125
137
  var BRAND_LOGO_VIEWBOX_WIDTH = 190;
126
138
  var BRAND_LOGO_VIEWBOX_HEIGHT = 186;
@@ -150,6 +162,10 @@ function createChart(element, options = {}) {
150
162
  tickerLine: {
151
163
  ...DEFAULT_OPTIONS.tickerLine,
152
164
  ...options.tickerLine
165
+ },
166
+ dashPatterns: {
167
+ ...DEFAULT_DASH_PATTERNS,
168
+ ...options.dashPatterns ?? {}
153
169
  }
154
170
  };
155
171
  let width = mergedOptions.width;
@@ -200,7 +216,9 @@ function createChart(element, options = {}) {
200
216
  element.innerHTML = "";
201
217
  element.appendChild(canvas);
202
218
  const margin = { top: 16, right: 72, bottom: 34, left: 12 };
203
- const maxPanBars = 1e6;
219
+ const minVisibleBars = Math.max(1, Math.floor(mergedOptions.minVisibleBars));
220
+ const maxVisibleBars = Math.max(minVisibleBars, Math.floor(mergedOptions.maxVisibleBars));
221
+ const maxPanBars = Math.max(0, Math.floor(mergedOptions.maxPanBars));
204
222
  const rightEdgePaddingBars = Math.max(0, mergedOptions.rightEdgePaddingBars);
205
223
  const getPixelRatio = () => {
206
224
  if (typeof window === "undefined") {
@@ -235,6 +253,23 @@ function createChart(element, options = {}) {
235
253
  const clamp = (value, min, max) => {
236
254
  return Math.min(max, Math.max(min, value));
237
255
  };
256
+ const dashPatterns = {
257
+ dotted: mergedOptions.dashPatterns.dotted ?? DEFAULT_DASH_PATTERNS.dotted,
258
+ dashed: mergedOptions.dashPatterns.dashed ?? DEFAULT_DASH_PATTERNS.dashed,
259
+ connectorDotted: mergedOptions.dashPatterns.connectorDotted ?? DEFAULT_DASH_PATTERNS.connectorDotted,
260
+ connectorDashed: mergedOptions.dashPatterns.connectorDashed ?? DEFAULT_DASH_PATTERNS.connectorDashed,
261
+ borderDotted: mergedOptions.dashPatterns.borderDotted ?? DEFAULT_DASH_PATTERNS.borderDotted,
262
+ borderDashed: mergedOptions.dashPatterns.borderDashed ?? DEFAULT_DASH_PATTERNS.borderDashed
263
+ };
264
+ const applyDashPattern = (style, dotted, dashed) => {
265
+ if (style === "dotted") {
266
+ ctx.setLineDash(dotted);
267
+ } else if (style === "dashed") {
268
+ ctx.setLineDash(dashed);
269
+ } else {
270
+ ctx.setLineDash([]);
271
+ }
272
+ };
238
273
  const clampXViewport = () => {
239
274
  const count = data.length;
240
275
  if (count === 0) {
@@ -242,8 +277,8 @@ function createChart(element, options = {}) {
242
277
  xSpan = 60;
243
278
  return;
244
279
  }
245
- const minSpan = 5;
246
- const maxSpan = Math.max(minSpan, count + maxPanBars * 2);
280
+ const minSpan = minVisibleBars;
281
+ const maxSpan = Math.min(maxVisibleBars, Math.max(minSpan, count + maxPanBars * 2));
247
282
  xSpan = clamp(xSpan, minSpan, maxSpan);
248
283
  xCenter = clamp(xCenter, -maxPanBars, count + maxPanBars);
249
284
  };
@@ -254,8 +289,9 @@ function createChart(element, options = {}) {
254
289
  xSpan = 60;
255
290
  return;
256
291
  }
257
- const requestedVisibleBars = Math.max(5, Math.floor(mergedOptions.initialVisibleBars));
258
- xSpan = Math.min(requestedVisibleBars, Math.max(5, count));
292
+ const maxSpan = Math.min(maxVisibleBars, Math.max(minVisibleBars, count + maxPanBars * 2));
293
+ const requestedVisibleBars = clamp(Math.floor(mergedOptions.initialVisibleBars), minVisibleBars, maxSpan);
294
+ xSpan = requestedVisibleBars;
259
295
  if (mergedOptions.initialViewport === "center") {
260
296
  xCenter = count / 2;
261
297
  } else {
@@ -407,13 +443,7 @@ function createChart(element, options = {}) {
407
443
  ctx.save();
408
444
  ctx.strokeStyle = color;
409
445
  ctx.lineWidth = Math.max(1, mergedLine.thickness);
410
- if (mergedLine.style === "dotted") {
411
- ctx.setLineDash([2, 4]);
412
- } else if (mergedLine.style === "dashed") {
413
- ctx.setLineDash([8, 6]);
414
- } else {
415
- ctx.setLineDash([]);
416
- }
446
+ applyDashPattern(mergedLine.style, dashPatterns.dotted, dashPatterns.dashed);
417
447
  ctx.beginPath();
418
448
  ctx.moveTo(crisp(chartLeft), crisp(lineY));
419
449
  ctx.lineTo(crisp(chartRight), crisp(lineY));
@@ -467,13 +497,7 @@ function createChart(element, options = {}) {
467
497
  ctx.save();
468
498
  ctx.strokeStyle = color;
469
499
  ctx.lineWidth = Math.max(1, mergedLine.thickness);
470
- if (mergedLine.style === "dotted") {
471
- ctx.setLineDash([2, 4]);
472
- } else if (mergedLine.style === "dashed") {
473
- ctx.setLineDash([8, 6]);
474
- } else {
475
- ctx.setLineDash([]);
476
- }
500
+ applyDashPattern(mergedLine.style, dashPatterns.dotted, dashPatterns.dashed);
477
501
  ctx.beginPath();
478
502
  ctx.moveTo(crisp(chartLeft), crisp(lineY));
479
503
  ctx.lineTo(crisp(chartRight), crisp(lineY));
@@ -485,13 +509,11 @@ function createChart(element, options = {}) {
485
509
  ctx.save();
486
510
  ctx.strokeStyle = mergedLine.connectorColor ?? color;
487
511
  ctx.lineWidth = Math.max(1, mergedLine.connectorThickness);
488
- if (mergedLine.connectorStyle === "dotted") {
489
- ctx.setLineDash([2, 5]);
490
- } else if (mergedLine.connectorStyle === "dashed") {
491
- ctx.setLineDash([6, 5]);
492
- } else {
493
- ctx.setLineDash([]);
494
- }
512
+ applyDashPattern(
513
+ mergedLine.connectorStyle,
514
+ dashPatterns.connectorDotted,
515
+ dashPatterns.connectorDashed
516
+ );
495
517
  ctx.beginPath();
496
518
  ctx.moveTo(crisp(connectorX), crisp(lineY));
497
519
  ctx.lineTo(crisp(connectorX), crisp(connectorY));
@@ -594,13 +616,11 @@ function createChart(element, options = {}) {
594
616
  ctx.save();
595
617
  ctx.strokeStyle = actionBorderColor;
596
618
  ctx.lineWidth = 1;
597
- if (actionBorderStyle === "dotted") {
598
- ctx.setLineDash([2, 3]);
599
- } else if (actionBorderStyle === "dashed") {
600
- ctx.setLineDash([6, 4]);
601
- } else {
602
- ctx.setLineDash([]);
603
- }
619
+ applyDashPattern(
620
+ actionBorderStyle,
621
+ dashPatterns.borderDotted,
622
+ dashPatterns.borderDashed
623
+ );
604
624
  strokeRoundedRect(Math.round(actionX), Math.round(actionY), actionW, actionH, actionRadius);
605
625
  ctx.restore();
606
626
  const baseFont = ctx.font;
@@ -939,13 +959,7 @@ function createChart(element, options = {}) {
939
959
  ctx.save();
940
960
  ctx.strokeStyle = crosshair.color;
941
961
  ctx.lineWidth = Math.max(1, crosshair.width);
942
- if (crosshair.style === "dotted") {
943
- ctx.setLineDash([2, 4]);
944
- } else if (crosshair.style === "dashed") {
945
- ctx.setLineDash([8, 6]);
946
- } else {
947
- ctx.setLineDash([]);
948
- }
962
+ applyDashPattern(crosshair.style, dashPatterns.dotted, dashPatterns.dashed);
949
963
  if (crosshair.showVertical) {
950
964
  ctx.beginPath();
951
965
  ctx.moveTo(crisp(cx), crisp(chartTop));
@@ -987,13 +1001,7 @@ function createChart(element, options = {}) {
987
1001
  ctx.save();
988
1002
  ctx.strokeStyle = tickerColor;
989
1003
  ctx.lineWidth = tickerThickness;
990
- if (tickerStyle === "dotted") {
991
- ctx.setLineDash([2, 4]);
992
- } else if (tickerStyle === "dashed") {
993
- ctx.setLineDash([8, 6]);
994
- } else {
995
- ctx.setLineDash([]);
996
- }
1004
+ applyDashPattern(tickerStyle, dashPatterns.dotted, dashPatterns.dashed);
997
1005
  ctx.beginPath();
998
1006
  ctx.moveTo(crisp(chartLeft), crisp(lineY));
999
1007
  ctx.lineTo(crisp(chartRight), crisp(lineY));
@@ -1066,13 +1074,11 @@ function createChart(element, options = {}) {
1066
1074
  ctx.save();
1067
1075
  ctx.strokeStyle = labelBorderColor;
1068
1076
  ctx.lineWidth = labelBorderWidth;
1069
- if (labelBorderStyle === "dotted") {
1070
- ctx.setLineDash([2, 3]);
1071
- } else if (labelBorderStyle === "dashed") {
1072
- ctx.setLineDash([6, 4]);
1073
- } else {
1074
- ctx.setLineDash([]);
1075
- }
1077
+ applyDashPattern(
1078
+ labelBorderStyle,
1079
+ dashPatterns.borderDotted,
1080
+ dashPatterns.borderDashed
1081
+ );
1076
1082
  strokeRoundedRect(Math.round(x), Math.round(y), widthValue, labelHeight, labelRadius);
1077
1083
  ctx.restore();
1078
1084
  };
@@ -1108,8 +1114,8 @@ function createChart(element, options = {}) {
1108
1114
  if (!drawState || data.length === 0) {
1109
1115
  return;
1110
1116
  }
1111
- const minSpan = 5;
1112
- 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));
1113
1119
  const nextSpan = clamp(xSpan * factor, minSpan, maxSpan);
1114
1120
  const anchorRatio = clamp((anchorX - drawState.chartLeft) / drawState.chartWidth, 0, 1);
1115
1121
  const anchorIndex = drawState.xStart + anchorRatio * xSpan;
@@ -1123,8 +1129,8 @@ function createChart(element, options = {}) {
1123
1129
  if (!drawState || data.length === 0) {
1124
1130
  return;
1125
1131
  }
1126
- const minSpan = 5;
1127
- 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));
1128
1134
  const nextSpan = clamp(xSpan * factor, minSpan, maxSpan);
1129
1135
  const rightEdgeIndex = data.length + rightEdgePaddingBars;
1130
1136
  const nextStart = rightEdgeIndex - nextSpan;
@@ -1173,6 +1179,64 @@ function createChart(element, options = {}) {
1173
1179
  }
1174
1180
  draw();
1175
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
+ };
1176
1240
  const getCanvasPoint = (event) => {
1177
1241
  const rect = canvas.getBoundingClientRect();
1178
1242
  return {
@@ -1516,19 +1580,11 @@ function createChart(element, options = {}) {
1516
1580
  return;
1517
1581
  }
1518
1582
  if (region === "y-axis") {
1519
- yMinOverride = null;
1520
- yMaxOverride = null;
1521
- autoYMin = null;
1522
- autoYMax = null;
1583
+ resetYViewport();
1523
1584
  draw();
1524
1585
  return;
1525
1586
  }
1526
- fitXViewport();
1527
- yMinOverride = null;
1528
- yMaxOverride = null;
1529
- autoYMin = null;
1530
- autoYMax = null;
1531
- draw();
1587
+ resetViewport();
1532
1588
  };
1533
1589
  canvas.addEventListener("pointerdown", onPointerDown);
1534
1590
  canvas.addEventListener("pointermove", onPointerMove);
@@ -1552,19 +1608,13 @@ function createChart(element, options = {}) {
1552
1608
  if (data.length === 0) {
1553
1609
  xCenter = 0;
1554
1610
  xSpan = 60;
1555
- yMinOverride = null;
1556
- yMaxOverride = null;
1557
- autoYMin = null;
1558
- autoYMax = null;
1611
+ resetYViewport();
1559
1612
  draw();
1560
1613
  return;
1561
1614
  }
1562
1615
  if (!hadData) {
1563
1616
  fitXViewport();
1564
- yMinOverride = null;
1565
- yMaxOverride = null;
1566
- autoYMin = null;
1567
- autoYMax = null;
1617
+ resetYViewport();
1568
1618
  } else {
1569
1619
  if (mergedOptions.preserveViewportOnDataUpdate) {
1570
1620
  clampXViewport();
@@ -1663,6 +1713,14 @@ function createChart(element, options = {}) {
1663
1713
  onOrderAction,
1664
1714
  onChartClick,
1665
1715
  onCrosshairMove,
1716
+ zoomInX,
1717
+ zoomOutX,
1718
+ zoomInY,
1719
+ zoomOutY,
1720
+ panX,
1721
+ panY,
1722
+ resetViewport,
1723
+ fitContent,
1666
1724
  setDoubleClickEnabled,
1667
1725
  setDoubleClickAction,
1668
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`)
@@ -54,18 +57,19 @@ Top-level options:
54
57
  - `priceLines?: PriceLineOptions[]`
55
58
  - `orderLines?: OrderLineOptions[]`
56
59
  - `tickerLine?: TickerLineOptions`
60
+ - `dashPatterns?: Partial<DashPatternOptions>` (controls dotted/dashed spacing)
57
61
 
58
62
  ### `AxisOptions`
59
63
 
60
- - `lineColor` (default `#94a3b8`)
61
- - `textColor` (default `#94a3b8`)
64
+ - `lineColor` (default `#3b3f47`)
65
+ - `textColor` (default `#a9adb6`)
62
66
  - `fontSize` (default `12`)
63
67
  - `lineWidth` (default `1`)
64
68
 
65
69
  ### `GridOptions`
66
70
 
67
- - `color` (default `#e2e8f0`)
68
- - `opacity` (default `0.9`)
71
+ - `color` (default `#2b2f38`)
72
+ - `opacity` (default `0.38`)
69
73
  - `horizontalLines` (default `true`)
70
74
  - `verticalLines` (default `true`)
71
75
  - `horizontalTickCount` (default `5`)
@@ -126,6 +130,15 @@ watermark: {
126
130
  - `labelTextColor` (default `#0b1220`)
127
131
  - `labelBorderRadius` (default `3`)
128
132
 
133
+ ### `DashPatternOptions`
134
+
135
+ - `dotted` (default `[2, 2]`)
136
+ - `dashed` (default `[8, 6]`)
137
+ - `connectorDotted` (default `[2, 3]`)
138
+ - `connectorDashed` (default `[6, 5]`)
139
+ - `borderDotted` (default `[2, 2]`)
140
+ - `borderDashed` (default `[6, 4]`)
141
+
129
142
  ### `PriceLineOptions`
130
143
 
131
144
  - `id?: string`
@@ -225,6 +238,14 @@ Connector/fill visuals:
225
238
  - `onOrderAction(handler: ((event: OrderActionEvent) => void) | null): void`
226
239
  - `onChartClick(handler: ((event: ChartClickEvent) => void) | null): void`
227
240
  - `onCrosshairMove(handler: ((event: CrosshairMoveEvent) => void) | null): void`
241
+ - `zoomInX(factor?: number): void` (default factor `1.25`)
242
+ - `zoomOutX(factor?: number): void` (default factor `1.25`)
243
+ - `zoomInY(factor?: number): void` (default factor `1.25`)
244
+ - `zoomOutY(factor?: number): void` (default factor `1.25`)
245
+ - `panX(bars: number): void` (negative = left, positive = right)
246
+ - `panY(priceDelta: number): void` (positive = move viewport up)
247
+ - `fitContent(): void` (x-only fit, keeps y zoom)
248
+ - `resetViewport(): void` (fit x + reset y auto-scale)
228
249
  - `setDoubleClickEnabled(enabled: boolean): void`
229
250
  - `setDoubleClickAction(action: "reset" | "placeLimitOrder"): void`
230
251
  - `resize(width?: number, height?: number): void`
package/docs/RECIPES.md CHANGED
@@ -38,6 +38,19 @@ chart.addPriceLine({
38
38
  });
39
39
  ```
40
40
 
41
+ ## Tighten dotted spacing globally
42
+
43
+ ```ts
44
+ const chart = createChart(root, {
45
+ dashPatterns: {
46
+ dotted: [2, 1],
47
+ dashed: [8, 5],
48
+ connectorDotted: [2, 2],
49
+ borderDotted: [2, 1]
50
+ }
51
+ });
52
+ ```
53
+
41
54
  ## Add a draggable pending limit line
42
55
 
43
56
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",