hyperprop-charting-library 0.1.12 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -32,6 +32,32 @@ const data: OhlcDataPoint[] = [
32
32
  chart.setData(data);
33
33
  ```
34
34
 
35
+ ## Dash Pattern Styling
36
+
37
+ You can control dotted/dashed spacing globally with `dashPatterns`:
38
+
39
+ ```ts
40
+ const chart = createChart(root, {
41
+ dashPatterns: {
42
+ dotted: [2, 1],
43
+ connectorDotted: [2, 2],
44
+ borderDotted: [2, 1]
45
+ }
46
+ });
47
+ ```
48
+
49
+ ## Stable Price Labels (No Shake)
50
+
51
+ If fast ticks make right-side labels jitter, keep width stable:
52
+
53
+ ```ts
54
+ const chart = createChart(root, {
55
+ priceDecimals: 2,
56
+ stabilizePriceLabels: true,
57
+ priceLabelMinIntegerDigits: 4
58
+ });
59
+ ```
60
+
35
61
  ## Full Documentation
36
62
 
37
63
  - API reference: `docs/API.md`
@@ -46,8 +72,9 @@ chart.setData(data);
46
72
  - `chart.setData(data)`
47
73
  - `chart.setPriceLines(lines)` / `chart.addPriceLine(line)` / `chart.removePriceLine(id)`
48
74
  - `chart.setOrderLines(lines)` / `chart.addOrderLine(line)` / `chart.updateOrderLine(id, patch)` / `chart.removeOrderLine(id)`
49
- - `chart.onOrderAction(handler)` / `chart.onChartClick(handler)`
75
+ - `chart.onOrderAction(handler)` / `chart.onChartClick(handler)` / `chart.onCrosshairMove(handler)`
50
76
  - `chart.setDoubleClickEnabled(enabled)` / `chart.setDoubleClickAction(action)`
77
+ - `chart.zoomInX()` / `chart.zoomOutX()` / `chart.panX(bars)` / `chart.resetViewport()`
51
78
  - `chart.resize(width, height)` / `chart.destroy()`
52
79
 
53
80
  ## Scope
@@ -68,6 +68,14 @@ var DEFAULT_WATERMARK_OPTIONS = {
68
68
  imageTintColor: "",
69
69
  imageTintOpacity: 1
70
70
  };
71
+ var DEFAULT_DASH_PATTERNS = {
72
+ dotted: [2, 2],
73
+ dashed: [8, 6],
74
+ connectorDotted: [2, 3],
75
+ connectorDashed: [6, 5],
76
+ borderDotted: [2, 2],
77
+ borderDashed: [6, 4]
78
+ };
71
79
  var DEFAULT_PRICE_LINE_OPTIONS = {
72
80
  visible: true,
73
81
  style: "solid",
@@ -100,6 +108,8 @@ var DEFAULT_ORDER_LINE_OPTIONS = {
100
108
  actionButtonFontWeight: 500,
101
109
  actionButtonBorderColor: "#2563eb",
102
110
  actionButtonBorderStyle: "solid",
111
+ actionButtonsInnerGap: 6,
112
+ actionButtonsGroupGap: 8,
103
113
  actionButtons: [],
104
114
  connectorToPrice: Number.NaN,
105
115
  connectorColor: "#2563eb",
@@ -116,6 +126,8 @@ var DEFAULT_OPTIONS = {
116
126
  axisColor: "#7f8289",
117
127
  axis: DEFAULT_AXIS_OPTIONS,
118
128
  priceDecimals: 2,
129
+ stabilizePriceLabels: true,
130
+ priceLabelMinIntegerDigits: 3,
119
131
  initialViewport: "latest",
120
132
  initialVisibleBars: 60,
121
133
  minVisibleBars: 5,
@@ -147,7 +159,8 @@ var DEFAULT_OPTIONS = {
147
159
  labelBackgroundColor: "#38bdf8",
148
160
  labelTextColor: "#0b1220",
149
161
  labelBorderRadius: 3
150
- }
162
+ },
163
+ dashPatterns: DEFAULT_DASH_PATTERNS
151
164
  };
152
165
  var BRAND_LOGO_VIEWBOX_WIDTH = 190;
153
166
  var BRAND_LOGO_VIEWBOX_HEIGHT = 186;
@@ -177,6 +190,10 @@ function createChart(element, options = {}) {
177
190
  tickerLine: {
178
191
  ...DEFAULT_OPTIONS.tickerLine,
179
192
  ...options.tickerLine
193
+ },
194
+ dashPatterns: {
195
+ ...DEFAULT_DASH_PATTERNS,
196
+ ...options.dashPatterns ?? {}
180
197
  }
181
198
  };
182
199
  let width = mergedOptions.width;
@@ -264,6 +281,23 @@ function createChart(element, options = {}) {
264
281
  const clamp = (value, min, max) => {
265
282
  return Math.min(max, Math.max(min, value));
266
283
  };
284
+ const dashPatterns = {
285
+ dotted: mergedOptions.dashPatterns.dotted ?? DEFAULT_DASH_PATTERNS.dotted,
286
+ dashed: mergedOptions.dashPatterns.dashed ?? DEFAULT_DASH_PATTERNS.dashed,
287
+ connectorDotted: mergedOptions.dashPatterns.connectorDotted ?? DEFAULT_DASH_PATTERNS.connectorDotted,
288
+ connectorDashed: mergedOptions.dashPatterns.connectorDashed ?? DEFAULT_DASH_PATTERNS.connectorDashed,
289
+ borderDotted: mergedOptions.dashPatterns.borderDotted ?? DEFAULT_DASH_PATTERNS.borderDotted,
290
+ borderDashed: mergedOptions.dashPatterns.borderDashed ?? DEFAULT_DASH_PATTERNS.borderDashed
291
+ };
292
+ const applyDashPattern = (style, dotted, dashed) => {
293
+ if (style === "dotted") {
294
+ ctx.setLineDash(dotted);
295
+ } else if (style === "dashed") {
296
+ ctx.setLineDash(dashed);
297
+ } else {
298
+ ctx.setLineDash([]);
299
+ }
300
+ };
267
301
  const clampXViewport = () => {
268
302
  const count = data.length;
269
303
  if (count === 0) {
@@ -333,6 +367,27 @@ function createChart(element, options = {}) {
333
367
  const decimals = clamp(Math.round(mergedOptions.priceDecimals), 0, 8);
334
368
  return price.toFixed(decimals);
335
369
  };
370
+ const getStabilizedPriceTemplate = () => {
371
+ const decimals = clamp(Math.round(mergedOptions.priceDecimals), 0, 8);
372
+ const configuredDigits = Math.max(1, Math.floor(mergedOptions.priceLabelMinIntegerDigits));
373
+ let maxAbsPrice = 0;
374
+ for (const point of data) {
375
+ maxAbsPrice = Math.max(maxAbsPrice, Math.abs(point.o), Math.abs(point.h), Math.abs(point.l), Math.abs(point.c));
376
+ }
377
+ const observedDigits = maxAbsPrice >= 1 ? Math.floor(Math.log10(maxAbsPrice)) + 1 : 1;
378
+ const integerDigits = Math.max(configuredDigits, observedDigits);
379
+ const integerPart = "8".repeat(integerDigits);
380
+ const decimalPart = decimals > 0 ? `.${"8".repeat(decimals)}` : "";
381
+ return `${integerPart}${decimalPart}`;
382
+ };
383
+ const getPriceLabelWidth = (priceText, paddingX) => {
384
+ const measured = Math.ceil(ctx.measureText(priceText).width) + paddingX * 2;
385
+ if (!mergedOptions.stabilizePriceLabels) {
386
+ return measured;
387
+ }
388
+ const templateWidth = Math.ceil(ctx.measureText(getStabilizedPriceTemplate()).width) + paddingX * 2;
389
+ return Math.max(measured, templateWidth);
390
+ };
336
391
  const parseData = (nextData) => {
337
392
  return nextData.map((point) => ({
338
393
  time: new Date(point.t),
@@ -437,13 +492,7 @@ function createChart(element, options = {}) {
437
492
  ctx.save();
438
493
  ctx.strokeStyle = color;
439
494
  ctx.lineWidth = Math.max(1, mergedLine.thickness);
440
- if (mergedLine.style === "dotted") {
441
- ctx.setLineDash([2, 4]);
442
- } else if (mergedLine.style === "dashed") {
443
- ctx.setLineDash([8, 6]);
444
- } else {
445
- ctx.setLineDash([]);
446
- }
495
+ applyDashPattern(mergedLine.style, dashPatterns.dotted, dashPatterns.dashed);
447
496
  ctx.beginPath();
448
497
  ctx.moveTo(crisp(chartLeft), crisp(lineY));
449
498
  ctx.lineTo(crisp(chartRight), crisp(lineY));
@@ -457,7 +506,7 @@ function createChart(element, options = {}) {
457
506
  ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
458
507
  const labelPaddingX = 8;
459
508
  const labelHeight = 20;
460
- const labelWidth = Math.ceil(ctx.measureText(labelText).width) + labelPaddingX * 2;
509
+ const labelWidth = mergedLine.label === void 0 ? getPriceLabelWidth(labelText, labelPaddingX) : Math.ceil(ctx.measureText(labelText).width) + labelPaddingX * 2;
461
510
  const labelX = chartRight + 4;
462
511
  const labelY = clamp(lineY - labelHeight / 2, chartTop, chartBottom - labelHeight);
463
512
  ctx.fillStyle = mergedLine.labelBackgroundColor;
@@ -497,13 +546,7 @@ function createChart(element, options = {}) {
497
546
  ctx.save();
498
547
  ctx.strokeStyle = color;
499
548
  ctx.lineWidth = Math.max(1, mergedLine.thickness);
500
- if (mergedLine.style === "dotted") {
501
- ctx.setLineDash([2, 4]);
502
- } else if (mergedLine.style === "dashed") {
503
- ctx.setLineDash([8, 6]);
504
- } else {
505
- ctx.setLineDash([]);
506
- }
549
+ applyDashPattern(mergedLine.style, dashPatterns.dotted, dashPatterns.dashed);
507
550
  ctx.beginPath();
508
551
  ctx.moveTo(crisp(chartLeft), crisp(lineY));
509
552
  ctx.lineTo(crisp(chartRight), crisp(lineY));
@@ -515,13 +558,11 @@ function createChart(element, options = {}) {
515
558
  ctx.save();
516
559
  ctx.strokeStyle = mergedLine.connectorColor ?? color;
517
560
  ctx.lineWidth = Math.max(1, mergedLine.connectorThickness);
518
- if (mergedLine.connectorStyle === "dotted") {
519
- ctx.setLineDash([2, 5]);
520
- } else if (mergedLine.connectorStyle === "dashed") {
521
- ctx.setLineDash([6, 5]);
522
- } else {
523
- ctx.setLineDash([]);
524
- }
561
+ applyDashPattern(
562
+ mergedLine.connectorStyle,
563
+ dashPatterns.connectorDotted,
564
+ dashPatterns.connectorDashed
565
+ );
525
566
  ctx.beginPath();
526
567
  ctx.moveTo(crisp(connectorX), crisp(lineY));
527
568
  ctx.lineTo(crisp(connectorX), crisp(connectorY));
@@ -567,9 +608,9 @@ function createChart(element, options = {}) {
567
608
  const width2 = Math.max(minWidth, Math.ceil(ctx.measureText(button.text).width) + paddingX * 2);
568
609
  return { button, width: width2 };
569
610
  });
570
- const actionButtonInnerGap = actionButtonMetrics.length > 1 ? 4 : 0;
611
+ const actionButtonInnerGap = actionButtonMetrics.length > 1 ? Math.max(0, mergedLine.actionButtonsInnerGap) : 0;
571
612
  const actionButtonsTotalWidth = actionButtonMetrics.reduce((sum, metric) => sum + metric.width, 0) + Math.max(0, actionButtonMetrics.length - 1) * actionButtonInnerGap;
572
- const actionButtonsGap = actionButtonMetrics.length > 0 ? 6 : 0;
613
+ const actionButtonsGap = actionButtonMetrics.length > 0 ? Math.max(0, mergedLine.actionButtonsGroupGap) : 0;
573
614
  const segmentPaddingX = 8;
574
615
  const labelHeight = 22;
575
616
  const qtyWidth = qtyText ? Math.ceil(ctx.measureText(qtyText).width) + segmentPaddingX * 2 : 0;
@@ -624,13 +665,11 @@ function createChart(element, options = {}) {
624
665
  ctx.save();
625
666
  ctx.strokeStyle = actionBorderColor;
626
667
  ctx.lineWidth = 1;
627
- if (actionBorderStyle === "dotted") {
628
- ctx.setLineDash([2, 3]);
629
- } else if (actionBorderStyle === "dashed") {
630
- ctx.setLineDash([6, 4]);
631
- } else {
632
- ctx.setLineDash([]);
633
- }
668
+ applyDashPattern(
669
+ actionBorderStyle,
670
+ dashPatterns.borderDotted,
671
+ dashPatterns.borderDashed
672
+ );
634
673
  strokeRoundedRect(Math.round(actionX), Math.round(actionY), actionW, actionH, actionRadius);
635
674
  ctx.restore();
636
675
  const baseFont = ctx.font;
@@ -695,7 +734,7 @@ function createChart(element, options = {}) {
695
734
  }
696
735
  const priceText = formatPrice(renderPrice);
697
736
  const pricePaddingX = 8;
698
- const measuredPriceWidth = Math.ceil(ctx.measureText(priceText).width) + pricePaddingX * 2;
737
+ const measuredPriceWidth = getPriceLabelWidth(priceText, pricePaddingX);
699
738
  const priceWidth = mergedLine.id === void 0 ? measuredPriceWidth : Math.max(measuredPriceWidth, orderPriceTagWidthById.get(mergedLine.id) ?? 0);
700
739
  if (mergedLine.id) {
701
740
  orderPriceTagWidthById.set(mergedLine.id, priceWidth);
@@ -969,13 +1008,7 @@ function createChart(element, options = {}) {
969
1008
  ctx.save();
970
1009
  ctx.strokeStyle = crosshair.color;
971
1010
  ctx.lineWidth = Math.max(1, crosshair.width);
972
- if (crosshair.style === "dotted") {
973
- ctx.setLineDash([2, 4]);
974
- } else if (crosshair.style === "dashed") {
975
- ctx.setLineDash([8, 6]);
976
- } else {
977
- ctx.setLineDash([]);
978
- }
1011
+ applyDashPattern(crosshair.style, dashPatterns.dotted, dashPatterns.dashed);
979
1012
  if (crosshair.showVertical) {
980
1013
  ctx.beginPath();
981
1014
  ctx.moveTo(crisp(cx), crisp(chartTop));
@@ -1017,13 +1050,7 @@ function createChart(element, options = {}) {
1017
1050
  ctx.save();
1018
1051
  ctx.strokeStyle = tickerColor;
1019
1052
  ctx.lineWidth = tickerThickness;
1020
- if (tickerStyle === "dotted") {
1021
- ctx.setLineDash([2, 4]);
1022
- } else if (tickerStyle === "dashed") {
1023
- ctx.setLineDash([8, 6]);
1024
- } else {
1025
- ctx.setLineDash([]);
1026
- }
1053
+ applyDashPattern(tickerStyle, dashPatterns.dotted, dashPatterns.dashed);
1027
1054
  ctx.beginPath();
1028
1055
  ctx.moveTo(crisp(chartLeft), crisp(lineY));
1029
1056
  ctx.lineTo(crisp(chartRight), crisp(lineY));
@@ -1034,7 +1061,7 @@ function createChart(element, options = {}) {
1034
1061
  ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
1035
1062
  const labelPaddingX = 8;
1036
1063
  const labelHeight = 20;
1037
- const labelWidth = Math.ceil(ctx.measureText(tickerLabel).width) + labelPaddingX * 2;
1064
+ const labelWidth = getPriceLabelWidth(tickerLabel, labelPaddingX);
1038
1065
  const labelX = chartRight + 4;
1039
1066
  const labelY = clamp(lineY - labelHeight / 2, chartTop, chartBottom - labelHeight);
1040
1067
  const labelRadius = Math.max(0, ticker.labelBorderRadius ?? 0);
@@ -1096,20 +1123,18 @@ function createChart(element, options = {}) {
1096
1123
  ctx.save();
1097
1124
  ctx.strokeStyle = labelBorderColor;
1098
1125
  ctx.lineWidth = labelBorderWidth;
1099
- if (labelBorderStyle === "dotted") {
1100
- ctx.setLineDash([2, 3]);
1101
- } else if (labelBorderStyle === "dashed") {
1102
- ctx.setLineDash([6, 4]);
1103
- } else {
1104
- ctx.setLineDash([]);
1105
- }
1126
+ applyDashPattern(
1127
+ labelBorderStyle,
1128
+ dashPatterns.borderDotted,
1129
+ dashPatterns.borderDashed
1130
+ );
1106
1131
  strokeRoundedRect(Math.round(x), Math.round(y), widthValue, labelHeight, labelRadius);
1107
1132
  ctx.restore();
1108
1133
  };
1109
1134
  if (crosshair.showPriceLabel) {
1110
1135
  const hoverPrice = yMin + (chartBottom - cy) / chartHeight * yRange;
1111
1136
  const priceText = formatPrice(hoverPrice);
1112
- const priceWidth = Math.ceil(ctx.measureText(priceText).width) + labelPaddingX * 2;
1137
+ const priceWidth = getPriceLabelWidth(priceText, labelPaddingX);
1113
1138
  const priceX = chartRight + 4;
1114
1139
  const priceY = clamp(cy - labelHeight / 2, chartTop, chartBottom - labelHeight);
1115
1140
  ctx.fillStyle = labelBackground;
@@ -5,6 +5,8 @@ interface ChartOptions {
5
5
  axisColor?: string;
6
6
  axis?: AxisOptions;
7
7
  priceDecimals?: number;
8
+ stabilizePriceLabels?: boolean;
9
+ priceLabelMinIntegerDigits?: number;
8
10
  initialViewport?: "latest" | "center";
9
11
  initialVisibleBars?: number;
10
12
  minVisibleBars?: number;
@@ -29,6 +31,15 @@ interface ChartOptions {
29
31
  priceLines?: PriceLineOptions[];
30
32
  orderLines?: OrderLineOptions[];
31
33
  tickerLine?: TickerLineOptions;
34
+ dashPatterns?: Partial<DashPatternOptions>;
35
+ }
36
+ interface DashPatternOptions {
37
+ dotted: [number, number];
38
+ dashed: [number, number];
39
+ connectorDotted: [number, number];
40
+ connectorDashed: [number, number];
41
+ borderDotted: [number, number];
42
+ borderDashed: [number, number];
32
43
  }
33
44
  interface AxisOptions {
34
45
  lineColor?: string;
@@ -119,6 +130,8 @@ interface OrderLineOptions {
119
130
  actionButtonFontWeight?: number | string;
120
131
  actionButtonBorderColor?: string;
121
132
  actionButtonBorderStyle?: "solid" | "dotted" | "dashed";
133
+ actionButtonsInnerGap?: number;
134
+ actionButtonsGroupGap?: number;
122
135
  actionButtons?: OrderActionButton[];
123
136
  connectorToPrice?: number;
124
137
  connectorColor?: string;
@@ -208,4 +221,4 @@ interface OhlcDataPoint {
208
221
  }
209
222
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
210
223
 
211
- export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type GridOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
224
+ export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type DashPatternOptions, type GridOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
@@ -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",
@@ -76,6 +84,8 @@ var DEFAULT_ORDER_LINE_OPTIONS = {
76
84
  actionButtonFontWeight: 500,
77
85
  actionButtonBorderColor: "#2563eb",
78
86
  actionButtonBorderStyle: "solid",
87
+ actionButtonsInnerGap: 6,
88
+ actionButtonsGroupGap: 8,
79
89
  actionButtons: [],
80
90
  connectorToPrice: Number.NaN,
81
91
  connectorColor: "#2563eb",
@@ -92,6 +102,8 @@ var DEFAULT_OPTIONS = {
92
102
  axisColor: "#7f8289",
93
103
  axis: DEFAULT_AXIS_OPTIONS,
94
104
  priceDecimals: 2,
105
+ stabilizePriceLabels: true,
106
+ priceLabelMinIntegerDigits: 3,
95
107
  initialViewport: "latest",
96
108
  initialVisibleBars: 60,
97
109
  minVisibleBars: 5,
@@ -123,7 +135,8 @@ var DEFAULT_OPTIONS = {
123
135
  labelBackgroundColor: "#38bdf8",
124
136
  labelTextColor: "#0b1220",
125
137
  labelBorderRadius: 3
126
- }
138
+ },
139
+ dashPatterns: DEFAULT_DASH_PATTERNS
127
140
  };
128
141
  var BRAND_LOGO_VIEWBOX_WIDTH = 190;
129
142
  var BRAND_LOGO_VIEWBOX_HEIGHT = 186;
@@ -153,6 +166,10 @@ function createChart(element, options = {}) {
153
166
  tickerLine: {
154
167
  ...DEFAULT_OPTIONS.tickerLine,
155
168
  ...options.tickerLine
169
+ },
170
+ dashPatterns: {
171
+ ...DEFAULT_DASH_PATTERNS,
172
+ ...options.dashPatterns ?? {}
156
173
  }
157
174
  };
158
175
  let width = mergedOptions.width;
@@ -240,6 +257,23 @@ function createChart(element, options = {}) {
240
257
  const clamp = (value, min, max) => {
241
258
  return Math.min(max, Math.max(min, value));
242
259
  };
260
+ const dashPatterns = {
261
+ dotted: mergedOptions.dashPatterns.dotted ?? DEFAULT_DASH_PATTERNS.dotted,
262
+ dashed: mergedOptions.dashPatterns.dashed ?? DEFAULT_DASH_PATTERNS.dashed,
263
+ connectorDotted: mergedOptions.dashPatterns.connectorDotted ?? DEFAULT_DASH_PATTERNS.connectorDotted,
264
+ connectorDashed: mergedOptions.dashPatterns.connectorDashed ?? DEFAULT_DASH_PATTERNS.connectorDashed,
265
+ borderDotted: mergedOptions.dashPatterns.borderDotted ?? DEFAULT_DASH_PATTERNS.borderDotted,
266
+ borderDashed: mergedOptions.dashPatterns.borderDashed ?? DEFAULT_DASH_PATTERNS.borderDashed
267
+ };
268
+ const applyDashPattern = (style, dotted, dashed) => {
269
+ if (style === "dotted") {
270
+ ctx.setLineDash(dotted);
271
+ } else if (style === "dashed") {
272
+ ctx.setLineDash(dashed);
273
+ } else {
274
+ ctx.setLineDash([]);
275
+ }
276
+ };
243
277
  const clampXViewport = () => {
244
278
  const count = data.length;
245
279
  if (count === 0) {
@@ -309,6 +343,27 @@ function createChart(element, options = {}) {
309
343
  const decimals = clamp(Math.round(mergedOptions.priceDecimals), 0, 8);
310
344
  return price.toFixed(decimals);
311
345
  };
346
+ const getStabilizedPriceTemplate = () => {
347
+ const decimals = clamp(Math.round(mergedOptions.priceDecimals), 0, 8);
348
+ const configuredDigits = Math.max(1, Math.floor(mergedOptions.priceLabelMinIntegerDigits));
349
+ let maxAbsPrice = 0;
350
+ for (const point of data) {
351
+ maxAbsPrice = Math.max(maxAbsPrice, Math.abs(point.o), Math.abs(point.h), Math.abs(point.l), Math.abs(point.c));
352
+ }
353
+ const observedDigits = maxAbsPrice >= 1 ? Math.floor(Math.log10(maxAbsPrice)) + 1 : 1;
354
+ const integerDigits = Math.max(configuredDigits, observedDigits);
355
+ const integerPart = "8".repeat(integerDigits);
356
+ const decimalPart = decimals > 0 ? `.${"8".repeat(decimals)}` : "";
357
+ return `${integerPart}${decimalPart}`;
358
+ };
359
+ const getPriceLabelWidth = (priceText, paddingX) => {
360
+ const measured = Math.ceil(ctx.measureText(priceText).width) + paddingX * 2;
361
+ if (!mergedOptions.stabilizePriceLabels) {
362
+ return measured;
363
+ }
364
+ const templateWidth = Math.ceil(ctx.measureText(getStabilizedPriceTemplate()).width) + paddingX * 2;
365
+ return Math.max(measured, templateWidth);
366
+ };
312
367
  const parseData = (nextData) => {
313
368
  return nextData.map((point) => ({
314
369
  time: new Date(point.t),
@@ -413,13 +468,7 @@ function createChart(element, options = {}) {
413
468
  ctx.save();
414
469
  ctx.strokeStyle = color;
415
470
  ctx.lineWidth = Math.max(1, mergedLine.thickness);
416
- if (mergedLine.style === "dotted") {
417
- ctx.setLineDash([2, 4]);
418
- } else if (mergedLine.style === "dashed") {
419
- ctx.setLineDash([8, 6]);
420
- } else {
421
- ctx.setLineDash([]);
422
- }
471
+ applyDashPattern(mergedLine.style, dashPatterns.dotted, dashPatterns.dashed);
423
472
  ctx.beginPath();
424
473
  ctx.moveTo(crisp(chartLeft), crisp(lineY));
425
474
  ctx.lineTo(crisp(chartRight), crisp(lineY));
@@ -433,7 +482,7 @@ function createChart(element, options = {}) {
433
482
  ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
434
483
  const labelPaddingX = 8;
435
484
  const labelHeight = 20;
436
- const labelWidth = Math.ceil(ctx.measureText(labelText).width) + labelPaddingX * 2;
485
+ const labelWidth = mergedLine.label === void 0 ? getPriceLabelWidth(labelText, labelPaddingX) : Math.ceil(ctx.measureText(labelText).width) + labelPaddingX * 2;
437
486
  const labelX = chartRight + 4;
438
487
  const labelY = clamp(lineY - labelHeight / 2, chartTop, chartBottom - labelHeight);
439
488
  ctx.fillStyle = mergedLine.labelBackgroundColor;
@@ -473,13 +522,7 @@ function createChart(element, options = {}) {
473
522
  ctx.save();
474
523
  ctx.strokeStyle = color;
475
524
  ctx.lineWidth = Math.max(1, mergedLine.thickness);
476
- if (mergedLine.style === "dotted") {
477
- ctx.setLineDash([2, 4]);
478
- } else if (mergedLine.style === "dashed") {
479
- ctx.setLineDash([8, 6]);
480
- } else {
481
- ctx.setLineDash([]);
482
- }
525
+ applyDashPattern(mergedLine.style, dashPatterns.dotted, dashPatterns.dashed);
483
526
  ctx.beginPath();
484
527
  ctx.moveTo(crisp(chartLeft), crisp(lineY));
485
528
  ctx.lineTo(crisp(chartRight), crisp(lineY));
@@ -491,13 +534,11 @@ function createChart(element, options = {}) {
491
534
  ctx.save();
492
535
  ctx.strokeStyle = mergedLine.connectorColor ?? color;
493
536
  ctx.lineWidth = Math.max(1, mergedLine.connectorThickness);
494
- if (mergedLine.connectorStyle === "dotted") {
495
- ctx.setLineDash([2, 5]);
496
- } else if (mergedLine.connectorStyle === "dashed") {
497
- ctx.setLineDash([6, 5]);
498
- } else {
499
- ctx.setLineDash([]);
500
- }
537
+ applyDashPattern(
538
+ mergedLine.connectorStyle,
539
+ dashPatterns.connectorDotted,
540
+ dashPatterns.connectorDashed
541
+ );
501
542
  ctx.beginPath();
502
543
  ctx.moveTo(crisp(connectorX), crisp(lineY));
503
544
  ctx.lineTo(crisp(connectorX), crisp(connectorY));
@@ -543,9 +584,9 @@ function createChart(element, options = {}) {
543
584
  const width2 = Math.max(minWidth, Math.ceil(ctx.measureText(button.text).width) + paddingX * 2);
544
585
  return { button, width: width2 };
545
586
  });
546
- const actionButtonInnerGap = actionButtonMetrics.length > 1 ? 4 : 0;
587
+ const actionButtonInnerGap = actionButtonMetrics.length > 1 ? Math.max(0, mergedLine.actionButtonsInnerGap) : 0;
547
588
  const actionButtonsTotalWidth = actionButtonMetrics.reduce((sum, metric) => sum + metric.width, 0) + Math.max(0, actionButtonMetrics.length - 1) * actionButtonInnerGap;
548
- const actionButtonsGap = actionButtonMetrics.length > 0 ? 6 : 0;
589
+ const actionButtonsGap = actionButtonMetrics.length > 0 ? Math.max(0, mergedLine.actionButtonsGroupGap) : 0;
549
590
  const segmentPaddingX = 8;
550
591
  const labelHeight = 22;
551
592
  const qtyWidth = qtyText ? Math.ceil(ctx.measureText(qtyText).width) + segmentPaddingX * 2 : 0;
@@ -600,13 +641,11 @@ function createChart(element, options = {}) {
600
641
  ctx.save();
601
642
  ctx.strokeStyle = actionBorderColor;
602
643
  ctx.lineWidth = 1;
603
- if (actionBorderStyle === "dotted") {
604
- ctx.setLineDash([2, 3]);
605
- } else if (actionBorderStyle === "dashed") {
606
- ctx.setLineDash([6, 4]);
607
- } else {
608
- ctx.setLineDash([]);
609
- }
644
+ applyDashPattern(
645
+ actionBorderStyle,
646
+ dashPatterns.borderDotted,
647
+ dashPatterns.borderDashed
648
+ );
610
649
  strokeRoundedRect(Math.round(actionX), Math.round(actionY), actionW, actionH, actionRadius);
611
650
  ctx.restore();
612
651
  const baseFont = ctx.font;
@@ -671,7 +710,7 @@ function createChart(element, options = {}) {
671
710
  }
672
711
  const priceText = formatPrice(renderPrice);
673
712
  const pricePaddingX = 8;
674
- const measuredPriceWidth = Math.ceil(ctx.measureText(priceText).width) + pricePaddingX * 2;
713
+ const measuredPriceWidth = getPriceLabelWidth(priceText, pricePaddingX);
675
714
  const priceWidth = mergedLine.id === void 0 ? measuredPriceWidth : Math.max(measuredPriceWidth, orderPriceTagWidthById.get(mergedLine.id) ?? 0);
676
715
  if (mergedLine.id) {
677
716
  orderPriceTagWidthById.set(mergedLine.id, priceWidth);
@@ -945,13 +984,7 @@ function createChart(element, options = {}) {
945
984
  ctx.save();
946
985
  ctx.strokeStyle = crosshair.color;
947
986
  ctx.lineWidth = Math.max(1, crosshair.width);
948
- if (crosshair.style === "dotted") {
949
- ctx.setLineDash([2, 4]);
950
- } else if (crosshair.style === "dashed") {
951
- ctx.setLineDash([8, 6]);
952
- } else {
953
- ctx.setLineDash([]);
954
- }
987
+ applyDashPattern(crosshair.style, dashPatterns.dotted, dashPatterns.dashed);
955
988
  if (crosshair.showVertical) {
956
989
  ctx.beginPath();
957
990
  ctx.moveTo(crisp(cx), crisp(chartTop));
@@ -993,13 +1026,7 @@ function createChart(element, options = {}) {
993
1026
  ctx.save();
994
1027
  ctx.strokeStyle = tickerColor;
995
1028
  ctx.lineWidth = tickerThickness;
996
- if (tickerStyle === "dotted") {
997
- ctx.setLineDash([2, 4]);
998
- } else if (tickerStyle === "dashed") {
999
- ctx.setLineDash([8, 6]);
1000
- } else {
1001
- ctx.setLineDash([]);
1002
- }
1029
+ applyDashPattern(tickerStyle, dashPatterns.dotted, dashPatterns.dashed);
1003
1030
  ctx.beginPath();
1004
1031
  ctx.moveTo(crisp(chartLeft), crisp(lineY));
1005
1032
  ctx.lineTo(crisp(chartRight), crisp(lineY));
@@ -1010,7 +1037,7 @@ function createChart(element, options = {}) {
1010
1037
  ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
1011
1038
  const labelPaddingX = 8;
1012
1039
  const labelHeight = 20;
1013
- const labelWidth = Math.ceil(ctx.measureText(tickerLabel).width) + labelPaddingX * 2;
1040
+ const labelWidth = getPriceLabelWidth(tickerLabel, labelPaddingX);
1014
1041
  const labelX = chartRight + 4;
1015
1042
  const labelY = clamp(lineY - labelHeight / 2, chartTop, chartBottom - labelHeight);
1016
1043
  const labelRadius = Math.max(0, ticker.labelBorderRadius ?? 0);
@@ -1072,20 +1099,18 @@ function createChart(element, options = {}) {
1072
1099
  ctx.save();
1073
1100
  ctx.strokeStyle = labelBorderColor;
1074
1101
  ctx.lineWidth = labelBorderWidth;
1075
- if (labelBorderStyle === "dotted") {
1076
- ctx.setLineDash([2, 3]);
1077
- } else if (labelBorderStyle === "dashed") {
1078
- ctx.setLineDash([6, 4]);
1079
- } else {
1080
- ctx.setLineDash([]);
1081
- }
1102
+ applyDashPattern(
1103
+ labelBorderStyle,
1104
+ dashPatterns.borderDotted,
1105
+ dashPatterns.borderDashed
1106
+ );
1082
1107
  strokeRoundedRect(Math.round(x), Math.round(y), widthValue, labelHeight, labelRadius);
1083
1108
  ctx.restore();
1084
1109
  };
1085
1110
  if (crosshair.showPriceLabel) {
1086
1111
  const hoverPrice = yMin + (chartBottom - cy) / chartHeight * yRange;
1087
1112
  const priceText = formatPrice(hoverPrice);
1088
- const priceWidth = Math.ceil(ctx.measureText(priceText).width) + labelPaddingX * 2;
1113
+ const priceWidth = getPriceLabelWidth(priceText, labelPaddingX);
1089
1114
  const priceX = chartRight + 4;
1090
1115
  const priceY = clamp(cy - labelHeight / 2, chartTop, chartBottom - labelHeight);
1091
1116
  ctx.fillStyle = labelBackground;
package/dist/index.cjs CHANGED
@@ -68,6 +68,14 @@ var DEFAULT_WATERMARK_OPTIONS = {
68
68
  imageTintColor: "",
69
69
  imageTintOpacity: 1
70
70
  };
71
+ var DEFAULT_DASH_PATTERNS = {
72
+ dotted: [2, 2],
73
+ dashed: [8, 6],
74
+ connectorDotted: [2, 3],
75
+ connectorDashed: [6, 5],
76
+ borderDotted: [2, 2],
77
+ borderDashed: [6, 4]
78
+ };
71
79
  var DEFAULT_PRICE_LINE_OPTIONS = {
72
80
  visible: true,
73
81
  style: "solid",
@@ -100,6 +108,8 @@ var DEFAULT_ORDER_LINE_OPTIONS = {
100
108
  actionButtonFontWeight: 500,
101
109
  actionButtonBorderColor: "#2563eb",
102
110
  actionButtonBorderStyle: "solid",
111
+ actionButtonsInnerGap: 6,
112
+ actionButtonsGroupGap: 8,
103
113
  actionButtons: [],
104
114
  connectorToPrice: Number.NaN,
105
115
  connectorColor: "#2563eb",
@@ -116,6 +126,8 @@ var DEFAULT_OPTIONS = {
116
126
  axisColor: "#7f8289",
117
127
  axis: DEFAULT_AXIS_OPTIONS,
118
128
  priceDecimals: 2,
129
+ stabilizePriceLabels: true,
130
+ priceLabelMinIntegerDigits: 3,
119
131
  initialViewport: "latest",
120
132
  initialVisibleBars: 60,
121
133
  minVisibleBars: 5,
@@ -147,7 +159,8 @@ var DEFAULT_OPTIONS = {
147
159
  labelBackgroundColor: "#38bdf8",
148
160
  labelTextColor: "#0b1220",
149
161
  labelBorderRadius: 3
150
- }
162
+ },
163
+ dashPatterns: DEFAULT_DASH_PATTERNS
151
164
  };
152
165
  var BRAND_LOGO_VIEWBOX_WIDTH = 190;
153
166
  var BRAND_LOGO_VIEWBOX_HEIGHT = 186;
@@ -177,6 +190,10 @@ function createChart(element, options = {}) {
177
190
  tickerLine: {
178
191
  ...DEFAULT_OPTIONS.tickerLine,
179
192
  ...options.tickerLine
193
+ },
194
+ dashPatterns: {
195
+ ...DEFAULT_DASH_PATTERNS,
196
+ ...options.dashPatterns ?? {}
180
197
  }
181
198
  };
182
199
  let width = mergedOptions.width;
@@ -264,6 +281,23 @@ function createChart(element, options = {}) {
264
281
  const clamp = (value, min, max) => {
265
282
  return Math.min(max, Math.max(min, value));
266
283
  };
284
+ const dashPatterns = {
285
+ dotted: mergedOptions.dashPatterns.dotted ?? DEFAULT_DASH_PATTERNS.dotted,
286
+ dashed: mergedOptions.dashPatterns.dashed ?? DEFAULT_DASH_PATTERNS.dashed,
287
+ connectorDotted: mergedOptions.dashPatterns.connectorDotted ?? DEFAULT_DASH_PATTERNS.connectorDotted,
288
+ connectorDashed: mergedOptions.dashPatterns.connectorDashed ?? DEFAULT_DASH_PATTERNS.connectorDashed,
289
+ borderDotted: mergedOptions.dashPatterns.borderDotted ?? DEFAULT_DASH_PATTERNS.borderDotted,
290
+ borderDashed: mergedOptions.dashPatterns.borderDashed ?? DEFAULT_DASH_PATTERNS.borderDashed
291
+ };
292
+ const applyDashPattern = (style, dotted, dashed) => {
293
+ if (style === "dotted") {
294
+ ctx.setLineDash(dotted);
295
+ } else if (style === "dashed") {
296
+ ctx.setLineDash(dashed);
297
+ } else {
298
+ ctx.setLineDash([]);
299
+ }
300
+ };
267
301
  const clampXViewport = () => {
268
302
  const count = data.length;
269
303
  if (count === 0) {
@@ -333,6 +367,27 @@ function createChart(element, options = {}) {
333
367
  const decimals = clamp(Math.round(mergedOptions.priceDecimals), 0, 8);
334
368
  return price.toFixed(decimals);
335
369
  };
370
+ const getStabilizedPriceTemplate = () => {
371
+ const decimals = clamp(Math.round(mergedOptions.priceDecimals), 0, 8);
372
+ const configuredDigits = Math.max(1, Math.floor(mergedOptions.priceLabelMinIntegerDigits));
373
+ let maxAbsPrice = 0;
374
+ for (const point of data) {
375
+ maxAbsPrice = Math.max(maxAbsPrice, Math.abs(point.o), Math.abs(point.h), Math.abs(point.l), Math.abs(point.c));
376
+ }
377
+ const observedDigits = maxAbsPrice >= 1 ? Math.floor(Math.log10(maxAbsPrice)) + 1 : 1;
378
+ const integerDigits = Math.max(configuredDigits, observedDigits);
379
+ const integerPart = "8".repeat(integerDigits);
380
+ const decimalPart = decimals > 0 ? `.${"8".repeat(decimals)}` : "";
381
+ return `${integerPart}${decimalPart}`;
382
+ };
383
+ const getPriceLabelWidth = (priceText, paddingX) => {
384
+ const measured = Math.ceil(ctx.measureText(priceText).width) + paddingX * 2;
385
+ if (!mergedOptions.stabilizePriceLabels) {
386
+ return measured;
387
+ }
388
+ const templateWidth = Math.ceil(ctx.measureText(getStabilizedPriceTemplate()).width) + paddingX * 2;
389
+ return Math.max(measured, templateWidth);
390
+ };
336
391
  const parseData = (nextData) => {
337
392
  return nextData.map((point) => ({
338
393
  time: new Date(point.t),
@@ -437,13 +492,7 @@ function createChart(element, options = {}) {
437
492
  ctx.save();
438
493
  ctx.strokeStyle = color;
439
494
  ctx.lineWidth = Math.max(1, mergedLine.thickness);
440
- if (mergedLine.style === "dotted") {
441
- ctx.setLineDash([2, 4]);
442
- } else if (mergedLine.style === "dashed") {
443
- ctx.setLineDash([8, 6]);
444
- } else {
445
- ctx.setLineDash([]);
446
- }
495
+ applyDashPattern(mergedLine.style, dashPatterns.dotted, dashPatterns.dashed);
447
496
  ctx.beginPath();
448
497
  ctx.moveTo(crisp(chartLeft), crisp(lineY));
449
498
  ctx.lineTo(crisp(chartRight), crisp(lineY));
@@ -457,7 +506,7 @@ function createChart(element, options = {}) {
457
506
  ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
458
507
  const labelPaddingX = 8;
459
508
  const labelHeight = 20;
460
- const labelWidth = Math.ceil(ctx.measureText(labelText).width) + labelPaddingX * 2;
509
+ const labelWidth = mergedLine.label === void 0 ? getPriceLabelWidth(labelText, labelPaddingX) : Math.ceil(ctx.measureText(labelText).width) + labelPaddingX * 2;
461
510
  const labelX = chartRight + 4;
462
511
  const labelY = clamp(lineY - labelHeight / 2, chartTop, chartBottom - labelHeight);
463
512
  ctx.fillStyle = mergedLine.labelBackgroundColor;
@@ -497,13 +546,7 @@ function createChart(element, options = {}) {
497
546
  ctx.save();
498
547
  ctx.strokeStyle = color;
499
548
  ctx.lineWidth = Math.max(1, mergedLine.thickness);
500
- if (mergedLine.style === "dotted") {
501
- ctx.setLineDash([2, 4]);
502
- } else if (mergedLine.style === "dashed") {
503
- ctx.setLineDash([8, 6]);
504
- } else {
505
- ctx.setLineDash([]);
506
- }
549
+ applyDashPattern(mergedLine.style, dashPatterns.dotted, dashPatterns.dashed);
507
550
  ctx.beginPath();
508
551
  ctx.moveTo(crisp(chartLeft), crisp(lineY));
509
552
  ctx.lineTo(crisp(chartRight), crisp(lineY));
@@ -515,13 +558,11 @@ function createChart(element, options = {}) {
515
558
  ctx.save();
516
559
  ctx.strokeStyle = mergedLine.connectorColor ?? color;
517
560
  ctx.lineWidth = Math.max(1, mergedLine.connectorThickness);
518
- if (mergedLine.connectorStyle === "dotted") {
519
- ctx.setLineDash([2, 5]);
520
- } else if (mergedLine.connectorStyle === "dashed") {
521
- ctx.setLineDash([6, 5]);
522
- } else {
523
- ctx.setLineDash([]);
524
- }
561
+ applyDashPattern(
562
+ mergedLine.connectorStyle,
563
+ dashPatterns.connectorDotted,
564
+ dashPatterns.connectorDashed
565
+ );
525
566
  ctx.beginPath();
526
567
  ctx.moveTo(crisp(connectorX), crisp(lineY));
527
568
  ctx.lineTo(crisp(connectorX), crisp(connectorY));
@@ -567,9 +608,9 @@ function createChart(element, options = {}) {
567
608
  const width2 = Math.max(minWidth, Math.ceil(ctx.measureText(button.text).width) + paddingX * 2);
568
609
  return { button, width: width2 };
569
610
  });
570
- const actionButtonInnerGap = actionButtonMetrics.length > 1 ? 4 : 0;
611
+ const actionButtonInnerGap = actionButtonMetrics.length > 1 ? Math.max(0, mergedLine.actionButtonsInnerGap) : 0;
571
612
  const actionButtonsTotalWidth = actionButtonMetrics.reduce((sum, metric) => sum + metric.width, 0) + Math.max(0, actionButtonMetrics.length - 1) * actionButtonInnerGap;
572
- const actionButtonsGap = actionButtonMetrics.length > 0 ? 6 : 0;
613
+ const actionButtonsGap = actionButtonMetrics.length > 0 ? Math.max(0, mergedLine.actionButtonsGroupGap) : 0;
573
614
  const segmentPaddingX = 8;
574
615
  const labelHeight = 22;
575
616
  const qtyWidth = qtyText ? Math.ceil(ctx.measureText(qtyText).width) + segmentPaddingX * 2 : 0;
@@ -624,13 +665,11 @@ function createChart(element, options = {}) {
624
665
  ctx.save();
625
666
  ctx.strokeStyle = actionBorderColor;
626
667
  ctx.lineWidth = 1;
627
- if (actionBorderStyle === "dotted") {
628
- ctx.setLineDash([2, 3]);
629
- } else if (actionBorderStyle === "dashed") {
630
- ctx.setLineDash([6, 4]);
631
- } else {
632
- ctx.setLineDash([]);
633
- }
668
+ applyDashPattern(
669
+ actionBorderStyle,
670
+ dashPatterns.borderDotted,
671
+ dashPatterns.borderDashed
672
+ );
634
673
  strokeRoundedRect(Math.round(actionX), Math.round(actionY), actionW, actionH, actionRadius);
635
674
  ctx.restore();
636
675
  const baseFont = ctx.font;
@@ -695,7 +734,7 @@ function createChart(element, options = {}) {
695
734
  }
696
735
  const priceText = formatPrice(renderPrice);
697
736
  const pricePaddingX = 8;
698
- const measuredPriceWidth = Math.ceil(ctx.measureText(priceText).width) + pricePaddingX * 2;
737
+ const measuredPriceWidth = getPriceLabelWidth(priceText, pricePaddingX);
699
738
  const priceWidth = mergedLine.id === void 0 ? measuredPriceWidth : Math.max(measuredPriceWidth, orderPriceTagWidthById.get(mergedLine.id) ?? 0);
700
739
  if (mergedLine.id) {
701
740
  orderPriceTagWidthById.set(mergedLine.id, priceWidth);
@@ -969,13 +1008,7 @@ function createChart(element, options = {}) {
969
1008
  ctx.save();
970
1009
  ctx.strokeStyle = crosshair.color;
971
1010
  ctx.lineWidth = Math.max(1, crosshair.width);
972
- if (crosshair.style === "dotted") {
973
- ctx.setLineDash([2, 4]);
974
- } else if (crosshair.style === "dashed") {
975
- ctx.setLineDash([8, 6]);
976
- } else {
977
- ctx.setLineDash([]);
978
- }
1011
+ applyDashPattern(crosshair.style, dashPatterns.dotted, dashPatterns.dashed);
979
1012
  if (crosshair.showVertical) {
980
1013
  ctx.beginPath();
981
1014
  ctx.moveTo(crisp(cx), crisp(chartTop));
@@ -1017,13 +1050,7 @@ function createChart(element, options = {}) {
1017
1050
  ctx.save();
1018
1051
  ctx.strokeStyle = tickerColor;
1019
1052
  ctx.lineWidth = tickerThickness;
1020
- if (tickerStyle === "dotted") {
1021
- ctx.setLineDash([2, 4]);
1022
- } else if (tickerStyle === "dashed") {
1023
- ctx.setLineDash([8, 6]);
1024
- } else {
1025
- ctx.setLineDash([]);
1026
- }
1053
+ applyDashPattern(tickerStyle, dashPatterns.dotted, dashPatterns.dashed);
1027
1054
  ctx.beginPath();
1028
1055
  ctx.moveTo(crisp(chartLeft), crisp(lineY));
1029
1056
  ctx.lineTo(crisp(chartRight), crisp(lineY));
@@ -1034,7 +1061,7 @@ function createChart(element, options = {}) {
1034
1061
  ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
1035
1062
  const labelPaddingX = 8;
1036
1063
  const labelHeight = 20;
1037
- const labelWidth = Math.ceil(ctx.measureText(tickerLabel).width) + labelPaddingX * 2;
1064
+ const labelWidth = getPriceLabelWidth(tickerLabel, labelPaddingX);
1038
1065
  const labelX = chartRight + 4;
1039
1066
  const labelY = clamp(lineY - labelHeight / 2, chartTop, chartBottom - labelHeight);
1040
1067
  const labelRadius = Math.max(0, ticker.labelBorderRadius ?? 0);
@@ -1096,20 +1123,18 @@ function createChart(element, options = {}) {
1096
1123
  ctx.save();
1097
1124
  ctx.strokeStyle = labelBorderColor;
1098
1125
  ctx.lineWidth = labelBorderWidth;
1099
- if (labelBorderStyle === "dotted") {
1100
- ctx.setLineDash([2, 3]);
1101
- } else if (labelBorderStyle === "dashed") {
1102
- ctx.setLineDash([6, 4]);
1103
- } else {
1104
- ctx.setLineDash([]);
1105
- }
1126
+ applyDashPattern(
1127
+ labelBorderStyle,
1128
+ dashPatterns.borderDotted,
1129
+ dashPatterns.borderDashed
1130
+ );
1106
1131
  strokeRoundedRect(Math.round(x), Math.round(y), widthValue, labelHeight, labelRadius);
1107
1132
  ctx.restore();
1108
1133
  };
1109
1134
  if (crosshair.showPriceLabel) {
1110
1135
  const hoverPrice = yMin + (chartBottom - cy) / chartHeight * yRange;
1111
1136
  const priceText = formatPrice(hoverPrice);
1112
- const priceWidth = Math.ceil(ctx.measureText(priceText).width) + labelPaddingX * 2;
1137
+ const priceWidth = getPriceLabelWidth(priceText, labelPaddingX);
1113
1138
  const priceX = chartRight + 4;
1114
1139
  const priceY = clamp(cy - labelHeight / 2, chartTop, chartBottom - labelHeight);
1115
1140
  ctx.fillStyle = labelBackground;
package/dist/index.d.cts CHANGED
@@ -5,6 +5,8 @@ interface ChartOptions {
5
5
  axisColor?: string;
6
6
  axis?: AxisOptions;
7
7
  priceDecimals?: number;
8
+ stabilizePriceLabels?: boolean;
9
+ priceLabelMinIntegerDigits?: number;
8
10
  initialViewport?: "latest" | "center";
9
11
  initialVisibleBars?: number;
10
12
  minVisibleBars?: number;
@@ -29,6 +31,15 @@ interface ChartOptions {
29
31
  priceLines?: PriceLineOptions[];
30
32
  orderLines?: OrderLineOptions[];
31
33
  tickerLine?: TickerLineOptions;
34
+ dashPatterns?: Partial<DashPatternOptions>;
35
+ }
36
+ interface DashPatternOptions {
37
+ dotted: [number, number];
38
+ dashed: [number, number];
39
+ connectorDotted: [number, number];
40
+ connectorDashed: [number, number];
41
+ borderDotted: [number, number];
42
+ borderDashed: [number, number];
32
43
  }
33
44
  interface AxisOptions {
34
45
  lineColor?: string;
@@ -119,6 +130,8 @@ interface OrderLineOptions {
119
130
  actionButtonFontWeight?: number | string;
120
131
  actionButtonBorderColor?: string;
121
132
  actionButtonBorderStyle?: "solid" | "dotted" | "dashed";
133
+ actionButtonsInnerGap?: number;
134
+ actionButtonsGroupGap?: number;
122
135
  actionButtons?: OrderActionButton[];
123
136
  connectorToPrice?: number;
124
137
  connectorColor?: string;
@@ -208,4 +221,4 @@ interface OhlcDataPoint {
208
221
  }
209
222
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
210
223
 
211
- export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type GridOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
224
+ export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type DashPatternOptions, type GridOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
package/dist/index.d.ts CHANGED
@@ -5,6 +5,8 @@ interface ChartOptions {
5
5
  axisColor?: string;
6
6
  axis?: AxisOptions;
7
7
  priceDecimals?: number;
8
+ stabilizePriceLabels?: boolean;
9
+ priceLabelMinIntegerDigits?: number;
8
10
  initialViewport?: "latest" | "center";
9
11
  initialVisibleBars?: number;
10
12
  minVisibleBars?: number;
@@ -29,6 +31,15 @@ interface ChartOptions {
29
31
  priceLines?: PriceLineOptions[];
30
32
  orderLines?: OrderLineOptions[];
31
33
  tickerLine?: TickerLineOptions;
34
+ dashPatterns?: Partial<DashPatternOptions>;
35
+ }
36
+ interface DashPatternOptions {
37
+ dotted: [number, number];
38
+ dashed: [number, number];
39
+ connectorDotted: [number, number];
40
+ connectorDashed: [number, number];
41
+ borderDotted: [number, number];
42
+ borderDashed: [number, number];
32
43
  }
33
44
  interface AxisOptions {
34
45
  lineColor?: string;
@@ -119,6 +130,8 @@ interface OrderLineOptions {
119
130
  actionButtonFontWeight?: number | string;
120
131
  actionButtonBorderColor?: string;
121
132
  actionButtonBorderStyle?: "solid" | "dotted" | "dashed";
133
+ actionButtonsInnerGap?: number;
134
+ actionButtonsGroupGap?: number;
122
135
  actionButtons?: OrderActionButton[];
123
136
  connectorToPrice?: number;
124
137
  connectorColor?: string;
@@ -208,4 +221,4 @@ interface OhlcDataPoint {
208
221
  }
209
222
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
210
223
 
211
- export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type GridOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
224
+ export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type DashPatternOptions, type GridOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
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",
@@ -76,6 +84,8 @@ var DEFAULT_ORDER_LINE_OPTIONS = {
76
84
  actionButtonFontWeight: 500,
77
85
  actionButtonBorderColor: "#2563eb",
78
86
  actionButtonBorderStyle: "solid",
87
+ actionButtonsInnerGap: 6,
88
+ actionButtonsGroupGap: 8,
79
89
  actionButtons: [],
80
90
  connectorToPrice: Number.NaN,
81
91
  connectorColor: "#2563eb",
@@ -92,6 +102,8 @@ var DEFAULT_OPTIONS = {
92
102
  axisColor: "#7f8289",
93
103
  axis: DEFAULT_AXIS_OPTIONS,
94
104
  priceDecimals: 2,
105
+ stabilizePriceLabels: true,
106
+ priceLabelMinIntegerDigits: 3,
95
107
  initialViewport: "latest",
96
108
  initialVisibleBars: 60,
97
109
  minVisibleBars: 5,
@@ -123,7 +135,8 @@ var DEFAULT_OPTIONS = {
123
135
  labelBackgroundColor: "#38bdf8",
124
136
  labelTextColor: "#0b1220",
125
137
  labelBorderRadius: 3
126
- }
138
+ },
139
+ dashPatterns: DEFAULT_DASH_PATTERNS
127
140
  };
128
141
  var BRAND_LOGO_VIEWBOX_WIDTH = 190;
129
142
  var BRAND_LOGO_VIEWBOX_HEIGHT = 186;
@@ -153,6 +166,10 @@ function createChart(element, options = {}) {
153
166
  tickerLine: {
154
167
  ...DEFAULT_OPTIONS.tickerLine,
155
168
  ...options.tickerLine
169
+ },
170
+ dashPatterns: {
171
+ ...DEFAULT_DASH_PATTERNS,
172
+ ...options.dashPatterns ?? {}
156
173
  }
157
174
  };
158
175
  let width = mergedOptions.width;
@@ -240,6 +257,23 @@ function createChart(element, options = {}) {
240
257
  const clamp = (value, min, max) => {
241
258
  return Math.min(max, Math.max(min, value));
242
259
  };
260
+ const dashPatterns = {
261
+ dotted: mergedOptions.dashPatterns.dotted ?? DEFAULT_DASH_PATTERNS.dotted,
262
+ dashed: mergedOptions.dashPatterns.dashed ?? DEFAULT_DASH_PATTERNS.dashed,
263
+ connectorDotted: mergedOptions.dashPatterns.connectorDotted ?? DEFAULT_DASH_PATTERNS.connectorDotted,
264
+ connectorDashed: mergedOptions.dashPatterns.connectorDashed ?? DEFAULT_DASH_PATTERNS.connectorDashed,
265
+ borderDotted: mergedOptions.dashPatterns.borderDotted ?? DEFAULT_DASH_PATTERNS.borderDotted,
266
+ borderDashed: mergedOptions.dashPatterns.borderDashed ?? DEFAULT_DASH_PATTERNS.borderDashed
267
+ };
268
+ const applyDashPattern = (style, dotted, dashed) => {
269
+ if (style === "dotted") {
270
+ ctx.setLineDash(dotted);
271
+ } else if (style === "dashed") {
272
+ ctx.setLineDash(dashed);
273
+ } else {
274
+ ctx.setLineDash([]);
275
+ }
276
+ };
243
277
  const clampXViewport = () => {
244
278
  const count = data.length;
245
279
  if (count === 0) {
@@ -309,6 +343,27 @@ function createChart(element, options = {}) {
309
343
  const decimals = clamp(Math.round(mergedOptions.priceDecimals), 0, 8);
310
344
  return price.toFixed(decimals);
311
345
  };
346
+ const getStabilizedPriceTemplate = () => {
347
+ const decimals = clamp(Math.round(mergedOptions.priceDecimals), 0, 8);
348
+ const configuredDigits = Math.max(1, Math.floor(mergedOptions.priceLabelMinIntegerDigits));
349
+ let maxAbsPrice = 0;
350
+ for (const point of data) {
351
+ maxAbsPrice = Math.max(maxAbsPrice, Math.abs(point.o), Math.abs(point.h), Math.abs(point.l), Math.abs(point.c));
352
+ }
353
+ const observedDigits = maxAbsPrice >= 1 ? Math.floor(Math.log10(maxAbsPrice)) + 1 : 1;
354
+ const integerDigits = Math.max(configuredDigits, observedDigits);
355
+ const integerPart = "8".repeat(integerDigits);
356
+ const decimalPart = decimals > 0 ? `.${"8".repeat(decimals)}` : "";
357
+ return `${integerPart}${decimalPart}`;
358
+ };
359
+ const getPriceLabelWidth = (priceText, paddingX) => {
360
+ const measured = Math.ceil(ctx.measureText(priceText).width) + paddingX * 2;
361
+ if (!mergedOptions.stabilizePriceLabels) {
362
+ return measured;
363
+ }
364
+ const templateWidth = Math.ceil(ctx.measureText(getStabilizedPriceTemplate()).width) + paddingX * 2;
365
+ return Math.max(measured, templateWidth);
366
+ };
312
367
  const parseData = (nextData) => {
313
368
  return nextData.map((point) => ({
314
369
  time: new Date(point.t),
@@ -413,13 +468,7 @@ function createChart(element, options = {}) {
413
468
  ctx.save();
414
469
  ctx.strokeStyle = color;
415
470
  ctx.lineWidth = Math.max(1, mergedLine.thickness);
416
- if (mergedLine.style === "dotted") {
417
- ctx.setLineDash([2, 4]);
418
- } else if (mergedLine.style === "dashed") {
419
- ctx.setLineDash([8, 6]);
420
- } else {
421
- ctx.setLineDash([]);
422
- }
471
+ applyDashPattern(mergedLine.style, dashPatterns.dotted, dashPatterns.dashed);
423
472
  ctx.beginPath();
424
473
  ctx.moveTo(crisp(chartLeft), crisp(lineY));
425
474
  ctx.lineTo(crisp(chartRight), crisp(lineY));
@@ -433,7 +482,7 @@ function createChart(element, options = {}) {
433
482
  ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
434
483
  const labelPaddingX = 8;
435
484
  const labelHeight = 20;
436
- const labelWidth = Math.ceil(ctx.measureText(labelText).width) + labelPaddingX * 2;
485
+ const labelWidth = mergedLine.label === void 0 ? getPriceLabelWidth(labelText, labelPaddingX) : Math.ceil(ctx.measureText(labelText).width) + labelPaddingX * 2;
437
486
  const labelX = chartRight + 4;
438
487
  const labelY = clamp(lineY - labelHeight / 2, chartTop, chartBottom - labelHeight);
439
488
  ctx.fillStyle = mergedLine.labelBackgroundColor;
@@ -473,13 +522,7 @@ function createChart(element, options = {}) {
473
522
  ctx.save();
474
523
  ctx.strokeStyle = color;
475
524
  ctx.lineWidth = Math.max(1, mergedLine.thickness);
476
- if (mergedLine.style === "dotted") {
477
- ctx.setLineDash([2, 4]);
478
- } else if (mergedLine.style === "dashed") {
479
- ctx.setLineDash([8, 6]);
480
- } else {
481
- ctx.setLineDash([]);
482
- }
525
+ applyDashPattern(mergedLine.style, dashPatterns.dotted, dashPatterns.dashed);
483
526
  ctx.beginPath();
484
527
  ctx.moveTo(crisp(chartLeft), crisp(lineY));
485
528
  ctx.lineTo(crisp(chartRight), crisp(lineY));
@@ -491,13 +534,11 @@ function createChart(element, options = {}) {
491
534
  ctx.save();
492
535
  ctx.strokeStyle = mergedLine.connectorColor ?? color;
493
536
  ctx.lineWidth = Math.max(1, mergedLine.connectorThickness);
494
- if (mergedLine.connectorStyle === "dotted") {
495
- ctx.setLineDash([2, 5]);
496
- } else if (mergedLine.connectorStyle === "dashed") {
497
- ctx.setLineDash([6, 5]);
498
- } else {
499
- ctx.setLineDash([]);
500
- }
537
+ applyDashPattern(
538
+ mergedLine.connectorStyle,
539
+ dashPatterns.connectorDotted,
540
+ dashPatterns.connectorDashed
541
+ );
501
542
  ctx.beginPath();
502
543
  ctx.moveTo(crisp(connectorX), crisp(lineY));
503
544
  ctx.lineTo(crisp(connectorX), crisp(connectorY));
@@ -543,9 +584,9 @@ function createChart(element, options = {}) {
543
584
  const width2 = Math.max(minWidth, Math.ceil(ctx.measureText(button.text).width) + paddingX * 2);
544
585
  return { button, width: width2 };
545
586
  });
546
- const actionButtonInnerGap = actionButtonMetrics.length > 1 ? 4 : 0;
587
+ const actionButtonInnerGap = actionButtonMetrics.length > 1 ? Math.max(0, mergedLine.actionButtonsInnerGap) : 0;
547
588
  const actionButtonsTotalWidth = actionButtonMetrics.reduce((sum, metric) => sum + metric.width, 0) + Math.max(0, actionButtonMetrics.length - 1) * actionButtonInnerGap;
548
- const actionButtonsGap = actionButtonMetrics.length > 0 ? 6 : 0;
589
+ const actionButtonsGap = actionButtonMetrics.length > 0 ? Math.max(0, mergedLine.actionButtonsGroupGap) : 0;
549
590
  const segmentPaddingX = 8;
550
591
  const labelHeight = 22;
551
592
  const qtyWidth = qtyText ? Math.ceil(ctx.measureText(qtyText).width) + segmentPaddingX * 2 : 0;
@@ -600,13 +641,11 @@ function createChart(element, options = {}) {
600
641
  ctx.save();
601
642
  ctx.strokeStyle = actionBorderColor;
602
643
  ctx.lineWidth = 1;
603
- if (actionBorderStyle === "dotted") {
604
- ctx.setLineDash([2, 3]);
605
- } else if (actionBorderStyle === "dashed") {
606
- ctx.setLineDash([6, 4]);
607
- } else {
608
- ctx.setLineDash([]);
609
- }
644
+ applyDashPattern(
645
+ actionBorderStyle,
646
+ dashPatterns.borderDotted,
647
+ dashPatterns.borderDashed
648
+ );
610
649
  strokeRoundedRect(Math.round(actionX), Math.round(actionY), actionW, actionH, actionRadius);
611
650
  ctx.restore();
612
651
  const baseFont = ctx.font;
@@ -671,7 +710,7 @@ function createChart(element, options = {}) {
671
710
  }
672
711
  const priceText = formatPrice(renderPrice);
673
712
  const pricePaddingX = 8;
674
- const measuredPriceWidth = Math.ceil(ctx.measureText(priceText).width) + pricePaddingX * 2;
713
+ const measuredPriceWidth = getPriceLabelWidth(priceText, pricePaddingX);
675
714
  const priceWidth = mergedLine.id === void 0 ? measuredPriceWidth : Math.max(measuredPriceWidth, orderPriceTagWidthById.get(mergedLine.id) ?? 0);
676
715
  if (mergedLine.id) {
677
716
  orderPriceTagWidthById.set(mergedLine.id, priceWidth);
@@ -945,13 +984,7 @@ function createChart(element, options = {}) {
945
984
  ctx.save();
946
985
  ctx.strokeStyle = crosshair.color;
947
986
  ctx.lineWidth = Math.max(1, crosshair.width);
948
- if (crosshair.style === "dotted") {
949
- ctx.setLineDash([2, 4]);
950
- } else if (crosshair.style === "dashed") {
951
- ctx.setLineDash([8, 6]);
952
- } else {
953
- ctx.setLineDash([]);
954
- }
987
+ applyDashPattern(crosshair.style, dashPatterns.dotted, dashPatterns.dashed);
955
988
  if (crosshair.showVertical) {
956
989
  ctx.beginPath();
957
990
  ctx.moveTo(crisp(cx), crisp(chartTop));
@@ -993,13 +1026,7 @@ function createChart(element, options = {}) {
993
1026
  ctx.save();
994
1027
  ctx.strokeStyle = tickerColor;
995
1028
  ctx.lineWidth = tickerThickness;
996
- if (tickerStyle === "dotted") {
997
- ctx.setLineDash([2, 4]);
998
- } else if (tickerStyle === "dashed") {
999
- ctx.setLineDash([8, 6]);
1000
- } else {
1001
- ctx.setLineDash([]);
1002
- }
1029
+ applyDashPattern(tickerStyle, dashPatterns.dotted, dashPatterns.dashed);
1003
1030
  ctx.beginPath();
1004
1031
  ctx.moveTo(crisp(chartLeft), crisp(lineY));
1005
1032
  ctx.lineTo(crisp(chartRight), crisp(lineY));
@@ -1010,7 +1037,7 @@ function createChart(element, options = {}) {
1010
1037
  ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
1011
1038
  const labelPaddingX = 8;
1012
1039
  const labelHeight = 20;
1013
- const labelWidth = Math.ceil(ctx.measureText(tickerLabel).width) + labelPaddingX * 2;
1040
+ const labelWidth = getPriceLabelWidth(tickerLabel, labelPaddingX);
1014
1041
  const labelX = chartRight + 4;
1015
1042
  const labelY = clamp(lineY - labelHeight / 2, chartTop, chartBottom - labelHeight);
1016
1043
  const labelRadius = Math.max(0, ticker.labelBorderRadius ?? 0);
@@ -1072,20 +1099,18 @@ function createChart(element, options = {}) {
1072
1099
  ctx.save();
1073
1100
  ctx.strokeStyle = labelBorderColor;
1074
1101
  ctx.lineWidth = labelBorderWidth;
1075
- if (labelBorderStyle === "dotted") {
1076
- ctx.setLineDash([2, 3]);
1077
- } else if (labelBorderStyle === "dashed") {
1078
- ctx.setLineDash([6, 4]);
1079
- } else {
1080
- ctx.setLineDash([]);
1081
- }
1102
+ applyDashPattern(
1103
+ labelBorderStyle,
1104
+ dashPatterns.borderDotted,
1105
+ dashPatterns.borderDashed
1106
+ );
1082
1107
  strokeRoundedRect(Math.round(x), Math.round(y), widthValue, labelHeight, labelRadius);
1083
1108
  ctx.restore();
1084
1109
  };
1085
1110
  if (crosshair.showPriceLabel) {
1086
1111
  const hoverPrice = yMin + (chartBottom - cy) / chartHeight * yRange;
1087
1112
  const priceText = formatPrice(hoverPrice);
1088
- const priceWidth = Math.ceil(ctx.measureText(priceText).width) + labelPaddingX * 2;
1113
+ const priceWidth = getPriceLabelWidth(priceText, labelPaddingX);
1089
1114
  const priceX = chartRight + 4;
1090
1115
  const priceY = clamp(cy - labelHeight / 2, chartTop, chartBottom - labelHeight);
1091
1116
  ctx.fillStyle = labelBackground;
package/docs/API.md CHANGED
@@ -33,6 +33,8 @@ Top-level options:
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
+ - `stabilizePriceLabels` (default `true`, prevents ticker/crosshair/price-tag width jitter)
37
+ - `priceLabelMinIntegerDigits` (default `3`, baseline integer-digit width for stabilized labels)
36
38
  - `initialViewport` (`"latest"` | `"center"`, default `"latest"`)
37
39
  - `initialVisibleBars` (default `60`)
38
40
  - `minVisibleBars` (default `5`, lower clamp for x zoom)
@@ -57,6 +59,7 @@ Top-level options:
57
59
  - `priceLines?: PriceLineOptions[]`
58
60
  - `orderLines?: OrderLineOptions[]`
59
61
  - `tickerLine?: TickerLineOptions`
62
+ - `dashPatterns?: Partial<DashPatternOptions>` (controls dotted/dashed spacing)
60
63
 
61
64
  ### `AxisOptions`
62
65
 
@@ -129,6 +132,15 @@ watermark: {
129
132
  - `labelTextColor` (default `#0b1220`)
130
133
  - `labelBorderRadius` (default `3`)
131
134
 
135
+ ### `DashPatternOptions`
136
+
137
+ - `dotted` (default `[2, 2]`)
138
+ - `dashed` (default `[8, 6]`)
139
+ - `connectorDotted` (default `[2, 3]`)
140
+ - `connectorDashed` (default `[6, 5]`)
141
+ - `borderDotted` (default `[2, 2]`)
142
+ - `borderDashed` (default `[6, 4]`)
143
+
132
144
  ### `PriceLineOptions`
133
145
 
134
146
  - `id?: string`
@@ -198,6 +210,8 @@ Legacy single action button:
198
210
  - `actionButtonFontWeight?: number | string`
199
211
  - `actionButtonBorderColor?: string`
200
212
  - `actionButtonBorderStyle?: "solid" | "dotted" | "dashed"`
213
+ - `actionButtonsInnerGap?: number` (default `6`, spacing between action buttons)
214
+ - `actionButtonsGroupGap?: number` (default `8`, spacing between action-button group and main order widget)
201
215
 
202
216
  Multi-button actions:
203
217
 
package/docs/RECIPES.md CHANGED
@@ -38,6 +38,29 @@ 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
+
54
+ ## Keep price labels from shaking on fast ticks
55
+
56
+ ```ts
57
+ const chart = createChart(root, {
58
+ priceDecimals: 2,
59
+ stabilizePriceLabels: true,
60
+ priceLabelMinIntegerDigits: 4
61
+ });
62
+ ```
63
+
41
64
  ## Add a draggable pending limit line
42
65
 
43
66
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",