Refactoring/Enhancements/Fixes
Breaking Changes:
- Removed the `api` parameter; callbacks no longer need to be in a specific class.
- Topbar callbacks now take a chart as an argument (see updated callback examples)
- Removed the `topbar` parameter from chart declaration. The Topbar will be automatically created upon declaration of a topbar widget.
- Removed the `searchbox` parameter from charts. It will be created upon subscribing to it in `chart.events`.
- Removed `dynamic_loading`.
- Removed ‘volume_enabled’ parameter. Volume will be enabled if the volumn column is present in the dataframe.
- Widgets’ `func` parameter is now declared last.
- Switchers take a tuple of options rather than a variable number of arguments.
- `add_hotkey` renamed to `hotkey`
- Horizontal lines now take a `func` argument rather than `interactive`. This event will emit the Line object that was moved.
- Removed the `name` parameter from `line.set`. Line object names are now declared upon creation.
Enhancements:
- Added the `button` widget to the Topbar.
- Added the color picker to the drawing context menu.
- Charts now have a `candle_data` method, which returns the current data displayed on the chart as a DataFrame.
- Fixed callbacks are now located in the `chart.events` object:
- search (e.g `chart.events.search += on_search`)
- new_bar
- range_change
- Added volume to the legend
- Drawings can now be accessed through `chart.toolbox.drawings`
- added the `style` and `name` parameters to `create_line`
Bug Fixes:
- Fixed a bug causing new charts not to load after `exit` was called.
- Refactored rayline placement to ensure they do not move the visible range.
- Fixed a bug causing the visible range to shift when trendlines are moved past the final candlestick.
- Fixed a bug preventing trendlines and raylines on irregular timeframes.
- Fixed a bug causing the legend to prevent mouse input into the chart.
This commit is contained in:
@ -1,3 +1,123 @@
|
||||
if (!window.TopBar) {
|
||||
class TopBar {
|
||||
constructor(chart, hoverBackgroundColor, clickBackgroundColor, activeBackgroundColor, textColor, activeTextColor) {
|
||||
this.makeSwitcher = this.makeSwitcher.bind(this)
|
||||
this.hoverBackgroundColor = hoverBackgroundColor
|
||||
this.clickBackgroundColor = clickBackgroundColor
|
||||
this.activeBackgroundColor = activeBackgroundColor
|
||||
this.textColor = textColor
|
||||
this.activeTextColor = activeTextColor
|
||||
|
||||
this.topBar = document.createElement('div')
|
||||
this.topBar.style.backgroundColor = '#0c0d0f'
|
||||
this.topBar.style.borderBottom = '2px solid #3C434C'
|
||||
this.topBar.style.display = 'flex'
|
||||
this.topBar.style.alignItems = 'center'
|
||||
chart.wrapper.prepend(this.topBar)
|
||||
}
|
||||
makeSwitcher(items, activeItem, callbackName) {
|
||||
let switcherElement = document.createElement('div');
|
||||
switcherElement.style.margin = '4px 12px'
|
||||
let widget = {
|
||||
elem: switcherElement,
|
||||
callbackName: callbackName,
|
||||
}
|
||||
let intervalElements = items.map((item)=> {
|
||||
let itemEl = document.createElement('button');
|
||||
itemEl.style.border = 'none'
|
||||
itemEl.style.padding = '2px 5px'
|
||||
itemEl.style.margin = '0px 2px'
|
||||
itemEl.style.fontSize = '13px'
|
||||
itemEl.style.borderRadius = '4px'
|
||||
itemEl.style.backgroundColor = item === activeItem ? this.activeBackgroundColor : 'transparent'
|
||||
itemEl.style.color = item === activeItem ? this.activeTextColor : this.textColor
|
||||
itemEl.innerText = item;
|
||||
document.body.appendChild(itemEl)
|
||||
itemEl.style.minWidth = itemEl.clientWidth + 1 + 'px'
|
||||
document.body.removeChild(itemEl)
|
||||
|
||||
itemEl.addEventListener('mouseenter', () => itemEl.style.backgroundColor = item === activeItem ? this.activeBackgroundColor : this.hoverBackgroundColor)
|
||||
itemEl.addEventListener('mouseleave', () => itemEl.style.backgroundColor = item === activeItem ? this.activeBackgroundColor : 'transparent')
|
||||
itemEl.addEventListener('mousedown', () => itemEl.style.backgroundColor = item === activeItem ? this.activeBackgroundColor : this.clickBackgroundColor)
|
||||
itemEl.addEventListener('mouseup', () => itemEl.style.backgroundColor = item === activeItem ? this.activeBackgroundColor : this.hoverBackgroundColor)
|
||||
itemEl.addEventListener('click', () => onItemClicked(item))
|
||||
|
||||
switcherElement.appendChild(itemEl);
|
||||
return itemEl;
|
||||
});
|
||||
|
||||
let onItemClicked = (item)=> {
|
||||
if (item === activeItem) return
|
||||
intervalElements.forEach((element, index) => {
|
||||
element.style.backgroundColor = items[index] === item ? this.activeBackgroundColor : 'transparent'
|
||||
element.style.color = items[index] === item ? this.activeTextColor : this.textColor
|
||||
element.style.fontWeight = items[index] === item ? '500' : 'normal'
|
||||
})
|
||||
activeItem = item;
|
||||
window.callbackFunction(`${widget.callbackName}_~_${item}`);
|
||||
}
|
||||
|
||||
this.topBar.appendChild(switcherElement)
|
||||
this.makeSeparator(this.topBar)
|
||||
return widget
|
||||
}
|
||||
makeTextBoxWidget(text) {
|
||||
let textBox = document.createElement('div')
|
||||
textBox.style.margin = '0px 18px'
|
||||
textBox.style.fontSize = '16px'
|
||||
textBox.style.color = 'rgb(220, 220, 220)'
|
||||
textBox.innerText = text
|
||||
this.topBar.append(textBox)
|
||||
this.makeSeparator(this.topBar)
|
||||
return textBox
|
||||
}
|
||||
makeButton(defaultText, callbackName) {
|
||||
let button = document.createElement('button')
|
||||
button.style.border = 'none'
|
||||
button.style.padding = '2px 5px'
|
||||
button.style.margin = '4px 18px'
|
||||
button.style.fontSize = '13px'
|
||||
button.style.backgroundColor = 'transparent'
|
||||
button.style.color = this.textColor
|
||||
button.style.borderRadius = '4px'
|
||||
button.innerText = defaultText;
|
||||
document.body.appendChild(button)
|
||||
button.style.minWidth = button.clientWidth+1+'px'
|
||||
document.body.removeChild(button)
|
||||
|
||||
let widget = {
|
||||
elem: button,
|
||||
callbackName: callbackName
|
||||
}
|
||||
|
||||
button.addEventListener('mouseenter', () => button.style.backgroundColor = this.hoverBackgroundColor)
|
||||
button.addEventListener('mouseleave', () => button.style.backgroundColor = 'transparent')
|
||||
button.addEventListener('click', () => window.callbackFunction(`${widget.callbackName}_~_${button.innerText}`));
|
||||
button.addEventListener('mousedown', () => {
|
||||
button.style.backgroundColor = this.activeBackgroundColor
|
||||
button.style.color = this.activeTextColor
|
||||
button.style.fontWeight = '500'
|
||||
})
|
||||
button.addEventListener('mouseup', () => {
|
||||
button.style.backgroundColor = this.hoverBackgroundColor
|
||||
button.style.color = this.textColor
|
||||
button.style.fontWeight = 'normal'
|
||||
})
|
||||
this.topBar.appendChild(button)
|
||||
return widget
|
||||
}
|
||||
|
||||
makeSeparator() {
|
||||
let seperator = document.createElement('div')
|
||||
seperator.style.width = '1px'
|
||||
seperator.style.height = '20px'
|
||||
seperator.style.backgroundColor = '#3C434C'
|
||||
this.topBar.appendChild(seperator)
|
||||
}
|
||||
}
|
||||
window.TopBar = TopBar
|
||||
}
|
||||
|
||||
function makeSearchBox(chart) {
|
||||
let searchWindow = document.createElement('div')
|
||||
searchWindow.style.position = 'absolute'
|
||||
@ -37,19 +157,13 @@ function makeSearchBox(chart) {
|
||||
|
||||
let yPrice = null
|
||||
chart.chart.subscribeCrosshairMove((param) => {
|
||||
if (param.point){
|
||||
yPrice = param.point.y;
|
||||
}
|
||||
});
|
||||
if (param.point) yPrice = param.point.y;
|
||||
})
|
||||
let selectedChart = false
|
||||
chart.wrapper.addEventListener('mouseover', (event) => {
|
||||
selectedChart = true
|
||||
})
|
||||
chart.wrapper.addEventListener('mouseout', (event) => {
|
||||
selectedChart = false
|
||||
})
|
||||
chart.wrapper.addEventListener('mouseover', (event) => selectedChart = true)
|
||||
chart.wrapper.addEventListener('mouseout', (event) => selectedChart = false)
|
||||
chart.commandFunctions.push((event) => {
|
||||
if (!selectedChart) return
|
||||
if (!selectedChart) return false
|
||||
if (searchWindow.style.display === 'none') {
|
||||
if (/^[a-zA-Z0-9]$/.test(event.key)) {
|
||||
searchWindow.style.display = 'flex';
|
||||
@ -58,22 +172,15 @@ function makeSearchBox(chart) {
|
||||
}
|
||||
else return false
|
||||
}
|
||||
else if (event.key === 'Enter') {
|
||||
window.callbackFunction(`on_search_~_${chart.id}_~_${sBox.value}`)
|
||||
searchWindow.style.display = 'none'
|
||||
sBox.value = ''
|
||||
return true
|
||||
}
|
||||
else if (event.key === 'Escape') {
|
||||
else if (event.key === 'Enter' || event.key === 'Escape') {
|
||||
if (event.key === 'Enter') window.callbackFunction(`search${chart.id}_~_${sBox.value}`)
|
||||
searchWindow.style.display = 'none'
|
||||
sBox.value = ''
|
||||
return true
|
||||
}
|
||||
else return false
|
||||
})
|
||||
sBox.addEventListener('input', function() {
|
||||
sBox.value = sBox.value.toUpperCase();
|
||||
});
|
||||
sBox.addEventListener('input', () => sBox.value = sBox.value.toUpperCase())
|
||||
return {
|
||||
window: searchWindow,
|
||||
box: sBox,
|
||||
@ -104,77 +211,4 @@ function makeSpinner(chart) {
|
||||
animateSpinner();
|
||||
}
|
||||
|
||||
function makeSwitcher(chart, items, activeItem, callbackName, activeBackgroundColor, activeColor, inactiveColor, hoverColor) {
|
||||
let switcherElement = document.createElement('div');
|
||||
switcherElement.style.margin = '4px 14px'
|
||||
switcherElement.style.zIndex = '1000'
|
||||
|
||||
let intervalElements = items.map(function(item) {
|
||||
let itemEl = document.createElement('button');
|
||||
itemEl.style.cursor = 'pointer'
|
||||
itemEl.style.padding = '2px 5px'
|
||||
itemEl.style.margin = '0px 4px'
|
||||
itemEl.style.fontSize = '13px'
|
||||
itemEl.style.backgroundColor = item === activeItem ? activeBackgroundColor : 'transparent'
|
||||
itemEl.style.color = item === activeItem ? activeColor : inactiveColor
|
||||
itemEl.style.border = 'none'
|
||||
itemEl.style.borderRadius = '4px'
|
||||
|
||||
itemEl.addEventListener('mouseenter', function() {
|
||||
itemEl.style.backgroundColor = item === activeItem ? activeBackgroundColor : hoverColor
|
||||
itemEl.style.color = activeColor
|
||||
})
|
||||
itemEl.addEventListener('mouseleave', function() {
|
||||
itemEl.style.backgroundColor = item === activeItem ? activeBackgroundColor : 'transparent'
|
||||
itemEl.style.color = item === activeItem ? activeColor : inactiveColor
|
||||
})
|
||||
itemEl.innerText = item;
|
||||
itemEl.addEventListener('click', function() {
|
||||
onItemClicked(item);
|
||||
});
|
||||
switcherElement.appendChild(itemEl);
|
||||
return itemEl;
|
||||
});
|
||||
function onItemClicked(item) {
|
||||
if (item === activeItem) {
|
||||
return;
|
||||
}
|
||||
intervalElements.forEach(function(element, index) {
|
||||
element.style.backgroundColor = items[index] === item ? activeBackgroundColor : 'transparent'
|
||||
element.style.color = items[index] === item ? 'activeColor' : inactiveColor
|
||||
});
|
||||
activeItem = item;
|
||||
window.callbackFunction(`${callbackName}_~_${chart.id}_~_${item}`);
|
||||
}
|
||||
chart.topBar.appendChild(switcherElement)
|
||||
makeSeperator(chart.topBar)
|
||||
return switcherElement;
|
||||
}
|
||||
|
||||
function makeTextBoxWidget(chart, text) {
|
||||
let textBox = document.createElement('div')
|
||||
textBox.style.margin = '0px 18px'
|
||||
textBox.style.fontSize = '16px'
|
||||
textBox.style.color = 'rgb(220, 220, 220)'
|
||||
textBox.innerText = text
|
||||
chart.topBar.append(textBox)
|
||||
makeSeperator(chart.topBar)
|
||||
return textBox
|
||||
}
|
||||
|
||||
function makeTopBar(chart) {
|
||||
chart.topBar = document.createElement('div')
|
||||
chart.topBar.style.backgroundColor = '#191B1E'
|
||||
chart.topBar.style.borderBottom = '2px solid #3C434C'
|
||||
chart.topBar.style.display = 'flex'
|
||||
chart.topBar.style.alignItems = 'center'
|
||||
chart.wrapper.prepend(chart.topBar)
|
||||
}
|
||||
|
||||
function makeSeperator(topBar) {
|
||||
let seperator = document.createElement('div')
|
||||
seperator.style.width = '1px'
|
||||
seperator.style.height = '20px'
|
||||
seperator.style.backgroundColor = '#3C434C'
|
||||
topBar.appendChild(seperator)
|
||||
}
|
||||
|
||||
@ -10,7 +10,8 @@ function makeChart(innerWidth, innerHeight, autoSize=true) {
|
||||
height: innerHeight,
|
||||
},
|
||||
candleData: [],
|
||||
commandFunctions: []
|
||||
commandFunctions: [],
|
||||
precision: 2,
|
||||
}
|
||||
chart.chart = LightweightCharts.createChart(chart.div, {
|
||||
width: window.innerWidth*innerWidth,
|
||||
@ -125,6 +126,12 @@ if (!window.HorizontalLine) {
|
||||
this.line = this.chart.series.createPriceLine(this.priceLine)
|
||||
}
|
||||
|
||||
updateColor(color) {
|
||||
this.chart.series.removePriceLine(this.line)
|
||||
this.priceLine.color = color
|
||||
this.line = this.chart.series.createPriceLine(this.priceLine)
|
||||
}
|
||||
|
||||
deleteLine() {
|
||||
this.chart.series.removePriceLine(this.line)
|
||||
this.chart.horizontal_lines.splice(this.chart.horizontal_lines.indexOf(this))
|
||||
@ -135,16 +142,17 @@ if (!window.HorizontalLine) {
|
||||
window.HorizontalLine = HorizontalLine
|
||||
|
||||
class Legend {
|
||||
constructor(chart, ohlcEnabled = true, percentEnabled = true, linesEnabled = true,
|
||||
constructor(chart, ohlcEnabled, percentEnabled, linesEnabled,
|
||||
color = 'rgb(191, 195, 203)', fontSize = '11', fontFamily = 'Monaco') {
|
||||
this.div = document.createElement('div')
|
||||
this.div.style.position = 'absolute'
|
||||
this.div.style.zIndex = '3000'
|
||||
this.div.style.pointerEvents = 'none'
|
||||
this.div.style.top = '10px'
|
||||
this.div.style.left = '10px'
|
||||
this.div.style.display = 'flex'
|
||||
this.div.style.flexDirection = 'column'
|
||||
this.div.style.width = `${(chart.scale.width * 100) - 8}vw`
|
||||
this.div.style.maxWidth = `${(chart.scale.width * 100) - 8}vw`
|
||||
this.div.style.color = color
|
||||
this.div.style.fontSize = fontSize + 'px'
|
||||
this.div.style.fontFamily = fontFamily
|
||||
@ -158,7 +166,16 @@ if (!window.HorizontalLine) {
|
||||
this.linesEnabled = linesEnabled
|
||||
this.makeLines(chart)
|
||||
|
||||
let legendItemFormat = (num) => num.toFixed(2).toString().padStart(8, ' ')
|
||||
let legendItemFormat = (num, decimal) => num.toFixed(decimal).toString().padStart(8, ' ')
|
||||
|
||||
let shorthandFormat = (num) => {
|
||||
if (num >= 1000000) {
|
||||
return (num / 1000000).toFixed(1) + 'M';
|
||||
} else if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + 'K';
|
||||
}
|
||||
return num.toString().padStart(8, ' ');
|
||||
}
|
||||
|
||||
chart.chart.subscribeCrosshairMove((param) => {
|
||||
if (param.time) {
|
||||
@ -166,22 +183,23 @@ if (!window.HorizontalLine) {
|
||||
let finalString = '<span style="line-height: 1.8;">'
|
||||
if (data) {
|
||||
this.candle.style.color = ''
|
||||
let ohlc = `O ${legendItemFormat(data.open)}
|
||||
| H ${legendItemFormat(data.high)}
|
||||
| L ${legendItemFormat(data.low)}
|
||||
| C ${legendItemFormat(data.close)} `
|
||||
let ohlc = `O ${legendItemFormat(data.open, chart.precision)}
|
||||
| H ${legendItemFormat(data.high, chart.precision)}
|
||||
| L ${legendItemFormat(data.low, chart.precision)}
|
||||
| C ${legendItemFormat(data.close, chart.precision)} `
|
||||
let percentMove = ((data.close - data.open) / data.open) * 100
|
||||
let percent = `| ${percentMove >= 0 ? '+' : ''}${percentMove.toFixed(2)} %`
|
||||
|
||||
finalString += ohlcEnabled ? ohlc : ''
|
||||
finalString += percentEnabled ? percent : ''
|
||||
|
||||
let volumeData = param.seriesData.get(chart.volumeSeries)
|
||||
if (volumeData) finalString += ohlcEnabled ? `<br>V ${shorthandFormat(volumeData.value)}` : ''
|
||||
}
|
||||
this.candle.innerHTML = finalString + '</span>'
|
||||
this.lines.forEach((line) => {
|
||||
if (!param.seriesData.get(line.line.series)) return
|
||||
let price = legendItemFormat(param.seriesData.get(line.line.series).value)
|
||||
line.div.innerHTML = `<span style="color: ${line.line.color};">▨</span> ${line.line.name} : ${price}`
|
||||
let price = legendItemFormat(param.seriesData.get(line.line.series).value, line.line.precision)
|
||||
line.div.innerHTML = `<span style="color: ${line.solid};">▨</span> ${line.line.name} : ${price}`
|
||||
})
|
||||
|
||||
} else {
|
||||
@ -215,6 +233,7 @@ if (!window.HorizontalLine) {
|
||||
let toggle = document.createElement('div')
|
||||
toggle.style.borderRadius = '4px'
|
||||
toggle.style.marginLeft = '10px'
|
||||
toggle.style.pointerEvents = 'auto'
|
||||
|
||||
|
||||
let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
@ -258,6 +277,7 @@ if (!window.HorizontalLine) {
|
||||
row: row,
|
||||
toggle: toggle,
|
||||
line: line,
|
||||
solid: line.color.startsWith('rgba') ? line.color.replace(/[^,]+(?=\))/, '1') : line.color
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -382,16 +402,20 @@ if (!window.ContextMenu) {
|
||||
this.menu.style.position = 'absolute'
|
||||
this.menu.style.zIndex = '10000'
|
||||
this.menu.style.background = 'rgb(50, 50, 50)'
|
||||
this.menu.style.color = 'lightgrey'
|
||||
this.menu.style.color = '#ececed'
|
||||
this.menu.style.display = 'none'
|
||||
this.menu.style.borderRadius = '5px'
|
||||
this.menu.style.padding = '3px 3px'
|
||||
this.menu.style.fontSize = '14px'
|
||||
this.menu.style.fontSize = '13px'
|
||||
this.menu.style.cursor = 'default'
|
||||
document.body.appendChild(this.menu)
|
||||
this.hoverItem = null
|
||||
|
||||
let closeMenu = (event) => {
|
||||
if (!this.menu.contains(event.target)) this.menu.style.display = 'none';
|
||||
if (!this.menu.contains(event.target)) {
|
||||
this.menu.style.display = 'none';
|
||||
this.listen(false)
|
||||
}
|
||||
}
|
||||
|
||||
this.onRightClick = (event) => {
|
||||
@ -406,16 +430,44 @@ if (!window.ContextMenu) {
|
||||
listen(active) {
|
||||
active ? document.addEventListener('contextmenu', this.onRightClick) : document.removeEventListener('contextmenu', this.onRightClick)
|
||||
}
|
||||
menuItem(text, action) {
|
||||
menuItem(text, action, hover=false) {
|
||||
let item = document.createElement('div')
|
||||
item.style.display = 'flex'
|
||||
item.style.alignItems = 'center'
|
||||
item.style.justifyContent = 'space-between'
|
||||
item.style.padding = '0px 10px'
|
||||
item.style.margin = '3px 0px'
|
||||
item.style.borderRadius = '3px'
|
||||
this.menu.appendChild(item)
|
||||
|
||||
let elem = document.createElement('div')
|
||||
elem.innerText = text
|
||||
elem.style.padding = '0px 10px'
|
||||
elem.style.borderRadius = '3px'
|
||||
this.menu.appendChild(elem)
|
||||
elem.addEventListener('mouseover', (event) => elem.style.backgroundColor = 'rgba(0, 122, 255, 0.3)')
|
||||
elem.addEventListener('mouseout', (event) => elem.style.backgroundColor = 'transparent')
|
||||
elem.addEventListener('click', (event) => {action(); this.menu.style.display = 'none'})
|
||||
item.appendChild(elem)
|
||||
|
||||
if (hover) {
|
||||
let arrow = document.createElement('div')
|
||||
arrow.innerHTML = `<svg width="15px" height="10px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.82054 20.7313C8.21107 21.1218 8.84423 21.1218 9.23476 20.7313L15.8792 14.0868C17.0505 12.9155 17.0508 11.0167 15.88 9.84497L9.3097 3.26958C8.91918 2.87905 8.28601 2.87905 7.89549 3.26958C7.50497 3.6601 7.50497 4.29327 7.89549 4.68379L14.4675 11.2558C14.8581 11.6464 14.8581 12.2795 14.4675 12.67L7.82054 19.317C7.43002 19.7076 7.43002 20.3407 7.82054 20.7313Z" fill="#fff"/></svg>`
|
||||
item.appendChild(arrow)
|
||||
}
|
||||
|
||||
elem.addEventListener('mouseover', (event) => {
|
||||
item.style.backgroundColor = 'rgba(0, 122, 255, 0.3)'
|
||||
if (this.hoverItem && this.hoverItem.closeAction) this.hoverItem.closeAction()
|
||||
this.hoverItem = {elem: elem, action: action, closeAction: hover}
|
||||
})
|
||||
elem.addEventListener('mouseout', (event) => item.style.backgroundColor = 'transparent')
|
||||
if (!hover) elem.addEventListener('click', (event) => {action(event); this.menu.style.display = 'none'})
|
||||
else elem.addEventListener('mouseover', () => action(item.getBoundingClientRect()))
|
||||
}
|
||||
separator() {
|
||||
let separator = document.createElement('div')
|
||||
separator.style.width = '90%'
|
||||
separator.style.height = '1px'
|
||||
separator.style.margin = '4px 0px'
|
||||
separator.style.backgroundColor = '#3C434C'
|
||||
this.menu.appendChild(separator)
|
||||
}
|
||||
|
||||
}
|
||||
window.ContextMenu = ContextMenu
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
if (!window.Table) {
|
||||
class Table {
|
||||
constructor(width, height, headings, widths, alignments, position, draggable = false, pythonMethod, chart) {
|
||||
constructor(width, height, headings, widths, alignments, position, draggable = false, chart) {
|
||||
this.container = document.createElement('div')
|
||||
this.pythonMethod = pythonMethod
|
||||
this.callbackName = null
|
||||
this.chart = chart
|
||||
|
||||
if (draggable) {
|
||||
@ -15,12 +15,12 @@ if (!window.Table) {
|
||||
|
||||
this.container.style.zIndex = '2000'
|
||||
this.container.style.width = width <= 1 ? width * 100 + '%' : width + 'px'
|
||||
this.container.style.height = height <= 1 ? height * 100 + '%' : height + 'px'
|
||||
this.container.style.minHeight = height <= 1 ? height * 100 + '%' : height + 'px'
|
||||
this.container.style.display = 'flex'
|
||||
this.container.style.flexDirection = 'column'
|
||||
this.container.style.justifyContent = 'space-between'
|
||||
|
||||
this.container.style.backgroundColor = 'rgb(45, 45, 45)'
|
||||
this.container.style.backgroundColor = '#121417'
|
||||
this.container.style.borderRadius = '5px'
|
||||
this.container.style.color = 'white'
|
||||
this.container.style.fontSize = '12px'
|
||||
@ -29,7 +29,8 @@ if (!window.Table) {
|
||||
this.table = document.createElement('table')
|
||||
this.table.style.width = '100%'
|
||||
this.table.style.borderCollapse = 'collapse'
|
||||
this.table.style.border = '1px solid rgb(70, 70, 70)';
|
||||
this.container.style.overflow = 'hidden'
|
||||
|
||||
this.rows = {}
|
||||
|
||||
this.headings = headings
|
||||
@ -43,6 +44,9 @@ if (!window.Table) {
|
||||
let th = document.createElement('th')
|
||||
th.textContent = this.headings[i]
|
||||
th.style.width = this.widths[i]
|
||||
th.style.letterSpacing = '0.03rem'
|
||||
th.style.padding = '0.2rem 0px'
|
||||
th.style.fontWeight = '500'
|
||||
th.style.textAlign = 'center'
|
||||
row.appendChild(th)
|
||||
th.style.border = '1px solid rgb(70, 70, 70)'
|
||||
@ -93,10 +97,9 @@ if (!window.Table) {
|
||||
}
|
||||
row.addEventListener('mouseover', () => row.style.backgroundColor = 'rgba(60, 60, 60, 0.6)')
|
||||
row.addEventListener('mouseout', () => row.style.backgroundColor = 'transparent')
|
||||
row.addEventListener('mousedown', () => {
|
||||
row.style.backgroundColor = 'rgba(60, 60, 60)'
|
||||
window.callbackFunction(`${this.pythonMethod}_~_${this.chart.id}_~_${id}`)
|
||||
})
|
||||
row.addEventListener('mousedown', () => row.style.backgroundColor = 'rgba(60, 60, 60)')
|
||||
|
||||
row.addEventListener('click', () => window.callbackFunction(`${this.callbackName}_~_${id}`))
|
||||
row.addEventListener('mouseup', () => row.style.backgroundColor = 'rgba(60, 60, 60, 0.6)')
|
||||
|
||||
this.rows[id] = row
|
||||
@ -134,6 +137,11 @@ if (!window.Table) {
|
||||
this.footer[i].style.textAlign = 'center'
|
||||
}
|
||||
}
|
||||
toJSON() {
|
||||
// Exclude the chart attribute from serialization
|
||||
const {chart, ...serialized} = this;
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
window.Table = Table
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ if (!window.ToolBox) {
|
||||
this.onTrendSelect = this.onTrendSelect.bind(this)
|
||||
this.onHorzSelect = this.onHorzSelect.bind(this)
|
||||
this.onRaySelect = this.onRaySelect.bind(this)
|
||||
this.saveDrawings = this.saveDrawings.bind(this)
|
||||
|
||||
this.chart = chart
|
||||
this.drawings = []
|
||||
@ -15,7 +16,8 @@ if (!window.ToolBox) {
|
||||
this.activeIconColor = 'rgb(240, 240, 240)'
|
||||
this.iconColor = 'lightgrey'
|
||||
this.backgroundColor = 'transparent'
|
||||
this.hoverColor = 'rgba(60, 60, 60, 0.7)'
|
||||
this.hoverColor = 'rgba(80, 86, 94, 0.7)'
|
||||
this.clickBackgroundColor = 'rgba(90, 106, 104, 0.7)'
|
||||
|
||||
this.elem = this.makeToolBox()
|
||||
this.subscribeHoverMove()
|
||||
@ -92,11 +94,15 @@ if (!window.ToolBox) {
|
||||
|
||||
icon.elem.addEventListener('mouseenter', () => {
|
||||
icon.elem.style.backgroundColor = icon === this.chart.activeIcon ? this.activeBackgroundColor : this.hoverColor
|
||||
document.body.style.cursor = 'pointer'
|
||||
})
|
||||
icon.elem.addEventListener('mouseleave', () => {
|
||||
icon.elem.style.backgroundColor = icon === this.chart.activeIcon ? this.activeBackgroundColor : this.backgroundColor
|
||||
document.body.style.cursor = this.chart.cursor
|
||||
})
|
||||
icon.elem.addEventListener('mousedown', () => {
|
||||
icon.elem.style.backgroundColor = icon === this.chart.activeIcon ? this.activeBackgroundColor : this.clickBackgroundColor
|
||||
})
|
||||
icon.elem.addEventListener('mouseup', () => {
|
||||
icon.elem.style.backgroundColor = icon === this.chart.activeIcon ? this.activeBackgroundColor : 'transparent'
|
||||
})
|
||||
icon.elem.addEventListener('click', () => {
|
||||
if (this.chart.activeIcon) {
|
||||
@ -118,6 +124,7 @@ if (!window.ToolBox) {
|
||||
})
|
||||
this.chart.commandFunctions.push((event) => {
|
||||
if (event.altKey && event.code === keyCmd) {
|
||||
event.preventDefault()
|
||||
if (this.chart.activeIcon) {
|
||||
this.chart.activeIcon.elem.style.backgroundColor = this.backgroundColor
|
||||
group.setAttribute("fill", this.iconColor)
|
||||
@ -140,6 +147,7 @@ if (!window.ToolBox) {
|
||||
onTrendSelect(toggle, ray = false) {
|
||||
let trendLine = {
|
||||
line: null,
|
||||
color: 'rgb(15, 139, 237)',
|
||||
markers: null,
|
||||
data: null,
|
||||
from: null,
|
||||
@ -160,17 +168,14 @@ if (!window.ToolBox) {
|
||||
|
||||
if (!this.makingDrawing) return
|
||||
|
||||
let logical
|
||||
this.chart.chart.timeScale().applyOptions({shiftVisibleRangeOnNewBar: false})
|
||||
let logical = this.chart.chart.timeScale().getVisibleLogicalRange()
|
||||
let lastCandleTime = this.chart.candleData[this.chart.candleData.length - 1].time
|
||||
currentTime = this.chart.chart.timeScale().coordinateToTime(param.point.x)
|
||||
if (!currentTime) {
|
||||
let barsToMove = param.logical - this.chart.chart.timeScale().coordinateToLogical(this.chart.chart.timeScale().timeToCoordinate(lastCandleTime))
|
||||
logical = barsToMove <= 0 ? null : this.chart.chart.timeScale().getVisibleLogicalRange()
|
||||
currentTime = dateToChartTime(new Date(chartTimeToDate(this.chart.candleData[this.chart.candleData.length - 1].time).getTime() + (barsToMove * this.interval)), this.interval)
|
||||
} else if (chartTimeToDate(lastCandleTime).getTime() <= chartTimeToDate(currentTime).getTime()) {
|
||||
logical = this.chart.chart.timeScale().getVisibleLogicalRange()
|
||||
}
|
||||
|
||||
let currentPrice = this.chart.series.coordinateToPrice(param.point.y)
|
||||
|
||||
|
||||
@ -179,19 +184,11 @@ if (!window.ToolBox) {
|
||||
trendLine.from = [data[0].time, data[0].value]
|
||||
trendLine.to = [data[data.length - 1].time, data[data.length-1].value]
|
||||
|
||||
if (ray) logical = this.chart.chart.timeScale().getVisibleLogicalRange()
|
||||
|
||||
trendLine.line.setData(data)
|
||||
|
||||
if (logical) {
|
||||
this.chart.chart.applyOptions({handleScroll: false})
|
||||
setTimeout(() => {
|
||||
this.chart.chart.timeScale().setVisibleLogicalRange(logical)
|
||||
}, 1)
|
||||
setTimeout(() => {
|
||||
this.chart.chart.applyOptions({handleScroll: true})
|
||||
}, 50)
|
||||
}
|
||||
this.chart.chart.timeScale().applyOptions({shiftVisibleRangeOnNewBar: true})
|
||||
this.chart.chart.timeScale().setVisibleLogicalRange(logical)
|
||||
|
||||
if (!ray) {
|
||||
trendLine.markers = [
|
||||
{time: firstTime, position: 'inBar', color: '#1E80F0', shape: 'circle', size: 0.1},
|
||||
@ -208,6 +205,7 @@ if (!window.ToolBox) {
|
||||
if (!this.makingDrawing) {
|
||||
this.makingDrawing = true
|
||||
trendLine.line = this.chart.chart.addLineSeries({
|
||||
color: 'rgb(15, 139, 237)',
|
||||
lineWidth: 2,
|
||||
lastValueVisible: false,
|
||||
priceLineVisible: false,
|
||||
@ -221,14 +219,11 @@ if (!window.ToolBox) {
|
||||
})
|
||||
firstPrice = this.chart.series.coordinateToPrice(param.point.y)
|
||||
firstTime = !ray ? this.chart.chart.timeScale().coordinateToTime(param.point.x) : this.chart.candleData[this.chart.candleData.length - 1].time
|
||||
this.chart.chart.applyOptions({
|
||||
handleScroll: false
|
||||
})
|
||||
this.chart.chart.applyOptions({handleScroll: false})
|
||||
this.chart.chart.subscribeCrosshairMove(crosshairHandlerTrend)
|
||||
} else {
|
||||
this.chart.chart.applyOptions({
|
||||
handleScroll: true
|
||||
})
|
||||
}
|
||||
else {
|
||||
this.chart.chart.applyOptions({handleScroll: true})
|
||||
this.makingDrawing = false
|
||||
trendLine.line.setMarkers([])
|
||||
this.drawings.push(trendLine)
|
||||
@ -247,7 +242,7 @@ if (!window.ToolBox) {
|
||||
clickHandlerHorz = (param) => {
|
||||
let price = this.chart.series.coordinateToPrice(param.point.y)
|
||||
let lineStyle = LightweightCharts.LineStyle.Solid
|
||||
let line = new HorizontalLine(this.chart, 'toolBox', price, null, 2, lineStyle, true)
|
||||
let line = new HorizontalLine(this.chart, 'toolBox', price,'red', 2, lineStyle, true)
|
||||
this.drawings.push(line)
|
||||
this.chart.chart.unsubscribeClick(this.clickHandlerHorz)
|
||||
document.body.style.cursor = 'default'
|
||||
@ -267,9 +262,16 @@ if (!window.ToolBox) {
|
||||
subscribeHoverMove() {
|
||||
let hoveringOver = null
|
||||
let x, y
|
||||
let colorPicker = new ColorPicker(this.saveDrawings)
|
||||
|
||||
let onClickDelete = () => this.deleteDrawing(contextMenu.drawing)
|
||||
let onClickColor = (rect) => colorPicker.openMenu(rect, contextMenu.drawing)
|
||||
let contextMenu = new ContextMenu()
|
||||
contextMenu.menuItem('Color Picker', onClickColor, () =>{
|
||||
document.removeEventListener('click', colorPicker.closeMenu)
|
||||
colorPicker.container.style.display = 'none'
|
||||
})
|
||||
contextMenu.separator()
|
||||
contextMenu.menuItem('Delete Drawing', onClickDelete)
|
||||
|
||||
let hoverOver = (param) => {
|
||||
@ -325,9 +327,7 @@ if (!window.ToolBox) {
|
||||
let checkForClick = (event) => {
|
||||
mouseDown = true
|
||||
document.body.style.cursor = 'grabbing'
|
||||
this.chart.chart.applyOptions({
|
||||
handleScroll: false
|
||||
})
|
||||
this.chart.chart.applyOptions({handleScroll: false})
|
||||
|
||||
this.chart.chart.unsubscribeCrosshairMove(hoverOver)
|
||||
|
||||
@ -354,7 +354,7 @@ if (!window.ToolBox) {
|
||||
|
||||
this.chart.chart.applyOptions({handleScroll: true})
|
||||
if (hoveringOver && 'price' in hoveringOver && hoveringOver.id !== 'toolBox') {
|
||||
window.callbackFunction(`on_horizontal_line_move_~_${this.chart.id}_~_${hoveringOver.id};;;${hoveringOver.price.toFixed(8)}`);
|
||||
window.callbackFunction(`${hoveringOver.id}_~_${hoveringOver.price.toFixed(8)}`);
|
||||
}
|
||||
hoveringOver = null
|
||||
document.removeEventListener('mousedown', checkForClick)
|
||||
@ -390,14 +390,15 @@ if (!window.ToolBox) {
|
||||
let endValue = hoveringOver.to[1] + priceDiff
|
||||
let data = calculateTrendLine(startDate, startValue, endDate, endValue, this.interval, this.chart, hoveringOver.ray)
|
||||
|
||||
let logical
|
||||
if (chartTimeToDate(data[data.length - 1].time).getTime() >= chartTimeToDate(this.chart.candleData[this.chart.candleData.length - 1].time).getTime()) {
|
||||
logical = this.chart.chart.timeScale().getVisibleLogicalRange()
|
||||
}
|
||||
this.chart.chart.timeScale().applyOptions({shiftVisibleRangeOnNewBar: false})
|
||||
let logical = this.chart.chart.timeScale().getVisibleLogicalRange()
|
||||
|
||||
hoveringOver.from = [data[0].time, data[0].value]
|
||||
hoveringOver.to = [data[data.length - 1].time, data[data.length - 1].value]
|
||||
hoveringOver.line.setData(data)
|
||||
if (logical) this.chart.chart.timeScale().setVisibleLogicalRange(logical)
|
||||
|
||||
this.chart.chart.timeScale().applyOptions({shiftVisibleRangeOnNewBar: true})
|
||||
this.chart.chart.timeScale().setVisibleLogicalRange(logical)
|
||||
|
||||
if (!hoveringOver.ray) {
|
||||
hoveringOver.markers = [
|
||||
@ -428,22 +429,23 @@ if (!window.ToolBox) {
|
||||
firstPrice = hoveringOver.to[1]
|
||||
}
|
||||
|
||||
let logical
|
||||
this.chart.chart.timeScale().applyOptions({shiftVisibleRangeOnNewBar: false})
|
||||
let logical = this.chart.chart.timeScale().getVisibleLogicalRange()
|
||||
|
||||
let lastCandleTime = this.chart.candleData[this.chart.candleData.length - 1].time
|
||||
if (!currentTime) {
|
||||
let barsToMove = param.logical - this.chart.chart.timeScale().coordinateToLogical(this.chart.chart.timeScale().timeToCoordinate(lastCandleTime))
|
||||
logical = barsToMove <= 0 ? null : this.chart.chart.timeScale().getVisibleLogicalRange()
|
||||
currentTime = dateToChartTime(new Date(chartTimeToDate(this.chart.candleData[this.chart.candleData.length - 1].time).getTime() + (barsToMove * this.interval)), this.interval)
|
||||
} else if (chartTimeToDate(lastCandleTime).getTime() <= chartTimeToDate(currentTime).getTime()) {
|
||||
logical = this.chart.chart.timeScale().getVisibleLogicalRange()
|
||||
}
|
||||
|
||||
let data = calculateTrendLine(firstTime, firstPrice, currentTime, currentPrice, this.interval, this.chart)
|
||||
hoveringOver.line.setData(data)
|
||||
|
||||
this.chart.chart.timeScale().applyOptions({shiftVisibleRangeOnNewBar: true})
|
||||
this.chart.chart.timeScale().setVisibleLogicalRange(logical)
|
||||
|
||||
hoveringOver.from = [data[0].time, data[0].value]
|
||||
hoveringOver.to = [data[data.length - 1].time, data[data.length - 1].value]
|
||||
if (logical) this.chart.chart.timeScale().setVisibleLogicalRange(logical)
|
||||
|
||||
|
||||
hoveringOver.markers = [
|
||||
{time: firstTime, position: 'inBar', color: '#1E80F0', shape: 'circle', size: 0.1},
|
||||
@ -468,7 +470,6 @@ if (!window.ToolBox) {
|
||||
}
|
||||
|
||||
renderDrawings() {
|
||||
//let logical = this.chart.chart.timeScale().getVisibleLogicalRange()
|
||||
this.drawings.forEach((item) => {
|
||||
if ('price' in item) return
|
||||
let startDate = dateToChartTime(new Date(Math.round(chartTimeToDate(item.from[0]).getTime() / this.interval) * this.interval), this.interval)
|
||||
@ -478,7 +479,6 @@ if (!window.ToolBox) {
|
||||
item.to = [data[data.length - 1].time, data[data.length-1].value]
|
||||
item.line.setData(data)
|
||||
})
|
||||
//this.chart.chart.timeScale().setVisibleLogicalRange(logical)
|
||||
}
|
||||
|
||||
deleteDrawing(drawing) {
|
||||
@ -486,10 +486,9 @@ if (!window.ToolBox) {
|
||||
this.chart.series.removePriceLine(drawing.line)
|
||||
}
|
||||
else {
|
||||
let logical
|
||||
if (drawing.ray) logical = this.chart.chart.timeScale().getVisibleLogicalRange()
|
||||
this.chart.chart.timeScale().applyOptions({shiftVisibleRangeOnNewBar: false})
|
||||
this.chart.chart.removeSeries(drawing.line);
|
||||
if (drawing.ray) this.chart.chart.timeScale().setVisibleLogicalRange(logical)
|
||||
this.chart.chart.timeScale().applyOptions({shiftVisibleRangeOnNewBar: true})
|
||||
}
|
||||
this.drawings.splice(this.drawings.indexOf(drawing), 1)
|
||||
this.saveDrawings()
|
||||
@ -512,22 +511,22 @@ if (!window.ToolBox) {
|
||||
}
|
||||
return value;
|
||||
});
|
||||
window.callbackFunction(`save_drawings_~_${this.chart.id}_~_${drawingsString}`)
|
||||
window.callbackFunction(`save_drawings${this.chart.id}_~_${drawingsString}`)
|
||||
}
|
||||
|
||||
loadDrawings(drawings) {
|
||||
this.drawings = drawings
|
||||
this.chart.chart.applyOptions({
|
||||
handleScroll: false
|
||||
})
|
||||
let logical = this.chart.chart.timeScale().getVisibleLogicalRange()
|
||||
this.chart.chart.applyOptions({handleScroll: false})
|
||||
this.chart.chart.timeScale().applyOptions({shiftVisibleRangeOnNewBar: false})
|
||||
this.drawings.forEach((item) => {
|
||||
let idx = this.drawings.indexOf(item)
|
||||
if ('price' in item) {
|
||||
this.drawings[this.drawings.indexOf(item)] = new HorizontalLine(this.chart, 'toolBox', item.priceLine.price, item.priceLine.color, 2, item.priceLine.lineStyle, item.priceLine.axisLabelVisible)
|
||||
this.drawings[idx] = new HorizontalLine(this.chart, 'toolBox', item.priceLine.price, item.priceLine.color, 2, item.priceLine.lineStyle, item.priceLine.axisLabelVisible)
|
||||
}
|
||||
else {
|
||||
this.drawings[this.drawings.indexOf(item)].line = this.chart.chart.addLineSeries({
|
||||
this.drawings[idx].line = this.chart.chart.addLineSeries({
|
||||
lineWidth: 2,
|
||||
color: this.drawings[idx].color,
|
||||
lastValueVisible: false,
|
||||
priceLineVisible: false,
|
||||
crosshairMarkerVisible: false,
|
||||
@ -546,11 +545,140 @@ if (!window.ToolBox) {
|
||||
item.line.setData(data)
|
||||
}
|
||||
})
|
||||
this.chart.chart.applyOptions({
|
||||
handleScroll: true
|
||||
})
|
||||
this.chart.chart.timeScale().setVisibleLogicalRange(logical)
|
||||
this.chart.chart.applyOptions({handleScroll: true})
|
||||
this.chart.chart.timeScale().applyOptions({shiftVisibleRangeOnNewBar: true})
|
||||
}
|
||||
}
|
||||
window.ToolBox = ToolBox
|
||||
}
|
||||
}
|
||||
|
||||
if (!window.ColorPicker) {
|
||||
class ColorPicker {
|
||||
constructor(saveDrawings) {
|
||||
this.saveDrawings = saveDrawings
|
||||
|
||||
this.container = document.createElement('div')
|
||||
this.container.style.maxWidth = '170px'
|
||||
this.container.style.backgroundColor = '#191B1E'
|
||||
this.container.style.position = 'absolute'
|
||||
this.container.style.zIndex = '10000'
|
||||
this.container.style.display = 'none'
|
||||
this.container.style.flexDirection = 'column'
|
||||
this.container.style.alignItems = 'center'
|
||||
this.container.style.border = '2px solid #3C434C'
|
||||
this.container.style.borderRadius = '8px'
|
||||
this.container.style.cursor = 'default'
|
||||
|
||||
let colorPicker = document.createElement('div')
|
||||
colorPicker.style.margin = '10px'
|
||||
colorPicker.style.display = 'flex'
|
||||
colorPicker.style.flexWrap = 'wrap'
|
||||
|
||||
let colors = [
|
||||
'#EBB0B0','#E9CEA1','#E5DF80','#ADEB97','#A3C3EA','#D8BDED',
|
||||
'#E15F5D','#E1B45F','#E2D947','#4BE940','#639AE1','#D7A0E8',
|
||||
'#E42C2A','#E49D30','#E7D827','#3CFF0A','#3275E4','#B06CE3',
|
||||
'#F3000D','#EE9A14','#F1DA13','#2DFC0F','#1562EE','#BB00EF',
|
||||
'#B50911','#E3860E','#D2BD11','#48DE0E','#1455B4','#6E009F',
|
||||
'#7C1713','#B76B12','#8D7A13','#479C12','#165579','#51007E',
|
||||
]
|
||||
|
||||
colors.forEach((color) => colorPicker.appendChild(this.makeColorBox(color)))
|
||||
|
||||
let separator = document.createElement('div')
|
||||
separator.style.backgroundColor = '#3C434C'
|
||||
separator.style.height = '1px'
|
||||
separator.style.width = '130px'
|
||||
|
||||
let opacity = document.createElement('div')
|
||||
opacity.style.margin = '10px'
|
||||
|
||||
let opacityText = document.createElement('div')
|
||||
opacityText.style.color = 'lightgray'
|
||||
opacityText.style.fontSize = '12px'
|
||||
opacityText.innerText = 'Opacity'
|
||||
|
||||
let opacityValue = document.createElement('div')
|
||||
opacityValue.style.color = 'lightgray'
|
||||
opacityValue.style.fontSize = '12px'
|
||||
|
||||
let opacitySlider = document.createElement('input')
|
||||
opacitySlider.type = 'range'
|
||||
opacitySlider.value = this.opacity*100
|
||||
opacityValue.innerText = opacitySlider.value+'%'
|
||||
opacitySlider.oninput = () => {
|
||||
opacityValue.innerText = opacitySlider.value+'%'
|
||||
this.opacity = opacitySlider.value/100
|
||||
this.updateColor()
|
||||
}
|
||||
|
||||
opacity.appendChild(opacityText)
|
||||
opacity.appendChild(opacitySlider)
|
||||
opacity.appendChild(opacityValue)
|
||||
|
||||
this.container.appendChild(colorPicker)
|
||||
this.container.appendChild(separator)
|
||||
this.container.appendChild(opacity)
|
||||
document.getElementById('wrapper').appendChild(this.container)
|
||||
|
||||
}
|
||||
makeColorBox(color) {
|
||||
let box = document.createElement('div')
|
||||
box.style.width = '18px'
|
||||
box.style.height = '18px'
|
||||
box.style.borderRadius = '3px'
|
||||
box.style.margin = '3px'
|
||||
box.style.boxSizing = 'border-box'
|
||||
box.style.backgroundColor = color
|
||||
|
||||
box.addEventListener('mouseover', (event) => box.style.border = '2px solid lightgray')
|
||||
box.addEventListener('mouseout', (event) => box.style.border = 'none')
|
||||
|
||||
let rgbValues = this.extractRGB(color)
|
||||
|
||||
box.addEventListener('click', (event) => {
|
||||
this.rgbValues = rgbValues
|
||||
this.updateColor()
|
||||
})
|
||||
return box
|
||||
}
|
||||
extractRGB = (anyColor) => {
|
||||
let dummyElem = document.createElement('div');
|
||||
dummyElem.style.color = anyColor;
|
||||
document.body.appendChild(dummyElem);
|
||||
let computedColor = getComputedStyle(dummyElem).color;
|
||||
document.body.removeChild(dummyElem);
|
||||
let colorValues = computedColor.match(/\d+/g).map(Number);
|
||||
let isRgba = computedColor.includes('rgba');
|
||||
let opacity = isRgba ? parseFloat(computedColor.split(',')[3]) : 1
|
||||
return [colorValues[0], colorValues[1], colorValues[2], opacity]
|
||||
}
|
||||
updateColor() {
|
||||
let oColor = `rgba(${this.rgbValues[0]}, ${this.rgbValues[1]}, ${this.rgbValues[2]}, ${this.opacity})`
|
||||
if ('price' in this.drawing) this.drawing.updateColor(oColor)
|
||||
else {
|
||||
this.drawing.color = oColor
|
||||
this.drawing.line.applyOptions({color: oColor})
|
||||
}
|
||||
this.saveDrawings()
|
||||
}
|
||||
openMenu(rect, drawing) {
|
||||
this.drawing = drawing
|
||||
this.rgbValues = this.extractRGB('price' in drawing ? drawing.priceLine.color : drawing.color)
|
||||
this.opacity = parseFloat(this.rgbValues[3])
|
||||
this.container.style.top = (rect.top-30)+'px'
|
||||
this.container.style.left = rect.right+'px'
|
||||
this.container.style.display = 'flex'
|
||||
setTimeout(() => document.addEventListener('mousedown', (event) => {
|
||||
if (!this.container.contains(event.target)) {
|
||||
this.closeMenu()
|
||||
}
|
||||
}), 10)
|
||||
}
|
||||
closeMenu(event) {
|
||||
document.removeEventListener('click', this.closeMenu)
|
||||
this.container.style.display = 'none'
|
||||
}
|
||||
}
|
||||
window.ColorPicker = ColorPicker
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user