hyperprop-charting-library 0.1.44 → 0.1.46
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 +34 -0
- package/dist/hyperprop-charting-library.cjs +260 -19
- package/dist/hyperprop-charting-library.d.ts +35 -1
- package/dist/hyperprop-charting-library.js +260 -19
- package/dist/index.cjs +260 -19
- package/dist/index.d.cts +35 -1
- package/dist/index.d.ts +35 -1
- package/dist/index.js +260 -19
- package/docs/API.md +45 -0
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -37,6 +37,7 @@ interface ChartOptions {
|
|
|
37
37
|
priceLines?: PriceLineOptions[];
|
|
38
38
|
orderLines?: OrderLineOptions[];
|
|
39
39
|
tickerLine?: TickerLineOptions;
|
|
40
|
+
labels?: LabelsOptions;
|
|
40
41
|
dashPatterns?: Partial<DashPatternOptions>;
|
|
41
42
|
indicators?: IndicatorInstanceOptions[];
|
|
42
43
|
}
|
|
@@ -259,10 +260,43 @@ interface TickerLineOptions {
|
|
|
259
260
|
color?: string;
|
|
260
261
|
labelBackgroundColor?: string;
|
|
261
262
|
labelTextColor?: string;
|
|
263
|
+
labelSubtext?: string;
|
|
264
|
+
labelSubtextColor?: string;
|
|
265
|
+
labelSubtextFontSize?: number;
|
|
266
|
+
showCountdownInLabel?: boolean;
|
|
262
267
|
labelBorderRadius?: number;
|
|
263
268
|
smoothing?: boolean;
|
|
264
269
|
smoothingSpeed?: number;
|
|
265
270
|
}
|
|
271
|
+
interface LabelsOptions {
|
|
272
|
+
visible?: boolean;
|
|
273
|
+
symbolName?: string;
|
|
274
|
+
showSymbolName?: boolean;
|
|
275
|
+
showLastPrice?: boolean;
|
|
276
|
+
showPreviousClose?: boolean;
|
|
277
|
+
previousClosePrice?: number;
|
|
278
|
+
showHighLow?: boolean;
|
|
279
|
+
showBidAsk?: boolean;
|
|
280
|
+
bidPrice?: number;
|
|
281
|
+
askPrice?: number;
|
|
282
|
+
showIndicatorNames?: boolean;
|
|
283
|
+
showIndicatorValues?: boolean;
|
|
284
|
+
showCountdownToBarClose?: boolean;
|
|
285
|
+
noOverlapping?: boolean;
|
|
286
|
+
backgroundColor?: string;
|
|
287
|
+
textColor?: string;
|
|
288
|
+
mutedTextColor?: string;
|
|
289
|
+
symbolNameBackgroundColor?: string;
|
|
290
|
+
symbolNameTextColor?: string;
|
|
291
|
+
previousCloseColor?: string;
|
|
292
|
+
highLowColor?: string;
|
|
293
|
+
bidColor?: string;
|
|
294
|
+
askColor?: string;
|
|
295
|
+
indicatorTextColor?: string;
|
|
296
|
+
borderRadius?: number;
|
|
297
|
+
labelHeight?: number;
|
|
298
|
+
labelPaddingX?: number;
|
|
299
|
+
}
|
|
266
300
|
interface ChartInstance {
|
|
267
301
|
updateOptions: (options: ChartOptions) => void;
|
|
268
302
|
setData: (data: OhlcDataPoint[]) => void;
|
|
@@ -326,4 +360,4 @@ interface ViewportState {
|
|
|
326
360
|
}
|
|
327
361
|
declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
|
|
328
362
|
|
|
329
|
-
export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPlugin, type IndicatorRenderContext, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
|
|
363
|
+
export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
|
package/dist/index.d.ts
CHANGED
|
@@ -37,6 +37,7 @@ interface ChartOptions {
|
|
|
37
37
|
priceLines?: PriceLineOptions[];
|
|
38
38
|
orderLines?: OrderLineOptions[];
|
|
39
39
|
tickerLine?: TickerLineOptions;
|
|
40
|
+
labels?: LabelsOptions;
|
|
40
41
|
dashPatterns?: Partial<DashPatternOptions>;
|
|
41
42
|
indicators?: IndicatorInstanceOptions[];
|
|
42
43
|
}
|
|
@@ -259,10 +260,43 @@ interface TickerLineOptions {
|
|
|
259
260
|
color?: string;
|
|
260
261
|
labelBackgroundColor?: string;
|
|
261
262
|
labelTextColor?: string;
|
|
263
|
+
labelSubtext?: string;
|
|
264
|
+
labelSubtextColor?: string;
|
|
265
|
+
labelSubtextFontSize?: number;
|
|
266
|
+
showCountdownInLabel?: boolean;
|
|
262
267
|
labelBorderRadius?: number;
|
|
263
268
|
smoothing?: boolean;
|
|
264
269
|
smoothingSpeed?: number;
|
|
265
270
|
}
|
|
271
|
+
interface LabelsOptions {
|
|
272
|
+
visible?: boolean;
|
|
273
|
+
symbolName?: string;
|
|
274
|
+
showSymbolName?: boolean;
|
|
275
|
+
showLastPrice?: boolean;
|
|
276
|
+
showPreviousClose?: boolean;
|
|
277
|
+
previousClosePrice?: number;
|
|
278
|
+
showHighLow?: boolean;
|
|
279
|
+
showBidAsk?: boolean;
|
|
280
|
+
bidPrice?: number;
|
|
281
|
+
askPrice?: number;
|
|
282
|
+
showIndicatorNames?: boolean;
|
|
283
|
+
showIndicatorValues?: boolean;
|
|
284
|
+
showCountdownToBarClose?: boolean;
|
|
285
|
+
noOverlapping?: boolean;
|
|
286
|
+
backgroundColor?: string;
|
|
287
|
+
textColor?: string;
|
|
288
|
+
mutedTextColor?: string;
|
|
289
|
+
symbolNameBackgroundColor?: string;
|
|
290
|
+
symbolNameTextColor?: string;
|
|
291
|
+
previousCloseColor?: string;
|
|
292
|
+
highLowColor?: string;
|
|
293
|
+
bidColor?: string;
|
|
294
|
+
askColor?: string;
|
|
295
|
+
indicatorTextColor?: string;
|
|
296
|
+
borderRadius?: number;
|
|
297
|
+
labelHeight?: number;
|
|
298
|
+
labelPaddingX?: number;
|
|
299
|
+
}
|
|
266
300
|
interface ChartInstance {
|
|
267
301
|
updateOptions: (options: ChartOptions) => void;
|
|
268
302
|
setData: (data: OhlcDataPoint[]) => void;
|
|
@@ -326,4 +360,4 @@ interface ViewportState {
|
|
|
326
360
|
}
|
|
327
361
|
declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
|
|
328
362
|
|
|
329
|
-
export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPlugin, type IndicatorRenderContext, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
|
|
363
|
+
export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
|
package/dist/index.js
CHANGED
|
@@ -61,6 +61,35 @@ var DEFAULT_DASH_PATTERNS = {
|
|
|
61
61
|
borderDotted: [2, 2],
|
|
62
62
|
borderDashed: [6, 4]
|
|
63
63
|
};
|
|
64
|
+
var DEFAULT_LABELS_OPTIONS = {
|
|
65
|
+
visible: true,
|
|
66
|
+
symbolName: "",
|
|
67
|
+
showSymbolName: false,
|
|
68
|
+
showLastPrice: true,
|
|
69
|
+
showPreviousClose: false,
|
|
70
|
+
previousClosePrice: Number.NaN,
|
|
71
|
+
showHighLow: false,
|
|
72
|
+
showBidAsk: false,
|
|
73
|
+
bidPrice: Number.NaN,
|
|
74
|
+
askPrice: Number.NaN,
|
|
75
|
+
showIndicatorNames: false,
|
|
76
|
+
showIndicatorValues: false,
|
|
77
|
+
showCountdownToBarClose: false,
|
|
78
|
+
noOverlapping: true,
|
|
79
|
+
backgroundColor: "#0b1220",
|
|
80
|
+
textColor: "#cbd5e1",
|
|
81
|
+
mutedTextColor: "#94a3b8",
|
|
82
|
+
symbolNameBackgroundColor: "#1f2937",
|
|
83
|
+
symbolNameTextColor: "#e5e7eb",
|
|
84
|
+
previousCloseColor: "#94a3b8",
|
|
85
|
+
highLowColor: "#a78bfa",
|
|
86
|
+
bidColor: "#ef4444",
|
|
87
|
+
askColor: "#22c55e",
|
|
88
|
+
indicatorTextColor: "#cbd5e1",
|
|
89
|
+
borderRadius: 3,
|
|
90
|
+
labelHeight: 20,
|
|
91
|
+
labelPaddingX: 8
|
|
92
|
+
};
|
|
64
93
|
var DEFAULT_PRICE_LINE_OPTIONS = {
|
|
65
94
|
visible: true,
|
|
66
95
|
style: "solid",
|
|
@@ -148,8 +177,13 @@ var DEFAULT_OPTIONS = {
|
|
|
148
177
|
style: "dotted",
|
|
149
178
|
thickness: 1,
|
|
150
179
|
labelTextColor: "#0b1220",
|
|
180
|
+
labelSubtext: "",
|
|
181
|
+
labelSubtextColor: "#0b1220",
|
|
182
|
+
labelSubtextFontSize: 0,
|
|
183
|
+
showCountdownInLabel: false,
|
|
151
184
|
labelBorderRadius: 3
|
|
152
185
|
},
|
|
186
|
+
labels: DEFAULT_LABELS_OPTIONS,
|
|
153
187
|
dashPatterns: DEFAULT_DASH_PATTERNS,
|
|
154
188
|
indicators: []
|
|
155
189
|
};
|
|
@@ -189,6 +223,10 @@ var mergeChartOptions = (baseOptions, options = {}) => ({
|
|
|
189
223
|
...baseOptions.tickerLine,
|
|
190
224
|
...options.tickerLine ?? {}
|
|
191
225
|
},
|
|
226
|
+
labels: {
|
|
227
|
+
...baseOptions.labels,
|
|
228
|
+
...options.labels ?? {}
|
|
229
|
+
},
|
|
192
230
|
dashPatterns: {
|
|
193
231
|
...baseOptions.dashPatterns,
|
|
194
232
|
...options.dashPatterns ?? {}
|
|
@@ -1255,6 +1293,14 @@ function createChart(element, options = {}) {
|
|
|
1255
1293
|
ctx.textBaseline = baseline;
|
|
1256
1294
|
ctx.fillText(text, x, y);
|
|
1257
1295
|
};
|
|
1296
|
+
const formatDuration = (ms) => {
|
|
1297
|
+
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
1298
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
1299
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
1300
|
+
const seconds = totalSeconds % 60;
|
|
1301
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
1302
|
+
return hours > 0 ? `${hours}:${pad(minutes)}:${pad(seconds)}` : `${pad(minutes)}:${pad(seconds)}`;
|
|
1303
|
+
};
|
|
1258
1304
|
const drawPriceLine = (line, yFromPrice, chartLeft, chartTop, chartRight, chartBottom) => {
|
|
1259
1305
|
const mergedLine = { ...DEFAULT_PRICE_LINE_OPTIONS, ...line };
|
|
1260
1306
|
if (!mergedLine.visible || !Number.isFinite(mergedLine.price)) {
|
|
@@ -2014,14 +2060,49 @@ function createChart(element, options = {}) {
|
|
|
2014
2060
|
drawText(formatPrice(price), chartRight + 6, y, "left", "middle", yAxis.textColor);
|
|
2015
2061
|
ctx.font = prevFont;
|
|
2016
2062
|
}
|
|
2063
|
+
const labels = { ...DEFAULT_LABELS_OPTIONS, ...mergedOptions.labels ?? {} };
|
|
2064
|
+
const priceAxisLabels = [];
|
|
2065
|
+
const addPriceAxisLabel = (label) => {
|
|
2066
|
+
if (!labels.visible || !Number.isFinite(label.price) || label.text.length === 0) {
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2069
|
+
priceAxisLabels.push(label);
|
|
2070
|
+
};
|
|
2071
|
+
const drawReferenceLine = (price, color, style = "dotted") => {
|
|
2072
|
+
if (!Number.isFinite(price)) {
|
|
2073
|
+
return;
|
|
2074
|
+
}
|
|
2075
|
+
const y = clamp(yFromPrice(price), chartTop + 1, chartBottom - 1);
|
|
2076
|
+
ctx.save();
|
|
2077
|
+
ctx.strokeStyle = color;
|
|
2078
|
+
ctx.lineWidth = 1;
|
|
2079
|
+
applyDashPattern(style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2080
|
+
ctx.beginPath();
|
|
2081
|
+
ctx.moveTo(crisp(chartLeft), crisp(y));
|
|
2082
|
+
ctx.lineTo(crisp(chartRight), crisp(y));
|
|
2083
|
+
ctx.stroke();
|
|
2084
|
+
ctx.restore();
|
|
2085
|
+
};
|
|
2086
|
+
const getCountdownText = () => {
|
|
2087
|
+
const last = data[data.length - 1];
|
|
2088
|
+
if (!last) {
|
|
2089
|
+
return null;
|
|
2090
|
+
}
|
|
2091
|
+
const stepMs = getTimeStepMs();
|
|
2092
|
+
const rawRemainingMs = last.time.getTime() + stepMs - Date.now();
|
|
2093
|
+
const countdownMs = rawRemainingMs >= 0 && rawRemainingMs <= stepMs * 2 ? rawRemainingMs : stepMs - Date.now() % stepMs;
|
|
2094
|
+
return formatDuration(countdownMs);
|
|
2095
|
+
};
|
|
2017
2096
|
const ticker = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
|
|
2018
2097
|
const lastPoint = data[data.length - 1];
|
|
2098
|
+
let tickerPrice = null;
|
|
2099
|
+
let tickerColor = null;
|
|
2019
2100
|
if ((ticker.visible ?? true) && lastPoint) {
|
|
2020
|
-
|
|
2101
|
+
tickerPrice = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice : lastPoint.c;
|
|
2021
2102
|
const tickerY = yFromPrice(tickerPrice);
|
|
2022
2103
|
const lineY = clamp(tickerY, chartTop + 1, chartBottom - 1);
|
|
2023
2104
|
const lastDirection = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice >= lastPoint.o ? "up" : "down" : getCandleDirectionByIndex(data.length - 1);
|
|
2024
|
-
|
|
2105
|
+
tickerColor = ticker.color ?? (lastDirection === "up" ? mergedOptions.upColor : mergedOptions.downColor);
|
|
2025
2106
|
const tickerThickness = Math.max(1, ticker.thickness ?? 1);
|
|
2026
2107
|
const tickerStyle = ticker.style ?? "solid";
|
|
2027
2108
|
ctx.save();
|
|
@@ -2034,24 +2115,155 @@ function createChart(element, options = {}) {
|
|
|
2034
2115
|
ctx.stroke();
|
|
2035
2116
|
ctx.setLineDash([]);
|
|
2036
2117
|
ctx.restore();
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
const
|
|
2040
|
-
|
|
2041
|
-
|
|
2118
|
+
}
|
|
2119
|
+
if ((ticker.visible ?? true) && labels.showLastPrice && tickerPrice !== null && tickerColor !== null) {
|
|
2120
|
+
const tickerSubtext = ticker.showCountdownInLabel ? getCountdownText() : ticker.labelSubtext?.trim();
|
|
2121
|
+
addPriceAxisLabel({
|
|
2122
|
+
text: formatPrice(tickerPrice),
|
|
2123
|
+
...tickerSubtext ? { subtext: tickerSubtext } : {},
|
|
2124
|
+
subtextColor: ticker.labelSubtextColor ?? ticker.labelTextColor ?? "#0b1220",
|
|
2125
|
+
...ticker.labelSubtextFontSize === void 0 ? {} : { subtextFontSize: ticker.labelSubtextFontSize },
|
|
2126
|
+
price: tickerPrice,
|
|
2127
|
+
backgroundColor: ticker.labelBackgroundColor ?? tickerColor,
|
|
2128
|
+
textColor: ticker.labelTextColor ?? "#0b1220",
|
|
2129
|
+
color: tickerColor,
|
|
2130
|
+
priority: 100
|
|
2131
|
+
});
|
|
2132
|
+
}
|
|
2133
|
+
if (labels.showSymbolName && labels.symbolName.trim().length > 0 && tickerPrice !== null) {
|
|
2134
|
+
addPriceAxisLabel({
|
|
2135
|
+
text: labels.symbolName.trim(),
|
|
2136
|
+
price: tickerPrice,
|
|
2137
|
+
backgroundColor: labels.symbolNameBackgroundColor,
|
|
2138
|
+
textColor: labels.symbolNameTextColor,
|
|
2139
|
+
color: labels.symbolNameBackgroundColor,
|
|
2140
|
+
priority: 95
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
if (labels.showPreviousClose) {
|
|
2144
|
+
const previousCloseCandidate = Number.isFinite(labels.previousClosePrice) ? labels.previousClosePrice : data.length > 1 ? data[data.length - 2]?.c : Number.NaN;
|
|
2145
|
+
if (previousCloseCandidate !== void 0 && Number.isFinite(previousCloseCandidate)) {
|
|
2146
|
+
const previousClose = previousCloseCandidate;
|
|
2147
|
+
drawReferenceLine(previousClose, labels.previousCloseColor, "dashed");
|
|
2148
|
+
addPriceAxisLabel({
|
|
2149
|
+
text: `PDC ${formatPrice(previousClose)}`,
|
|
2150
|
+
price: previousClose,
|
|
2151
|
+
backgroundColor: labels.backgroundColor,
|
|
2152
|
+
textColor: labels.mutedTextColor,
|
|
2153
|
+
color: labels.previousCloseColor,
|
|
2154
|
+
priority: 50
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
if (labels.showHighLow && visibleData.length > 0) {
|
|
2159
|
+
const visibleHigh = Math.max(...visibleData.map((point) => point.h));
|
|
2160
|
+
const visibleLow = Math.min(...visibleData.map((point) => point.l));
|
|
2161
|
+
addPriceAxisLabel({
|
|
2162
|
+
text: `H ${formatPrice(visibleHigh)}`,
|
|
2163
|
+
price: visibleHigh,
|
|
2164
|
+
backgroundColor: labels.backgroundColor,
|
|
2165
|
+
textColor: labels.textColor,
|
|
2166
|
+
color: labels.highLowColor,
|
|
2167
|
+
priority: 40
|
|
2168
|
+
});
|
|
2169
|
+
addPriceAxisLabel({
|
|
2170
|
+
text: `L ${formatPrice(visibleLow)}`,
|
|
2171
|
+
price: visibleLow,
|
|
2172
|
+
backgroundColor: labels.backgroundColor,
|
|
2173
|
+
textColor: labels.textColor,
|
|
2174
|
+
color: labels.highLowColor,
|
|
2175
|
+
priority: 40
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
2178
|
+
if (labels.showBidAsk) {
|
|
2179
|
+
if (Number.isFinite(labels.bidPrice)) {
|
|
2180
|
+
drawReferenceLine(labels.bidPrice, labels.bidColor, "dotted");
|
|
2181
|
+
addPriceAxisLabel({
|
|
2182
|
+
text: `B ${formatPrice(labels.bidPrice)}`,
|
|
2183
|
+
price: labels.bidPrice,
|
|
2184
|
+
backgroundColor: labels.bidColor,
|
|
2185
|
+
textColor: "#0b1220",
|
|
2186
|
+
color: labels.bidColor,
|
|
2187
|
+
priority: 80
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
if (Number.isFinite(labels.askPrice)) {
|
|
2191
|
+
drawReferenceLine(labels.askPrice, labels.askColor, "dotted");
|
|
2192
|
+
addPriceAxisLabel({
|
|
2193
|
+
text: `A ${formatPrice(labels.askPrice)}`,
|
|
2194
|
+
price: labels.askPrice,
|
|
2195
|
+
backgroundColor: labels.askColor,
|
|
2196
|
+
textColor: "#0b1220",
|
|
2197
|
+
color: labels.askColor,
|
|
2198
|
+
priority: 80
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
if (priceAxisLabels.length > 0) {
|
|
2203
|
+
const labelPaddingX = Math.max(4, labels.labelPaddingX);
|
|
2204
|
+
const baseLabelHeight = Math.max(14, labels.labelHeight);
|
|
2205
|
+
const labelRadius = Math.max(0, labels.borderRadius);
|
|
2206
|
+
const priceLabelFontSize = Math.max(8, axis.fontSize);
|
|
2207
|
+
ctx.font = `${priceLabelFontSize}px ${mergedOptions.fontFamily}`;
|
|
2208
|
+
const positionedLabels = priceAxisLabels.map((label) => {
|
|
2209
|
+
const subtext = label.subtext?.trim();
|
|
2210
|
+
const subtextFontSize = label.subtextFontSize !== void 0 && label.subtextFontSize > 0 ? Math.max(8, Math.round(label.subtextFontSize)) : priceLabelFontSize;
|
|
2211
|
+
const labelHeight = baseLabelHeight + (subtext ? subtextFontSize + 5 : 0);
|
|
2212
|
+
const primaryWidth = label.text === formatPrice(label.price) ? getPriceLabelWidth(label.text, labelPaddingX) : Math.ceil(ctx.measureText(label.text).width) + labelPaddingX * 2;
|
|
2213
|
+
let subtextWidth = 0;
|
|
2214
|
+
if (subtext) {
|
|
2215
|
+
const baseFont = ctx.font;
|
|
2216
|
+
ctx.font = `${subtextFontSize}px ${mergedOptions.fontFamily}`;
|
|
2217
|
+
subtextWidth = Math.ceil(ctx.measureText(subtext).width) + labelPaddingX * 2;
|
|
2218
|
+
ctx.font = baseFont;
|
|
2219
|
+
}
|
|
2220
|
+
const labelTextWidth = Math.max(primaryWidth, subtextWidth);
|
|
2221
|
+
return {
|
|
2222
|
+
...label,
|
|
2223
|
+
subtext,
|
|
2224
|
+
subtextFontSize,
|
|
2225
|
+
height: labelHeight,
|
|
2226
|
+
width: labelTextWidth,
|
|
2227
|
+
targetY: clamp(yFromPrice(label.price), chartTop + 1, chartBottom - 1) - labelHeight / 2,
|
|
2228
|
+
y: 0
|
|
2229
|
+
};
|
|
2230
|
+
}).sort((a, b) => a.targetY - b.targetY || b.priority - a.priority);
|
|
2231
|
+
const minY = chartTop;
|
|
2232
|
+
let cursorY = minY;
|
|
2233
|
+
for (const label of positionedLabels) {
|
|
2234
|
+
const maxY = chartBottom - label.height;
|
|
2235
|
+
label.y = labels.noOverlapping ? Math.max(clamp(label.targetY, minY, maxY), cursorY) : clamp(label.targetY, minY, maxY);
|
|
2236
|
+
cursorY = label.y + label.height + 2;
|
|
2237
|
+
}
|
|
2238
|
+
if (labels.noOverlapping && positionedLabels.length > 0) {
|
|
2239
|
+
const lastLabel = positionedLabels[positionedLabels.length - 1];
|
|
2240
|
+
const overflow = lastLabel ? lastLabel.y + lastLabel.height - chartBottom : 0;
|
|
2241
|
+
if (overflow > 0) {
|
|
2242
|
+
for (const label of positionedLabels) {
|
|
2243
|
+
label.y = Math.max(minY, label.y - overflow);
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2042
2247
|
const labelX = chartRight + 4;
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2248
|
+
for (const label of positionedLabels) {
|
|
2249
|
+
ctx.fillStyle = label.backgroundColor;
|
|
2250
|
+
fillRoundedRect(Math.round(labelX), Math.round(label.y), label.width, label.height, labelRadius);
|
|
2251
|
+
const primaryY = label.subtext ? label.y + baseLabelHeight / 2 - 1 : label.y + label.height / 2;
|
|
2252
|
+
drawText(label.text, labelX + labelPaddingX, primaryY, "left", "middle", label.textColor);
|
|
2253
|
+
if (label.subtext) {
|
|
2254
|
+
const baseFont = ctx.font;
|
|
2255
|
+
ctx.font = `${label.subtextFontSize}px ${mergedOptions.fontFamily}`;
|
|
2256
|
+
drawText(
|
|
2257
|
+
label.subtext,
|
|
2258
|
+
labelX + labelPaddingX,
|
|
2259
|
+
label.y + baseLabelHeight + label.subtextFontSize / 2,
|
|
2260
|
+
"left",
|
|
2261
|
+
"middle",
|
|
2262
|
+
label.subtextColor ?? label.textColor
|
|
2263
|
+
);
|
|
2264
|
+
ctx.font = baseFont;
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2055
2267
|
}
|
|
2056
2268
|
for (const priceLine of priceLines) {
|
|
2057
2269
|
drawPriceLine(priceLine, yFromPrice, chartLeft, chartTop, chartRight, chartBottom);
|
|
@@ -2059,6 +2271,25 @@ function createChart(element, options = {}) {
|
|
|
2059
2271
|
for (const orderLine of orderLines) {
|
|
2060
2272
|
drawOrderLine(orderLine, yFromPrice, chartLeft, chartTop, chartRight, chartBottom);
|
|
2061
2273
|
}
|
|
2274
|
+
if (labels.visible && (labels.showIndicatorNames || labels.showIndicatorValues)) {
|
|
2275
|
+
const labelEntries = [...activeOverlayIndicators, ...activeSeparateIndicators].map(({ indicator, plugin }) => {
|
|
2276
|
+
const inputValues = Object.entries(indicator.inputs).filter(([, value]) => typeof value === "number" || typeof value === "string" || typeof value === "boolean").slice(0, 2).map(([, value]) => String(value));
|
|
2277
|
+
if (labels.showIndicatorNames && labels.showIndicatorValues && inputValues.length > 0) {
|
|
2278
|
+
return `${plugin.name} ${inputValues.join(" ")}`;
|
|
2279
|
+
}
|
|
2280
|
+
if (labels.showIndicatorNames) {
|
|
2281
|
+
return plugin.name;
|
|
2282
|
+
}
|
|
2283
|
+
return inputValues.join(" ");
|
|
2284
|
+
}).filter((entry) => entry.length > 0);
|
|
2285
|
+
if (labelEntries.length > 0) {
|
|
2286
|
+
const prevFont = ctx.font;
|
|
2287
|
+
ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
|
|
2288
|
+
const legendText = labelEntries.join(" ");
|
|
2289
|
+
drawText(legendText, chartLeft + 10, chartTop + 10, "left", "top", labels.indicatorTextColor);
|
|
2290
|
+
ctx.font = prevFont;
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2062
2293
|
for (let index = tickStartIndex; index <= visibleTickEnd; index += xStep) {
|
|
2063
2294
|
const tickTime = getTimeForIndex(index);
|
|
2064
2295
|
if (!tickTime) {
|
|
@@ -2074,6 +2305,16 @@ function createChart(element, options = {}) {
|
|
|
2074
2305
|
drawText(timeLabel, x, fullChartBottom + 8, "center", "top", xAxis.textColor);
|
|
2075
2306
|
ctx.font = prevFont;
|
|
2076
2307
|
}
|
|
2308
|
+
if (labels.visible && labels.showCountdownToBarClose && lastPoint) {
|
|
2309
|
+
const stepMs = getTimeStepMs();
|
|
2310
|
+
const rawRemainingMs = lastPoint.time.getTime() + stepMs - Date.now();
|
|
2311
|
+
const countdownMs = rawRemainingMs >= 0 && rawRemainingMs <= stepMs * 2 ? rawRemainingMs : stepMs - Date.now() % stepMs;
|
|
2312
|
+
const countdownText = formatDuration(countdownMs);
|
|
2313
|
+
const prevFont = ctx.font;
|
|
2314
|
+
ctx.font = `${xAxisFontSize}px ${mergedOptions.fontFamily}`;
|
|
2315
|
+
drawText(countdownText, chartRight + 6, fullChartBottom + 8, "left", "top", xAxis.textColor);
|
|
2316
|
+
ctx.font = prevFont;
|
|
2317
|
+
}
|
|
2077
2318
|
if (crosshair.visible && crosshairPoint) {
|
|
2078
2319
|
const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
|
|
2079
2320
|
const cy = clamp(crosshairPoint.y, chartTop, chartBottom);
|
package/docs/API.md
CHANGED
|
@@ -65,6 +65,7 @@ Top-level options:
|
|
|
65
65
|
- `priceLines?: PriceLineOptions[]`
|
|
66
66
|
- `orderLines?: OrderLineOptions[]`
|
|
67
67
|
- `tickerLine?: TickerLineOptions`
|
|
68
|
+
- `labels?: LabelsOptions` (TradingView-style price-scale/indicator label controls)
|
|
68
69
|
- `dashPatterns?: Partial<DashPatternOptions>` (controls dotted/dashed spacing)
|
|
69
70
|
- `indicators?: IndicatorInstanceOptions[]` (initial indicator instances, built-in includes `"volume"`)
|
|
70
71
|
|
|
@@ -148,8 +149,52 @@ watermark: {
|
|
|
148
149
|
- `color` (default `#38bdf8`)
|
|
149
150
|
- `labelBackgroundColor` (default `#38bdf8`)
|
|
150
151
|
- `labelTextColor` (default `#0b1220`)
|
|
152
|
+
- `labelSubtext` (optional second line inside the current-price label, for example `"MNQ"`)
|
|
153
|
+
- `labelSubtextColor` (defaults to the label text color)
|
|
154
|
+
- `labelSubtextFontSize` (default `0`, meaning use the same font size as the price)
|
|
155
|
+
- `showCountdownInLabel` (default `false`; renders bar-close countdown as the second line)
|
|
151
156
|
- `labelBorderRadius` (default `3`)
|
|
152
157
|
|
|
158
|
+
### `LabelsOptions`
|
|
159
|
+
|
|
160
|
+
TradingView-style labels can be controlled from a single top-level object:
|
|
161
|
+
|
|
162
|
+
- `visible` (default `true`, master switch for this label layer)
|
|
163
|
+
- `symbolName` (text for the symbol-name label)
|
|
164
|
+
- `showSymbolName` (default `false`)
|
|
165
|
+
- `showLastPrice` (default `true`; controls the ticker price tag, while `tickerLine.visible` still controls the line)
|
|
166
|
+
- `showPreviousClose` (default `false`)
|
|
167
|
+
- `previousClosePrice` (optional; falls back to the prior candle close)
|
|
168
|
+
- `showHighLow` (default `false`; labels visible-range high/low)
|
|
169
|
+
- `showBidAsk` (default `false`)
|
|
170
|
+
- `bidPrice`, `askPrice` (optional market data values for bid/ask labels)
|
|
171
|
+
- `showIndicatorNames` (default `false`; draws active indicator names in the chart)
|
|
172
|
+
- `showIndicatorValues` (default `false`; appends simple indicator input values)
|
|
173
|
+
- `showCountdownToBarClose` (default `false`; draws a bottom-axis countdown based on candle time spacing)
|
|
174
|
+
- `noOverlapping` (default `true`; stacks price-scale labels so they do not cover each other)
|
|
175
|
+
- Style fields: `backgroundColor`, `textColor`, `mutedTextColor`, `symbolNameBackgroundColor`, `symbolNameTextColor`, `previousCloseColor`, `highLowColor`, `bidColor`, `askColor`, `indicatorTextColor`, `borderRadius`, `labelHeight`, `labelPaddingX`
|
|
176
|
+
|
|
177
|
+
Example:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
createChart(root, {
|
|
181
|
+
labels: {
|
|
182
|
+
symbolName: "ESH6",
|
|
183
|
+
showSymbolName: true,
|
|
184
|
+
showPreviousClose: true,
|
|
185
|
+
previousClosePrice: 5231.25,
|
|
186
|
+
showHighLow: true,
|
|
187
|
+
showBidAsk: true,
|
|
188
|
+
bidPrice: 5234.75,
|
|
189
|
+
askPrice: 5235.0,
|
|
190
|
+
showIndicatorNames: true,
|
|
191
|
+
showIndicatorValues: true,
|
|
192
|
+
showCountdownToBarClose: true,
|
|
193
|
+
noOverlapping: true
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
153
198
|
### `DashPatternOptions`
|
|
154
199
|
|
|
155
200
|
- `dotted` (default `[2, 2]`)
|