Skip to content

Commit fecfb65

Browse files
committed
Enhancements & Bug Fixes
- Updated to Lightweight Charts 4.1 - Topbar menu widgets will now scroll when a large number of items are added to them - Vertical Spans can now be placed on Line objects Bugs - Histograms will now be deleted from the legend - autoScale is reset to true upon using `set`.
1 parent 5bb3739 commit fecfb65

6 files changed

Lines changed: 95 additions & 89 deletions

File tree

lightweight_charts/abstract.py

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -227,16 +227,24 @@ def set(self, df: pd.DataFrame = None, format_cols: bool = True):
227227
df = df.rename(columns={self.name: 'value'})
228228
self.data = df.copy()
229229
self._last_bar = df.iloc[-1]
230-
self.run_script(f'{self.id}.series.setData({js_data(df)})')
230+
self.run_script(f'{self.id}.data = {js_data(df)}; {self.id}.series.setData({self.id}.data); ')
231231

232232
def update(self, series: pd.Series):
233233
series = self._series_datetime_format(series, exclude_lowercase=self.name)
234234
if self.name in series.index:
235235
series.rename({self.name: 'value'}, inplace=True)
236-
if series['time'] != self._last_bar['time']:
236+
if self._last_bar and series['time'] != self._last_bar['time']:
237237
self.data.loc[self.data.index[-1]] = self._last_bar
238238
self.data = pd.concat([self.data, series.to_frame().T], ignore_index=True)
239239
self._last_bar = series
240+
bar = js_data(series)
241+
self.run_script(f'''
242+
if (stampToDate(lastBar({self.id}.data).time).getTime() === stampToDate({series['time']}).getTime()) {{
243+
{self.id}.data[{self.id}.data.length-1] = {bar}
244+
}}
245+
else {self.id}.data.push({bar})
246+
{self.id}.series.update({bar})
247+
''')
240248
self.run_script(f'{self.id}.series.update({js_data(series)})')
241249

242250
def marker(self, time: datetime = None, position: MARKER_POSITION = 'below',
@@ -345,6 +353,15 @@ def _toggle_data(self, arg):
345353
if ('volumeSeries' in {self.id}) {self.id}.volumeSeries.applyOptions({{visible: {jbool(arg)}}})
346354
''')
347355

356+
def vertical_span(self, start_time: Union[TIME, tuple, list], end_time: TIME = None,
357+
color: str = 'rgba(252, 219, 3, 0.2)'):
358+
"""
359+
Creates a vertical line or span across the chart.\n
360+
Start time and end time can be used together, or end_time can be
361+
omitted and a single time or a list of times can be passed to start_time.
362+
"""
363+
return VerticalSpan(self, start_time, end_time, color)
364+
348365

349366
class HorizontalLine(Pane):
350367
def __init__(self, chart, price, color, width, style, text, axis_label_visible, func):
@@ -388,13 +405,13 @@ def delete(self):
388405

389406

390407
class VerticalSpan(Pane):
391-
def __init__(self, chart: 'AbstractChart', start_time: Union[TIME, tuple, list], end_time: TIME = None,
408+
def __init__(self, series: 'SeriesCommon', start_time: Union[TIME, tuple, list], end_time: TIME = None,
392409
color: str = 'rgba(252, 219, 3, 0.2)'):
393-
super().__init__(chart.win)
394-
self._chart = chart
410+
self._chart = series._chart
411+
super().__init__(self._chart.win)
395412
start_time, end_time = pd.to_datetime(start_time), pd.to_datetime(end_time)
396413
self.run_script(f'''
397-
{self.id} = {chart.id}.chart.addHistogramSeries({{
414+
{self.id} = {self._chart.id}.chart.addHistogramSeries({{
398415
color: '{color}',
399416
priceFormat: {{type: 'volume'}},
400417
priceScaleId: 'vertical_line',
@@ -414,7 +431,7 @@ def __init__(self, chart: 'AbstractChart', start_time: Union[TIME, tuple, list],
414431
else:
415432
self.run_script(f'''
416433
{self.id}.setData(calculateTrendLine(
417-
{start_time.timestamp()}, 1, {end_time.timestamp()}, 1, {chart.id}))
434+
{start_time.timestamp()}, 1, {end_time.timestamp()}, 1, {series.id}))
418435
''')
419436

420437
def delete(self):
@@ -510,6 +527,9 @@ def delete(self):
510527
"""
511528
self.run_script(f'''
512529
{self._chart.id}.chart.removeSeries({self.id}.series)
530+
{self._chart.id}.legend.lines.forEach(line => {{
531+
if (line.line === {self.id}) {self._chart.id}.legend.div.removeChild(line.row)
532+
}})
513533
delete {self.id}
514534
''')
515535

@@ -545,7 +565,7 @@ def set(self, df: pd.DataFrame = None, render_drawings=False):
545565
self.candle_data = df.copy()
546566
self._last_bar = df.iloc[-1]
547567

548-
self.run_script(f'{self.id}.candleData = {js_data(df)}; {self.id}.series.setData({self.id}.candleData)')
568+
self.run_script(f'{self.id}.data = {js_data(df)}; {self.id}.series.setData({self.id}.data)')
549569
toolbox_action = 'clearDrawings' if not render_drawings else 'renderDrawings'
550570
self.run_script(f"if ('toolBox' in {self._chart.id}) {self._chart.id}.toolBox.{toolbox_action}()")
551571
if 'volume' not in df:
@@ -559,6 +579,11 @@ def set(self, df: pd.DataFrame = None, render_drawings=False):
559579
if line.name not in df.columns:
560580
continue
561581
line.set(df[['time', line.name]], format_cols=False)
582+
# set autoScale to true in case the user has dragged the price scale
583+
self.run_script(f'''
584+
if (!{self.id}.chart.priceScale("right").options.autoScale)
585+
{self.id}.chart.priceScale("right").applyOptions({{autoScale: true}})
586+
''')
562587

563588
def update(self, series: pd.Series, _from_tick=False):
564589
"""
@@ -574,10 +599,10 @@ def update(self, series: pd.Series, _from_tick=False):
574599
self._last_bar = series
575600
bar = js_data(series)
576601
self.run_script(f'''
577-
if (stampToDate(lastBar({self.id}.candleData).time).getTime() === stampToDate({series['time']}).getTime()) {{
578-
{self.id}.candleData[{self.id}.candleData.length-1] = {bar}
602+
if (stampToDate(lastBar({self.id}.data).time).getTime() === stampToDate({series['time']}).getTime()) {{
603+
{self.id}.data[{self.id}.data.length-1] = {bar}
579604
}}
580-
else {self.id}.candleData.push({bar})
605+
else {self.id}.data.push({bar})
581606
{self.id}.series.update({bar})
582607
''')
583608
if 'volume' not in series:
@@ -750,15 +775,6 @@ def ray_line(self, start_time: TIME, value: NUM, round: bool = False,
750775
line._set_trend(start_time, value, start_time, value, ray=True, round=round)
751776
return line
752777

753-
def vertical_span(self, start_time: Union[TIME, tuple, list], end_time: TIME = None,
754-
color: str = 'rgba(252, 219, 3, 0.2)'):
755-
"""
756-
Creates a vertical line or span across the chart.\n
757-
Start time and end time can be used together, or end_time can be
758-
omitted and a single time or a list of times can be passed to start_time.
759-
"""
760-
return VerticalSpan(self, start_time, end_time, color)
761-
762778
def set_visible_range(self, start_time: TIME, end_time: TIME):
763779
self.run_script(f'''
764780
{self.id}.chart.timeScale().setVisibleRange({{

lightweight_charts/js/callback.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ if (!window.TopBar) {
9090
menu.style.border = '2px solid '+pane.borderColor
9191
menu.style.borderTop = 'none'
9292
menu.style.alignItems = 'flex-start'
93+
menu.style.maxHeight = '80%'
94+
menu.style.overflowY = 'auto'
95+
menu.style.scrollbar
9396

9497
let menuOpen = false
9598
items.forEach(text => {

lightweight_charts/js/funcs.js

Lines changed: 43 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ if (!window.Chart) {
9191
makeCandlestickSeries() {
9292
this.markers = []
9393
this.horizontal_lines = []
94-
this.candleData = []
94+
this.data = []
9595
this.precision = 2
9696
let up = 'rgba(39, 157, 130, 100)'
9797
let down = 'rgba(200, 97, 100, 100)'
@@ -336,59 +336,46 @@ if (!window.Chart) {
336336
}
337337

338338
function syncCharts(childChart, parentChart) {
339-
syncCrosshairs(childChart.chart, parentChart.chart)
340-
syncRanges(childChart, parentChart)
341-
}
342-
function syncCrosshairs(childChart, parentChart) {
343-
function crosshairHandler (e, thisChart, otherChart, otherHandler) {
344-
thisChart.applyOptions({crosshair: { horzLine: {
345-
visible: true,
346-
labelVisible: true,
347-
}}})
348-
otherChart.applyOptions({crosshair: { horzLine: {
349-
visible: false,
350-
labelVisible: false,
351-
}}})
352-
353-
otherChart.unsubscribeCrosshairMove(otherHandler)
354-
if (e.time !== undefined) {
355-
let xx = otherChart.timeScale().timeToCoordinate(e.time);
356-
otherChart.setCrosshairXY(xx,300,true);
357-
} else if (e.point !== undefined){
358-
otherChart.setCrosshairXY(e.point.x,300,false);
339+
340+
function crosshairHandler(chart, series, point) {
341+
if (!point) {
342+
chart.clearCrosshairPosition()
343+
return
359344
}
360-
otherChart.subscribeCrosshairMove(otherHandler)
361-
}
362-
let parent = 0
363-
let child = 0
364-
let parentCrosshairHandler = (e) => {
365-
parent ++
366-
if (parent < 10) return
367-
child = 0
368-
crosshairHandler(e, parentChart, childChart, childCrosshairHandler)
345+
chart.setCrosshairPosition(point.value || point.close, point.time, series);
369346
}
370-
let childCrosshairHandler = (e) => {
371-
child ++
372-
if (child < 10) return
373-
parent = 0
374-
crosshairHandler(e, childChart, parentChart, parentCrosshairHandler)
347+
348+
function getPoint(series, param) {
349+
if (!param.time) return null;
350+
return param.seriesData.get(series) || null;
375351
}
376-
parentChart.subscribeCrosshairMove(parentCrosshairHandler)
377-
childChart.subscribeCrosshairMove(childCrosshairHandler)
378-
}
379-
function syncRanges(childChart, parentChart) {
352+
380353
let setChildRange = (timeRange) => childChart.chart.timeScale().setVisibleLogicalRange(timeRange)
381354
let setParentRange = (timeRange) => parentChart.chart.timeScale().setVisibleLogicalRange(timeRange)
382355

383-
parentChart.wrapper.addEventListener('mouseover', (event) => {
384-
childChart.chart.timeScale().unsubscribeVisibleLogicalRangeChange(setParentRange)
385-
parentChart.chart.timeScale().subscribeVisibleLogicalRangeChange(setChildRange)
386-
})
387-
childChart.wrapper.addEventListener('mouseover', (event) => {
388-
parentChart.chart.timeScale().unsubscribeVisibleLogicalRangeChange(setChildRange)
389-
childChart.chart.timeScale().subscribeVisibleLogicalRangeChange(setParentRange)
390-
})
356+
let setParentCrosshair = (param) => {
357+
crosshairHandler(parentChart.chart, parentChart.series, getPoint(childChart.series, param))
358+
}
359+
let setChildCrosshair = (param) => {
360+
crosshairHandler(childChart.chart, childChart.series, getPoint(parentChart.series, param))
361+
}
362+
363+
let selected = parentChart
364+
function addMouseOverListener(thisChart, otherChart, thisCrosshair, otherCrosshair, thisRange, otherRange) {
365+
thisChart.wrapper.addEventListener('mouseover', (event) => {
366+
if (selected === thisChart) return
367+
selected = thisChart
368+
otherChart.chart.timeScale().unsubscribeVisibleLogicalRangeChange(thisRange)
369+
otherChart.chart.unsubscribeCrosshairMove(thisCrosshair)
370+
thisChart.chart.timeScale().subscribeVisibleLogicalRangeChange(otherRange)
371+
thisChart.chart.subscribeCrosshairMove(otherCrosshair)
372+
})
373+
}
374+
addMouseOverListener(parentChart, childChart, setParentCrosshair, setChildCrosshair, setParentRange, setChildRange)
375+
addMouseOverListener(childChart, parentChart, setChildCrosshair, setParentCrosshair, setChildRange, setParentRange)
376+
391377
parentChart.chart.timeScale().subscribeVisibleLogicalRangeChange(setChildRange)
378+
parentChart.chart.subscribeCrosshairMove(setChildCrosshair)
392379
}
393380

394381
function stampToDate(stampOrBusiness) {
@@ -409,26 +396,26 @@ function calculateTrendLine(startDate, startValue, endDate, endValue, chart, ray
409396
[startDate, endDate] = [endDate, startDate];
410397
}
411398
let startIndex
412-
if (stampToDate(startDate).getTime() < stampToDate(chart.candleData[0].time).getTime()) {
399+
if (stampToDate(startDate).getTime() < stampToDate(chart.data[0].time).getTime()) {
413400
startIndex = 0
414401
}
415402
else {
416-
startIndex = chart.candleData.findIndex(item => stampToDate(item.time).getTime() === stampToDate(startDate).getTime())
403+
startIndex = chart.data.findIndex(item => stampToDate(item.time).getTime() === stampToDate(startDate).getTime())
417404
}
418405

419406
if (startIndex === -1) {
420407
return []
421408
}
422409
let endIndex
423410
if (ray) {
424-
endIndex = chart.candleData.length+1000
411+
endIndex = chart.data.length+1000
425412
startValue = endValue
426413
}
427414
else {
428-
endIndex = chart.candleData.findIndex(item => stampToDate(item.time).getTime() === stampToDate(endDate).getTime())
415+
endIndex = chart.data.findIndex(item => stampToDate(item.time).getTime() === stampToDate(endDate).getTime())
429416
if (endIndex === -1) {
430-
let barsBetween = (endDate-lastBar(chart.candleData).time)/chart.interval
431-
endIndex = chart.candleData.length-1+barsBetween
417+
let barsBetween = (endDate-lastBar(chart.data).time)/chart.interval
418+
endIndex = chart.data.length-1+barsBetween
432419
}
433420
}
434421

@@ -438,12 +425,12 @@ function calculateTrendLine(startDate, startValue, endDate, endValue, chart, ray
438425
let currentDate = null
439426
let iPastData = 0
440427
for (let i = 0; i <= numBars; i++) {
441-
if (chart.candleData[startIndex+i]) {
442-
currentDate = chart.candleData[startIndex+i].time
428+
if (chart.data[startIndex+i]) {
429+
currentDate = chart.data[startIndex+i].time
443430
}
444431
else {
445432
iPastData ++
446-
currentDate = lastBar(chart.candleData).time+(iPastData*chart.interval)
433+
currentDate = lastBar(chart.data).time+(iPastData*chart.interval)
447434
}
448435

449436
const currentValue = reversed ? startValue + rate_of_change * (numBars - i) : startValue + rate_of_change * i;

lightweight_charts/js/pkg.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lightweight_charts/js/toolbox.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,8 @@ if (!window.ToolBox) {
161161

162162
currentTime = this.chart.chart.timeScale().coordinateToTime(param.point.x)
163163
if (!currentTime) {
164-
let barsToMove = param.logical - this.chart.candleData.length-1
165-
currentTime = lastBar(this.chart.candleData).time+(barsToMove*this.chart.interval)
164+
let barsToMove = param.logical - this.chart.data.length-1
165+
currentTime = lastBar(this.chart.data).time+(barsToMove*this.chart.interval)
166166
}
167167
let currentPrice = this.chart.series.coordinateToPrice(param.point.y)
168168

@@ -179,7 +179,7 @@ if (!window.ToolBox) {
179179
this.makingDrawing = true
180180
trendLine = new TrendLine(this.chart, 'rgb(15, 139, 237)', ray)
181181
firstPrice = this.chart.series.coordinateToPrice(param.point.y)
182-
firstTime = !ray ? this.chart.chart.timeScale().coordinateToTime(param.point.x) : lastBar(this.chart.candleData).time
182+
firstTime = !ray ? this.chart.chart.timeScale().coordinateToTime(param.point.x) : lastBar(this.chart.data).time
183183
this.chart.chart.applyOptions({handleScroll: false})
184184
this.chart.chart.subscribeCrosshairMove(crosshairHandlerTrend)
185185
}
@@ -337,17 +337,17 @@ if (!window.ToolBox) {
337337
let priceDiff = priceAtCursor - originalPrice
338338
let barsToMove = param.logical - originalIndex
339339

340-
let startBarIndex = this.chart.candleData.findIndex(item => item.time === hoveringOver.from[0])
341-
let endBarIndex = this.chart.candleData.findIndex(item => item.time === hoveringOver.to[0])
340+
let startBarIndex = this.chart.data.findIndex(item => item.time === hoveringOver.from[0])
341+
let endBarIndex = this.chart.data.findIndex(item => item.time === hoveringOver.to[0])
342342

343343
let startDate
344344
let endBar
345345
if (hoveringOver.ray) {
346-
endBar = this.chart.candleData[startBarIndex + barsToMove]
346+
endBar = this.chart.data[startBarIndex + barsToMove]
347347
startDate = hoveringOver.to[0]
348348
} else {
349-
startDate = this.chart.candleData[startBarIndex + barsToMove].time
350-
endBar = endBarIndex === -1 ? null : this.chart.candleData[endBarIndex + barsToMove]
349+
startDate = this.chart.data[startBarIndex + barsToMove].time
350+
endBar = endBarIndex === -1 ? null : this.chart.data[endBarIndex + barsToMove]
351351
}
352352

353353
let endDate = endBar ? endBar.time : hoveringOver.to[0] + (barsToMove * this.chart.interval)
@@ -378,8 +378,8 @@ if (!window.ToolBox) {
378378
}
379379

380380
if (!currentTime) {
381-
let barsToMove = param.logical - this.chart.candleData.length-1
382-
currentTime = lastBar(this.chart.candleData).time + (barsToMove*this.chart.interval)
381+
let barsToMove = param.logical - this.chart.data.length-1
382+
currentTime = lastBar(this.chart.data).time + (barsToMove*this.chart.interval)
383383
}
384384

385385
hoveringOver.calculateAndSet(firstTime, firstPrice, currentTime, currentPrice)

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setup(
77
name='lightweight_charts',
8-
version='1.0.18.2',
8+
version='1.0.18.3',
99
packages=find_packages(),
1010
python_requires='>=3.8',
1111
install_requires=[

0 commit comments

Comments
 (0)