Skip to content

Commit 2b83263

Browse files
authored
Merge pull request #1 from EranGrin/dev-1
Remove standalone development distribution files from examples
2 parents 1157acb + 53b7989 commit 2b83263

File tree

5 files changed

+333
-19
lines changed

5 files changed

+333
-19
lines changed

src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export { histogramSeries as HistogramSeries } from './model/series/histogram-ser
3636
export { createTextWatermark } from './plugins/text-watermark/primitive';
3737
export { createImageWatermark } from './plugins/image-watermark/primitive';
3838
export { createSeriesMarkers } from './plugins/series-markers/wrapper';
39+
export { SeriesMarker, SeriesMarkerPosition, SeriesMarkerShape } from './plugins/series-markers/types';
3940
export { createUpDownMarkers } from './plugins/up-down-markers-plugin/wrapper';
4041

4142
/**

src/plugins/series-markers/pane-view.ts

+220-8
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,143 @@ interface Offsets {
3232
belowBar: number;
3333
}
3434

35+
// Helper function to calculate marker size more precisely
36+
function calculateMarkerSize(barSpacing: number, sizeMultiplier: number): number {
37+
// Base size from the standard calculation
38+
const baseSize = calculateShapeHeight(barSpacing);
39+
40+
// If size multiplier is 1, return the standard size
41+
if (sizeMultiplier === 1) {
42+
return baseSize;
43+
}
44+
45+
// For other size multipliers, scale more aggressively
46+
// This allows for more noticeable size differences
47+
if (sizeMultiplier < 1) {
48+
// For smaller sizes, scale down more aggressively
49+
return Math.max(baseSize * (0.5 + 0.5 * sizeMultiplier), 6);
50+
} else {
51+
// For larger sizes, scale up more aggressively
52+
return baseSize * sizeMultiplier;
53+
}
54+
}
55+
56+
// Helper function to handle price-based positioning
57+
/* eslint-disable */
58+
function positionMarkerAtPrice<HorzScaleItem>(
59+
rendererItem: SeriesMarkerRendererDataItem,
60+
marker: InternalSeriesMarker<TimePointIndex>,
61+
y: number | null,
62+
series: ISeriesApi<SeriesType, HorzScaleItem>,
63+
textHeight: number,
64+
shapeMargin: number,
65+
chart?: IChartApiBase<HorzScaleItem>
66+
): boolean {
67+
// If we don't have a price, we can't position properly
68+
if (marker.price === undefined) {
69+
return false;
70+
}
71+
72+
// ALWAYS get fresh coordinate for the marker price
73+
// This ensures we use the latest price scale, matching how price lines work
74+
const latestYCoordinate = series.priceToCoordinate(marker.price);
75+
if (latestYCoordinate === null) {
76+
return false;
77+
}
78+
79+
// Use the latest coordinate instead of the cached value
80+
y = latestYCoordinate;
81+
82+
// Get the timeScale from chart to ensure we use the exact same barSpacing
83+
const timeScale = chart?.timeScale();
84+
if (!timeScale) {
85+
return false;
86+
}
87+
88+
// Use EXACTLY the same size calculation as in fillSizeAndY
89+
const sizeMultiplier = isNumber(marker.size) ? Math.max(marker.size, 0) : 1;
90+
const barSpacing = timeScale.options().barSpacing;
91+
// Use our new more precise size calculation
92+
const shapeSize = calculateMarkerSize(barSpacing, sizeMultiplier);
93+
94+
const halfSize = shapeSize / 2;
95+
rendererItem.size = shapeSize;
96+
97+
// Position the marker based on its position property and price
98+
switch (marker.position) {
99+
case 'atPriceTop':
100+
// Position marker so its bottom edge is exactly at the price level
101+
rendererItem.y = (y - halfSize) as Coordinate;
102+
if (rendererItem.text !== undefined) {
103+
// Position text above marker using original logic
104+
rendererItem.text.y = (rendererItem.y - halfSize - textHeight * (0.5 + Constants.TextMargin)) as Coordinate;
105+
}
106+
break;
107+
case 'atPriceBottom':
108+
// Position marker so its top edge is exactly at the price level
109+
rendererItem.y = (y + halfSize) as Coordinate;
110+
if (rendererItem.text !== undefined) {
111+
// Position text below marker using original logic
112+
rendererItem.text.y = (rendererItem.y + halfSize + shapeMargin + textHeight * (0.5 + Constants.TextMargin)) as Coordinate;
113+
}
114+
break;
115+
case 'atPriceMiddle':
116+
// Position marker centered at the price level
117+
rendererItem.y = y as Coordinate;
118+
if (rendererItem.text !== undefined) {
119+
// Position text above marker using original logic
120+
rendererItem.text.y = (rendererItem.y - halfSize - textHeight * (0.5 + Constants.TextMargin)) as Coordinate;
121+
}
122+
break;
123+
default:
124+
// For other positions, we'll handle them in the original way
125+
return false;
126+
}
127+
128+
return true;
129+
}
130+
/* eslint-enable */
131+
132+
133+
134+
// Helper function to position marker above bar
135+
function positionAboveBar<HorzScaleItem>(
136+
series: ISeriesApi<SeriesType, HorzScaleItem>,
137+
highPrice: number,
138+
halfSize: number,
139+
offsets: Offsets,
140+
shapeSize: number,
141+
shapeMargin: number
142+
): Coordinate | null {
143+
const topCoordinate = series.priceToCoordinate(highPrice);
144+
if (topCoordinate === null) {
145+
return null;
146+
}
147+
const y = topCoordinate - halfSize - offsets.aboveBar as Coordinate;
148+
offsets.aboveBar += shapeSize + shapeMargin;
149+
return y;
150+
}
151+
152+
// Helper function to position marker below bar
153+
function positionBelowBar<HorzScaleItem>(
154+
series: ISeriesApi<SeriesType, HorzScaleItem>,
155+
lowPrice: number,
156+
halfSize: number,
157+
offsets: Offsets,
158+
shapeSize: number,
159+
shapeMargin: number
160+
): Coordinate | null {
161+
const bottomCoordinate = series.priceToCoordinate(lowPrice);
162+
if (bottomCoordinate === null) {
163+
return null;
164+
}
165+
const y = bottomCoordinate + halfSize + offsets.belowBar as Coordinate;
166+
offsets.belowBar += shapeSize + shapeMargin;
167+
return y;
168+
}
169+
35170
// eslint-disable-next-line max-params
171+
/* eslint-disable */
36172
function fillSizeAndY<HorzScaleItem>(
37173
rendererItem: SeriesMarkerRendererDataItem,
38174
marker: InternalSeriesMarker<TimePointIndex>,
@@ -43,6 +179,21 @@ function fillSizeAndY<HorzScaleItem>(
43179
series: ISeriesApi<SeriesType, HorzScaleItem>,
44180
chart: IChartApiBase<HorzScaleItem>
45181
): void {
182+
// If marker has a price property AND is using one of the atPrice* positions,
183+
// prioritize exact price positioning
184+
if (marker.price !== undefined &&
185+
(marker.position === 'atPriceTop' ||
186+
marker.position === 'atPriceBottom' ||
187+
marker.position === 'atPriceMiddle')) {
188+
// We don't need to pass the y coordinate - positionMarkerAtPrice will get it fresh
189+
if (positionMarkerAtPrice(rendererItem, marker, null, series, textHeight, shapeMargin, chart)) {
190+
// Successfully positioned using price, we're done
191+
return;
192+
}
193+
// Only if price-to-coordinate conversion fails, fall back to bar positioning
194+
}
195+
196+
// Original positioning logic
46197
const timeScale = chart.timeScale();
47198
let inBarPrice: number;
48199
let highPrice: number;
@@ -61,39 +212,100 @@ function fillSizeAndY<HorzScaleItem>(
61212
}
62213

63214
const sizeMultiplier = isNumber(marker.size) ? Math.max(marker.size, 0) : 1;
64-
const shapeSize = calculateShapeHeight(timeScale.options().barSpacing) * sizeMultiplier;
215+
const barSpacing = timeScale.options().barSpacing;
216+
217+
218+
// Use our new more precise size calculation
219+
const shapeSize = calculateMarkerSize(barSpacing, sizeMultiplier);
220+
65221
const halfSize = shapeSize / 2;
66222
rendererItem.size = shapeSize;
223+
224+
let y: Coordinate | null = null;
225+
67226
switch (marker.position) {
68227
case 'inBar': {
69228
rendererItem.y = ensureNotNull(series.priceToCoordinate(inBarPrice));
70229
if (rendererItem.text !== undefined) {
71-
rendererItem.text.y = rendererItem.y + halfSize + shapeMargin + textHeight * (0.5 + Constants.TextMargin) as Coordinate;
230+
rendererItem.text.y = (rendererItem.y + halfSize + shapeMargin + textHeight * (0.5 + Constants.TextMargin)) as Coordinate;
72231
}
73232
return;
74233
}
75234
case 'aboveBar': {
76-
rendererItem.y = (ensureNotNull(series.priceToCoordinate(highPrice)) - halfSize - offsets.aboveBar) as Coordinate;
235+
y = positionAboveBar(series, highPrice, halfSize, offsets, shapeSize, shapeMargin);
236+
if (y === null) return;
237+
rendererItem.y = y;
238+
77239
if (rendererItem.text !== undefined) {
78-
rendererItem.text.y = rendererItem.y - halfSize - textHeight * (0.5 + Constants.TextMargin) as Coordinate;
240+
rendererItem.text.y = (rendererItem.y - halfSize - textHeight * (0.5 + Constants.TextMargin)) as Coordinate;
79241
offsets.aboveBar += textHeight * (1 + 2 * Constants.TextMargin);
80242
}
81-
offsets.aboveBar += shapeSize + shapeMargin;
82243
return;
83244
}
84245
case 'belowBar': {
85-
rendererItem.y = (ensureNotNull(series.priceToCoordinate(lowPrice)) + halfSize + offsets.belowBar) as Coordinate;
246+
y = positionBelowBar(series, lowPrice, halfSize, offsets, shapeSize, shapeMargin);
247+
if (y === null) return;
248+
rendererItem.y = y;
249+
250+
if (rendererItem.text !== undefined) {
251+
rendererItem.text.y = (rendererItem.y + halfSize + shapeMargin + textHeight * (0.5 + Constants.TextMargin)) as Coordinate;
252+
offsets.belowBar += textHeight * (1 + 2 * Constants.TextMargin);
253+
}
254+
return;
255+
}
256+
// Handle the price-based positions as a fallback if they didn't have a price attribute
257+
case 'atPriceTop': {
258+
if (marker.price !== undefined) {
259+
// This should have been handled by positionMarkerAtPrice
260+
// If we're here, it means the price coordinate conversion failed
261+
return;
262+
}
263+
// Fallback to aboveBar behavior
264+
y = positionAboveBar(series, highPrice, halfSize, offsets, shapeSize, shapeMargin);
265+
if (y === null) return;
266+
rendererItem.y = y;
267+
86268
if (rendererItem.text !== undefined) {
87-
rendererItem.text.y = rendererItem.y + halfSize + shapeMargin + textHeight * (0.5 + Constants.TextMargin) as Coordinate;
269+
rendererItem.text.y = (rendererItem.y - halfSize - textHeight * (0.5 + Constants.TextMargin)) as Coordinate;
270+
offsets.aboveBar += textHeight * (1 + 2 * Constants.TextMargin);
271+
}
272+
return;
273+
}
274+
case 'atPriceBottom': {
275+
if (marker.price !== undefined) {
276+
// This should have been handled by positionMarkerAtPrice
277+
// If we're here, it means the price coordinate conversion failed
278+
return;
279+
}
280+
// Fallback to belowBar behavior
281+
y = positionBelowBar(series, lowPrice, halfSize, offsets, shapeSize, shapeMargin);
282+
if (y === null) return;
283+
rendererItem.y = y;
284+
285+
if (rendererItem.text !== undefined) {
286+
rendererItem.text.y = (rendererItem.y + halfSize + shapeMargin + textHeight * (0.5 + Constants.TextMargin)) as Coordinate;
88287
offsets.belowBar += textHeight * (1 + 2 * Constants.TextMargin);
89288
}
90-
offsets.belowBar += shapeSize + shapeMargin;
289+
return;
290+
}
291+
case 'atPriceMiddle': {
292+
if (marker.price !== undefined) {
293+
// This should have been handled by positionMarkerAtPrice
294+
// If we're here, it means the price coordinate conversion failed
295+
return;
296+
}
297+
// Fallback to inBar behavior
298+
rendererItem.y = ensureNotNull(series.priceToCoordinate(inBarPrice));
299+
if (rendererItem.text !== undefined) {
300+
rendererItem.text.y = (rendererItem.y - halfSize - textHeight * (0.5 + Constants.TextMargin)) as Coordinate;
301+
}
91302
return;
92303
}
93304
}
94305

95306
ensureNever(marker.position);
96307
}
308+
/* eslint-enable */
97309

98310
function isValueData<HorzScaleItem>(
99311
data: SeriesDataItemTypeMap<HorzScaleItem>[SeriesType]

0 commit comments

Comments
 (0)