This commit is contained in:
David Brazda
2024-11-15 09:18:40 +01:00
parent 37af631a3e
commit cab85bb4f8
3 changed files with 102 additions and 58 deletions

File diff suppressed because one or more lines are too long

View File

@ -5,7 +5,7 @@ with open('README.md', 'r', encoding='utf-8') as f:
setup( setup(
name='lightweight_charts', name='lightweight_charts',
version='2.2.22', version='2.2.23',
packages=find_packages(), packages=find_packages(),
python_requires='>=3.8', python_requires='>=3.8',
install_requires=[ install_requires=[

View File

@ -15,7 +15,9 @@ export class Legend {
public div: HTMLDivElement; public div: HTMLDivElement;
public contentWrapper: HTMLDivElement; public contentWrapper: HTMLDivElement;
private collapseButton: HTMLDivElement; private collapseButton: HTMLDivElement;
private toggleAllButton: HTMLDivElement;
private isCollapsed: boolean = false; private isCollapsed: boolean = false;
private allVisible: boolean = true;
private ohlcEnabled: boolean = false; private ohlcEnabled: boolean = false;
private percentEnabled: boolean = false; private percentEnabled: boolean = false;
@ -35,7 +37,6 @@ export class Legend {
this.linesEnabled = false this.linesEnabled = false
this.colorBasedOnCandle = false this.colorBasedOnCandle = false
// Create container div
this.div = document.createElement('div'); this.div = document.createElement('div');
this.div.classList.add('legend'); this.div.classList.add('legend');
this.div.style.maxWidth = '300px'; this.div.style.maxWidth = '300px';
@ -44,11 +45,11 @@ export class Legend {
this.div.style.overflowY = 'auto'; this.div.style.overflowY = 'auto';
this.div.style.overflowX = 'hidden'; this.div.style.overflowX = 'hidden';
this.div.style.position = 'absolute'; this.div.style.position = 'absolute';
this.div.style.backgroundColor = 'rgba(19, 23, 34, 0.1)'; this.div.style.backgroundColor = 'rgba(19, 23, 34, 0.85)';
this.div.style.color = '#D1D4DC'; this.div.style.color = '#D1D4DC';
this.div.style.padding = '12px'; this.div.style.padding = '8px';
this.div.style.borderRadius = '4px'; this.div.style.borderRadius = '4px';
//this.div.style.border = '1px solid rgba(42, 46, 57, 0.85)'; this.div.style.border = '1px solid rgba(42, 46, 57, 0.85)';
this.div.style.boxShadow = '0 2px 5px rgba(0,0,0,0.3)'; this.div.style.boxShadow = '0 2px 5px rgba(0,0,0,0.3)';
this.div.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'; this.div.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
this.div.style.fontSize = '12px'; this.div.style.fontSize = '12px';
@ -56,11 +57,16 @@ export class Legend {
this.div.style.display = 'none'; this.div.style.display = 'none';
this.div.style.pointerEvents = 'all'; this.div.style.pointerEvents = 'all';
// Create collapse button const buttonsContainer = document.createElement('div');
buttonsContainer.style.position = 'absolute';
buttonsContainer.style.right = '8px';
buttonsContainer.style.top = '8px';
buttonsContainer.style.display = 'flex';
buttonsContainer.style.gap = '8px';
buttonsContainer.style.zIndex = '6';
buttonsContainer.style.pointerEvents = 'all';
this.collapseButton = document.createElement('div'); this.collapseButton = document.createElement('div');
this.collapseButton.style.position = 'absolute';
this.collapseButton.style.right = '8px';
this.collapseButton.style.top = '8px';
this.collapseButton.style.cursor = 'pointer'; this.collapseButton.style.cursor = 'pointer';
this.collapseButton.style.width = '20px'; this.collapseButton.style.width = '20px';
this.collapseButton.style.height = '20px'; this.collapseButton.style.height = '20px';
@ -70,15 +76,34 @@ export class Legend {
this.collapseButton.style.color = '#D1D4DC'; this.collapseButton.style.color = '#D1D4DC';
this.collapseButton.style.fontSize = '16px'; this.collapseButton.style.fontSize = '16px';
this.collapseButton.style.userSelect = 'none'; this.collapseButton.style.userSelect = 'none';
this.collapseButton.style.zIndex = '6';
this.collapseButton.style.pointerEvents = 'all'; this.collapseButton.style.pointerEvents = 'all';
this.collapseButton.innerHTML = ''; this.collapseButton.innerHTML = '';
this.collapseButton.addEventListener('click', (e) => { this.collapseButton.addEventListener('click', (e) => {
e.stopPropagation(); e.stopPropagation();
this.toggleCollapse(); this.toggleCollapse();
}); });
this.toggleAllButton = document.createElement('div');
this.toggleAllButton.style.cursor = 'pointer';
this.toggleAllButton.style.width = '20px';
this.toggleAllButton.style.height = '20px';
this.toggleAllButton.style.display = 'flex';
this.toggleAllButton.style.alignItems = 'center';
this.toggleAllButton.style.justifyContent = 'center';
this.toggleAllButton.style.color = '#D1D4DC';
this.toggleAllButton.style.fontSize = '14px';
this.toggleAllButton.style.userSelect = 'none';
this.toggleAllButton.style.pointerEvents = 'all';
this.toggleAllButton.innerHTML = '👁️';
this.toggleAllButton.title = 'Toggle all';
this.toggleAllButton.addEventListener('click', (e) => {
e.stopPropagation();
this.toggleAll();
});
buttonsContainer.appendChild(this.toggleAllButton);
buttonsContainer.appendChild(this.collapseButton);
// Style the scrollbar
const style = document.createElement('style'); const style = document.createElement('style');
style.textContent = ` style.textContent = `
.legend::-webkit-scrollbar { .legend::-webkit-scrollbar {
@ -101,7 +126,6 @@ export class Legend {
`; `;
document.head.appendChild(style); document.head.appendChild(style);
// Create a wrapper for the content
this.contentWrapper = document.createElement('div'); this.contentWrapper = document.createElement('div');
this.contentWrapper.style.minHeight = '100%'; this.contentWrapper.style.minHeight = '100%';
this.contentWrapper.style.width = '100%'; this.contentWrapper.style.width = '100%';
@ -112,26 +136,72 @@ export class Legend {
this.contentWrapper.style.pointerEvents = 'all'; this.contentWrapper.style.pointerEvents = 'all';
this.text = document.createElement('span'); this.text = document.createElement('span');
this.text.style.lineHeight = '1'; this.text.style.lineHeight = '1.4';
this.text.style.display = 'block'; this.text.style.display = 'block';
this.text.style.color = '#D1D4DC'; this.text.style.color = '#D1D4DC';
this.text.style.pointerEvents = 'all';
this.candle = document.createElement('div'); this.candle = document.createElement('div');
this.candle.style.color = '#D1D4DC'; this.candle.style.color = '#D1D4DC';
this.candle.style.width = '100%'; this.candle.style.width = '100%';
this.candle.style.pointerEvents = 'all';
// Append in the correct order
this.contentWrapper.appendChild(this.text); this.contentWrapper.appendChild(this.text);
this.contentWrapper.appendChild(this.candle); this.contentWrapper.appendChild(this.candle);
this.div.appendChild(this.collapseButton); this.div.appendChild(buttonsContainer);
this.div.appendChild(this.contentWrapper); this.div.appendChild(this.contentWrapper);
handler.div.appendChild(this.div); handler.div.appendChild(this.div);
handler.chart.subscribeCrosshairMove(this.legendHandler) handler.chart.subscribeCrosshairMove(this.legendHandler)
} }
private toggleCollapse() {
this.isCollapsed = !this.isCollapsed;
if (this.isCollapsed) {
this.contentWrapper.style.display = 'none';
this.div.style.maxHeight = 'auto';
this.div.style.height = 'auto';
this.collapseButton.innerHTML = '+';
} else {
this.contentWrapper.style.display = 'flex';
this.div.style.maxHeight = '300px';
this.collapseButton.innerHTML = '';
}
}
private toggleAll() {
this.allVisible = !this.allVisible;
this._lines.forEach(line => {
line.series.applyOptions({
visible: this.allVisible
});
const group = line.toggle.querySelector('g');
if (group) {
if (this.allVisible) {
group.innerHTML = this.openEyeSvg(line.solid);
} else {
group.innerHTML = this.closedEyeSvg(line.solid);
}
}
});
this.toggleAllButton.style.opacity = this.allVisible ? '1' : '0.5';
}
private openEyeSvg(strokeColor: string): string {
return `
<path style="fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke:${strokeColor};stroke-opacity:1;stroke-miterlimit:4;" d="M 21.998437 12 C 21.998437 12 18.998437 18 12 18 C 5.001562 18 2.001562 12 2.001562 12 C 2.001562 12 5.001562 6 12 6 C 18.998437 6 21.998437 12 21.998437 12 Z M 21.998437 12 " transform="matrix(0.833333,0,0,0.833333,0,0)"/>
<path style="fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke:${strokeColor};stroke-opacity:1;stroke-miterlimit:4;" d="M 15 12 C 15 13.654687 13.654687 15 12 15 C 10.345312 15 9 13.654687 9 12 C 9 10.345312 10.345312 9 12 9 C 13.654687 9 15 10.345312 15 12 Z M 15 12 " transform="matrix(0.833333,0,0,0.833333,0,0)"/>
`;
}
private closedEyeSvg(strokeColor: string): string {
return `
<path style="fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke:${strokeColor};stroke-opacity:1;stroke-miterlimit:4;" d="M 20.001562 9 C 20.001562 9 19.678125 9.665625 18.998437 10.514062 M 12 14.001562 C 10.392187 14.001562 9.046875 13.589062 7.95 12.998437 M 12 14.001562 C 13.607812 14.001562 14.953125 13.589062 16.05 12.998437 M 12 14.001562 L 12 17.498437 M 3.998437 9 C 3.998437 9 4.354687 9.735937 5.104687 10.645312 M 7.95 12.998437 L 5.001562 15.998437 M 7.95 12.998437 C 6.689062 12.328125 5.751562 11.423437 5.104687 10.645312 M 16.05 12.998437 L 18.501562 15.998437 M 16.05 12.998437 C 17.38125 12.290625 18.351562 11.320312 18.998437 10.514062 M 5.104687 10.645312 L 2.001562 12 M 18.998437 10.514062 L 21.998437 12 " transform="matrix(0.833333,0,0,0.833333,0,0)"/>
`;
}
toJSON() { toJSON() {
const {_lines, handler, ...serialized} = this; const {_lines, handler, ...serialized} = this;
return serialized; return serialized;
@ -139,14 +209,6 @@ export class Legend {
makeSeriesRow(name: string, series: ISeriesApi<SeriesType>) { makeSeriesRow(name: string, series: ISeriesApi<SeriesType>) {
const strokeColor = series.options().color; const strokeColor = series.options().color;
let openEye = `
<path style="fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke:${strokeColor};stroke-opacity:1;stroke-miterlimit:4;" d="M 21.998437 12 C 21.998437 12 18.998437 18 12 18 C 5.001562 18 2.001562 12 2.001562 12 C 2.001562 12 5.001562 6 12 6 C 18.998437 6 21.998437 12 21.998437 12 Z M 21.998437 12 " transform="matrix(0.833333,0,0,0.833333,0,0)"/>
<path style="fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke:${strokeColor};stroke-opacity:1;stroke-miterlimit:4;" d="M 15 12 C 15 13.654687 13.654687 15 12 15 C 10.345312 15 9 13.654687 9 12 C 9 10.345312 10.345312 9 12 9 C 13.654687 9 15 10.345312 15 12 Z M 15 12 " transform="matrix(0.833333,0,0,0.833333,0,0)"/>\`
`
let closedEye = `
<path style="fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke:${strokeColor};stroke-opacity:1;stroke-miterlimit:4;" d="M 20.001562 9 C 20.001562 9 19.678125 9.665625 18.998437 10.514062 M 12 14.001562 C 10.392187 14.001562 9.046875 13.589062 7.95 12.998437 M 12 14.001562 C 13.607812 14.001562 14.953125 13.589062 16.05 12.998437 M 12 14.001562 L 12 17.498437 M 3.998437 9 C 3.998437 9 4.354687 9.735937 5.104687 10.645312 M 7.95 12.998437 L 5.001562 15.998437 M 7.95 12.998437 C 6.689062 12.328125 5.751562 11.423437 5.104687 10.645312 M 16.05 12.998437 L 18.501562 15.998437 M 16.05 12.998437 C 17.38125 12.290625 18.351562 11.320312 18.998437 10.514062 M 5.104687 10.645312 L 2.001562 12 M 18.998437 10.514062 L 21.998437 12 " transform="matrix(0.833333,0,0,0.833333,0,0)"/>
`
let row = document.createElement('div') let row = document.createElement('div')
row.style.display = 'flex' row.style.display = 'flex'
row.style.alignItems = 'center' row.style.alignItems = 'center'
@ -158,25 +220,21 @@ export class Legend {
let div = document.createElement('div') let div = document.createElement('div')
div.style.flex = '1' div.style.flex = '1'
div.style.pointerEvents = 'all'
let toggle = document.createElement('div') let toggle = document.createElement('div')
toggle.classList.add('legend-toggle-switch'); toggle.classList.add('legend-toggle-switch');
toggle.style.pointerEvents = 'all'
let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("width", "22"); svg.setAttribute("width", "22");
svg.setAttribute("height", "16"); svg.setAttribute("height", "16");
svg.style.pointerEvents = 'all'
let group = document.createElementNS("http://www.w3.org/2000/svg", "g"); let group = document.createElementNS("http://www.w3.org/2000/svg", "g");
group.innerHTML = openEye group.innerHTML = this.openEyeSvg(strokeColor);
let on = true let on = true
toggle.addEventListener('click', () => { toggle.addEventListener('click', () => {
if (on) { if (on) {
on = false on = false
group.innerHTML = closedEye group.innerHTML = this.closedEyeSvg(strokeColor);
series.applyOptions({ series.applyOptions({
visible: false visible: false
}) })
@ -185,7 +243,7 @@ export class Legend {
series.applyOptions({ series.applyOptions({
visible: true visible: true
}) })
group.innerHTML = openEye group.innerHTML = this.openEyeSvg(strokeColor);
} }
}) })
@ -206,23 +264,10 @@ export class Legend {
}); });
} }
private toggleCollapse() { legendItemFormat(num: number, decimal: number) {
this.isCollapsed = !this.isCollapsed; return num.toFixed(decimal).toString().padStart(8, ' ')
if (this.isCollapsed) {
this.contentWrapper.style.display = 'none';
this.div.style.maxHeight = 'auto';
this.div.style.height = 'auto';
this.collapseButton.innerHTML = '+';
} else {
this.contentWrapper.style.display = 'flex';
this.div.style.maxHeight = '300px';
this.collapseButton.innerHTML = '';
}
} }
legendItemFormat(num: number, decimal: number) { return num.toFixed(decimal).toString().padStart(8, ' ') }
shorthandFormat(num: number) { shorthandFormat(num: number) {
const absNum = Math.abs(num) const absNum = Math.abs(num)
if (absNum >= 1000000) { if (absNum >= 1000000) {
@ -232,20 +277,19 @@ export class Legend {
} }
return num.toString().padStart(8, ' '); return num.toString().padStart(8, ' ');
} }
legendHandler(param: MouseEventParams, usingPoint= false) { legendHandler(param: MouseEventParams, usingPoint= false) {
if (!this.ohlcEnabled && !this.linesEnabled && !this.percentEnabled) return; if (!this.ohlcEnabled && !this.linesEnabled && !this.percentEnabled) return;
const options: any = this.handler.series.options() const options: any = this.handler.series.options()
if (!param.time) { if (!param.time) {
this.candle.style.color = 'transparent' this.candle.style.color = 'transparent'
this.candle.innerHTML = this.candle.innerHTML.replace(options['upColor'], '').replace(options['downColor'], '') this.candle.innerHTML = this.candle.innerHTML.replace(options['upColor'], '').replace(options['downColor'], '')
return return
} }
let data: any; let data: any;
let logical: Logical | null = null; let logical: Logical | null = null;
if (usingPoint) { if (usingPoint) {
const timeScale = this.handler.chart.timeScale(); const timeScale = this.handler.chart.timeScale();
let coordinate = timeScale.timeToCoordinate(param.time) let coordinate = timeScale.timeToCoordinate(param.time)
@ -257,7 +301,7 @@ export class Legend {
else { else {
data = param.seriesData.get(this.handler.series); data = param.seriesData.get(this.handler.series);
} }
this.candle.style.color = '' this.candle.style.color = ''
let str = '<span style="line-height: 1.4;">' let str = '<span style="line-height: 1.4;">'
if (data) { if (data) {
@ -267,19 +311,19 @@ export class Legend {
str += `| L ${this.legendItemFormat(data.low, this.handler.precision)} ` str += `| L ${this.legendItemFormat(data.low, this.handler.precision)} `
str += `| C ${this.legendItemFormat(data.close, this.handler.precision)} ` str += `| C ${this.legendItemFormat(data.close, this.handler.precision)} `
} }
if (this.percentEnabled) { if (this.percentEnabled) {
let percentMove = ((data.close - data.open) / data.open) * 100 let percentMove = ((data.close - data.open) / data.open) * 100
let color = percentMove > 0 ? options['upColor'] : options['downColor'] let color = percentMove > 0 ? options['upColor'] : options['downColor']
let percentStr = `${percentMove >= 0 ? '+' : ''}${percentMove.toFixed(2)} %` let percentStr = `${percentMove >= 0 ? '+' : ''}${percentMove.toFixed(2)} %`
if (this.colorBasedOnCandle) { if (this.colorBasedOnCandle) {
str += `| <span style="color: ${color};">${percentStr}</span>` str += `| <span style="color: ${color};">${percentStr}</span>`
} else { } else {
str += '| ' + percentStr str += '| ' + percentStr
} }
} }
if (this.handler.volumeSeries) { if (this.handler.volumeSeries) {
let volumeData: any; let volumeData: any;
if (logical) { if (logical) {
@ -294,14 +338,14 @@ export class Legend {
} }
} }
this.candle.innerHTML = str + '</span>' this.candle.innerHTML = str + '</span>'
this._lines.forEach((e) => { this._lines.forEach((e) => {
if (!this.linesEnabled) { if (!this.linesEnabled) {
e.row.style.display = 'none' e.row.style.display = 'none'
return return
} }
e.row.style.display = 'flex' e.row.style.display = 'flex'
let data let data
if (usingPoint && logical) { if (usingPoint && logical) {
data = e.series.dataByIndex(logical) as LineData data = e.series.dataByIndex(logical) as LineData