diff --git a/lightweight_charts/abstract.py b/lightweight_charts/abstract.py index f541a76..60bdbd2 100644 --- a/lightweight_charts/abstract.py +++ b/lightweight_charts/abstract.py @@ -82,15 +82,22 @@ class Window: background_color, border_color, border_width, heading_text_colors, heading_background_colors, return_clicked_cells, func) - def create_subchart(self, position: FLOAT = 'left', width: float = 0.5, height: float = 0.5, - sync_id: Optional[str] = None, scale_candles_only: bool = False, - sync_crosshairs_only: bool = False, toolbox: bool = False - ) -> 'AbstractChart': + def create_subchart( + self, + position: FLOAT = 'left', + width: float = 0.5, + height: float = 0.5, + sync_id: Optional[str] = None, + scale_candles_only: bool = False, + sync_crosshairs_only: bool = False, + toolbox: bool = False + ) -> 'AbstractChart': subchart = AbstractChart(self, width, height, scale_candles_only, toolbox, position=position) if not sync_id: return subchart self.run_script(f''' Handler.syncCharts({subchart.id}, {sync_id}, {jbool(sync_crosshairs_only)}) + // TODO this should be in syncCharts {subchart.id}.chart.timeScale().setVisibleLogicalRange( {sync_id}.chart.timeScale().getVisibleLogicalRange() ) @@ -296,15 +303,15 @@ class SeriesCommon(Pane): Creates a horizontal line at the given price. """ return HorizontalLine(self, price, color, width, style, text, axis_label_visible, func) - - def remove_horizontal_line(self, price: NUM): - """ - Removes a horizontal line at the given price. - """ - self.run_script(f''' - {self.id}.horizontal_lines.forEach(function (line) {{ - if ({price} === line.price) line.deleteLine() - }})''') + # TODO should these methods be removed + # def remove_horizontal_line(self, price: NUM): + # """ + # Removes a horizontal line at the given price. + # """ + # self.run_script(f''' + # {self.id}.horizontal_lines.forEach(function (line) {{ + # if ({price} === line._point.price) line.detach() + # }})''') def clear_markers(self): """ @@ -312,14 +319,14 @@ class SeriesCommon(Pane): """ self.run_script(f'''{self.id}.markers = []; {self.id}.series.setMarkers([])''') - def clear_horizontal_lines(self): - """ - Clears the horizontal lines displayed on the data.\n - """ - self.run_script(f''' - {self.id}.horizontal_lines.forEach(function (line) {{{self.id}.series.removePriceLine(line.line);}}); - {self.id}.horizontal_lines = []; - ''') + # def clear_horizontal_lines(self): + # """ + # Clears the horizontal lines displayed on the data.\n + # """ + # self.run_script(f''' + # {self.id}.horizontal_lines.forEach(function (line) {{{self.id}.series.removePriceLine(line.line);}}); + # {self.id}.horizontal_lines = []; + # ''') def price_line(self, label_visible: bool = True, line_visible: bool = True, title: str = ''): self.run_script(f''' @@ -370,16 +377,27 @@ class SeriesCommon(Pane): end_time = self._single_datetime_format(end_time) if end_time else None return VerticalSpan(self, start_time, end_time, color) - +# TODO drawings should be in a seperate folder, and inherbit a abstract Drawing class class HorizontalLine(Pane): def __init__(self, chart, price, color, width, style, text, axis_label_visible, func): super().__init__(chart.win) self.price = price self.run_script(f''' + {self.id} = new HorizontalLine( - {chart.id}, '{self.id}', {price}, '{color}', {width}, - {as_enum(style, LINE_STYLE)}, {jbool(axis_label_visible)}, '{text}' - )''') + {{price: {price}}}, + {{ + lineColor: '{color}', + lineStyle: {as_enum(style, LINE_STYLE)}, + }}, + callbackName={f"'{self.id}'" if func else 'null'} + ) + {chart.id}.series.attachPrimitive({self.id}) + ''') + # {self.id} = new HorizontalLine( + # {chart.id}, '{self.id}', {price}, '{color}', {width}, + # {as_enum(style, LINE_STYLE)}, {jbool(axis_label_visible)}, '{text}' + # )''') if not func: return @@ -392,24 +410,24 @@ class HorizontalLine(Pane): await func(chart, self) self.win.handlers[self.id] = wrapper_async if asyncio.iscoroutinefunction(func) else wrapper - self.run_script(f'if ("toolBox" in {chart.id}) {chart.id}.toolBox.drawings.push({self.id})') + self.run_script(f'{chart.id}.toolBox?.addNewDrawing({self.id})') - def update(self, price): + def update(self, price: float): """ Moves the horizontal line to the given price. """ - self.run_script(f'{self.id}.updatePrice({price})') + self.run_script(f'{self.id}.updatePoints({{price: {price}}})') + # self.run_script(f'{self.id}.updatePrice({price})') self.price = price - def label(self, text: str): + def label(self, text: str): # TODO self.run_script(f'{self.id}.updateLabel("{text}")') - def delete(self): + def delete(self): # TODO test all methods """ Irreversibly deletes the horizontal line. """ - self.run_script(f'{self.id}.deleteLine()') - del self + self.run_script(f'{self.id}.detach()') class VerticalSpan(Pane): diff --git a/lightweight_charts/chart.py b/lightweight_charts/chart.py index 9f9e606..671f535 100644 --- a/lightweight_charts/chart.py +++ b/lightweight_charts/chart.py @@ -59,9 +59,11 @@ class PyWV: background_color='#000000') ) + self.windows[-1].events.loaded += lambda: self.loaded_event.set() + def loop(self): - self.loaded_event.set() + # self.loaded_event.set() while self.is_alive: i, arg = self.queue.get() diff --git a/lightweight_charts/js/bundle.js b/lightweight_charts/js/bundle.js index 3feb80e..4361326 100644 --- a/lightweight_charts/js/bundle.js +++ b/lightweight_charts/js/bundle.js @@ -1 +1 @@ -import{LineStyle as t,isUTCTimestamp as e,isBusinessDay as i,createChart as s,ColorType as n,CrosshairMode as o}from"lightweight-charts";class r{_div;callbackName;borderColor;borderWidth;table;rows={};headings;widths;alignments;footer;header;constructor(t,e,i,s,n,o,r=!1,a,l,d,h,c){this._div=document.createElement("div"),this.callbackName=null,this.borderColor=l,this.borderWidth=d,r?(this._div.style.position="absolute",this._div.style.cursor="move"):(this._div.style.position="relative",this._div.style.float=o),this._div.style.zIndex="2000",this.reSize(t,e),this._div.style.display="flex",this._div.style.flexDirection="column",this._div.style.borderRadius="5px",this._div.style.color="white",this._div.style.fontSize="12px",this._div.style.fontVariantNumeric="tabular-nums",this.table=document.createElement("table"),this.table.style.width="100%",this.table.style.borderCollapse="collapse",this._div.style.overflow="hidden",this.headings=i,this.widths=s.map((t=>100*t+"%")),this.alignments=n;let p=this.table.createTHead().insertRow();for(let t=0;t0?c[t]:a,e.style.color=h[t],p.appendChild(e)}let u,_,m=document.createElement("div");if(m.style.overflowY="auto",m.style.overflowX="hidden",m.style.backgroundColor=a,m.appendChild(this.table),this._div.appendChild(m),window.containerDiv.appendChild(this._div),!r)return;let v=t=>{this._div.style.left=t.clientX-u+"px",this._div.style.top=t.clientY-_+"px"},y=()=>{document.removeEventListener("mousemove",v),document.removeEventListener("mouseup",y)};this._div.addEventListener("mousedown",(t=>{u=t.clientX-this._div.offsetLeft,_=t.clientY-this._div.offsetTop,document.addEventListener("mousemove",v),document.addEventListener("mouseup",y)}))}divToButton(t,e){t.addEventListener("mouseover",(()=>t.style.backgroundColor="rgba(60, 60, 60, 0.6)")),t.addEventListener("mouseout",(()=>t.style.backgroundColor="transparent")),t.addEventListener("mousedown",(()=>t.style.backgroundColor="rgba(60, 60, 60)")),t.addEventListener("click",(()=>window.callbackFunction(e))),t.addEventListener("mouseup",(()=>t.style.backgroundColor="rgba(60, 60, 60, 0.6)"))}newRow(t,e=!1){let i=this.table.insertRow();i.style.cursor="default";for(let s=0;s\n \`\n `,n=`\n \n `,o=document.createElement("div");o.style.display="flex",o.style.alignItems="center";let r=document.createElement("div"),a=document.createElement("div");a.classList.add("legend-toggle-switch");let l=document.createElementNS("http://www.w3.org/2000/svg","svg");l.setAttribute("width","22"),l.setAttribute("height","16");let d=document.createElementNS("http://www.w3.org/2000/svg","g");d.innerHTML=s;let h=!0;a.addEventListener("click",(()=>{h?(h=!1,d.innerHTML=n,e.applyOptions({visible:!1})):(h=!0,e.applyOptions({visible:!0}),d.innerHTML=s)})),l.appendChild(d),a.appendChild(l),o.appendChild(r),o.appendChild(a),this.div.appendChild(o);const c=e.options().baseLineColor;this._lines.push({name:t,div:r,row:o,toggle:a,series:e,solid:c.startsWith("rgba")?c.replace(/[^,]+(?=\))/,"1"):c})}legendItemFormat(t,e){return t.toFixed(e).toString().padStart(8," ")}shorthandFormat(t){const e=Math.abs(t);return e>=1e6?(t/1e6).toFixed(1)+"M":e>=1e3?(t/1e3).toFixed(1)+"K":t.toString().padStart(8," ")}legendHandler(t,e=!1){if(!this.ohlcEnabled&&!this.linesEnabled&&!this.percentEnabled)return;const i=this.handler.series.options();if(!t.time)return this.candle.style.color="transparent",void(this.candle.innerHTML=this.candle.innerHTML.replace(i.upColor,"").replace(i.downColor,""));let s,n=null;if(e){const e=this.handler.chart.timeScale();let i=e.timeToCoordinate(t.time);i&&(n=e.coordinateToLogical(i.valueOf())),n&&(s=this.handler.series.dataByIndex(n.valueOf()))}else s=t.seriesData.get(this.handler.series);this.candle.style.color="";let o='';if(s){if(this.ohlcEnabled&&(o+=`O ${this.legendItemFormat(s.open,this.handler.precision)} `,o+=`| H ${this.legendItemFormat(s.high,this.handler.precision)} `,o+=`| L ${this.legendItemFormat(s.low,this.handler.precision)} `,o+=`| C ${this.legendItemFormat(s.close,this.handler.precision)} `),this.percentEnabled){let t=(s.close-s.open)/s.open*100,e=t>0?i.upColor:i.downColor,n=`${t>=0?"+":""}${t.toFixed(2)} %`;this.colorBasedOnCandle?o+=`| ${n}`:o+="| "+n}if(this.handler.volumeSeries){let e;e=n?this.handler.volumeSeries.dataByIndex(n):t.seriesData.get(this.handler.volumeSeries),e&&(o+=this.ohlcEnabled?`
V ${this.shorthandFormat(e.value)}`:"")}}this.candle.innerHTML=o+"
",this._lines.forEach((i=>{if(!this.linesEnabled)return void(i.row.style.display="none");let s,o;if(i.row.style.display="flex",s=e&&n?i.series.dataByIndex(n):t.seriesData.get(i.series),"Histogram"==i.series.seriesType())o=this.shorthandFormat(s.value);else{const t=i.series.options().priceFormat;o=this.legendItemFormat(s.value,t.precision)}i.div.innerHTML=` ${i.name} : ${o}`}))}}function d(t){if(void 0===t)throw new Error("Value is undefined");return t}class h{_chart=void 0;_series=void 0;requestUpdate(){this._requestUpdate&&this._requestUpdate()}_requestUpdate;attached({chart:t,series:e,requestUpdate:i}){this._chart=t,this._series=e,this._series.subscribeDataChanged(this._fireDataUpdated),this._requestUpdate=i,this.requestUpdate()}detached(){this._chart=void 0,this._series=void 0,this._requestUpdate=void 0}get chart(){return d(this._chart)}get series(){return d(this._series)}_fireDataUpdated(t){this.dataUpdated&&this.dataUpdated(t)}}const c={lineColor:"rgb(255, 255, 255)",lineStyle:t.Solid,width:4,showLabels:!0,showCircles:!1};function p(t){if(e(t))return 1e3*t;if(i(t))return new Date(t.year,t.month,t.day).valueOf();const[s,n,o]=t.split("-").map(parseInt);return new Date(s,n,o).valueOf()}var u;!function(t){t[t.NONE=0]="NONE",t[t.HOVERING=1]="HOVERING",t[t.DRAGGING=2]="DRAGGING",t[t.DRAGGINGP1=3]="DRAGGINGP1",t[t.DRAGGINGP2=4]="DRAGGINGP2",t[t.DRAGGINGP3=5]="DRAGGINGP3",t[t.DRAGGINGP4=6]="DRAGGINGP4"}(u||(u={}));class _ extends h{_paneViews=[];_options;_state=u.NONE;_startDragPoint=null;_latestHoverPoint=null;static _mouseIsDown=!1;static hoveredObject=null;static lastHoveredObject=null;_listeners=[];constructor(t){super(),this._options={...c,...t}}updateAllViews(){this._paneViews.forEach((t=>t.update()))}paneViews(){return this._paneViews}applyOptions(t){this._options={...this._options,...t},this.requestUpdate()}detach(){this.series.detachPrimitive(this);for(const t of this._listeners)document.body.removeEventListener(t.name,t.listener)}_subscribe(t,e){document.body.addEventListener(t,e),this._listeners.push({name:t,listener:e})}_unsubscribe(t,e){document.body.removeEventListener(t,e);const i=this._listeners.find((i=>i.name===t&&i.listener===e));this._listeners.splice(this._listeners.indexOf(i),1)}_handleHoverInteraction(t){if(this._latestHoverPoint=t.point,_._mouseIsDown)this._handleDragInteraction(t);else if(this._mouseIsOverDrawing(t)){if(this._state!=u.NONE)return;this._moveToState(u.HOVERING),_.hoveredObject=_.lastHoveredObject=this}else{if(this._state==u.NONE)return;this._moveToState(u.NONE),_.hoveredObject===this&&(_.hoveredObject=null)}}static _eventToPoint(t,e){if(!e||!t.point||!t.logical)return null;const i=e.coordinateToPrice(t.point.y);return null==i?null:{time:t.time||null,logical:t.logical,price:i.valueOf()}}static _getDiff(t,e){const i={time:null,logical:t.logical-e.logical,price:t.price-e.price};return t.time&&e.time&&(i.time=p(t.time)-p(e.time)),i}static _addDiffToPoint(t,e,i,s){null!=e&&null!=t.time?t.time=(p(t.time)+e)/1e3:t.time=null,t.logical=t.logical+i,t.price=t.price+s}_handleMouseDownInteraction=()=>{_._mouseIsDown||(_._mouseIsDown=!0,this._onMouseDown())};_handleMouseUpInteraction=()=>{_._mouseIsDown&&(_._mouseIsDown=!1,this._moveToState(u.HOVERING))};_handleDragInteraction(t){if(this._state!=u.DRAGGING&&this._state!=u.DRAGGINGP1&&this._state!=u.DRAGGINGP2&&this._state!=u.DRAGGINGP3&&this._state!=u.DRAGGINGP4)return;const e=_._eventToPoint(t,this.series);if(!e)return;this._startDragPoint=this._startDragPoint||e;const i=_._getDiff(e,this._startDragPoint);this._onDrag(i),this.requestUpdate(),this._startDragPoint=e}}class m{_chart;_series;_finishDrawingCallback=null;_drawings=[];_activeDrawing=null;_isDrawing=!1;_drawingType=null;constructor(t,e,i=null){this._chart=t,this._series=e,this._finishDrawingCallback=i,this._chart.subscribeClick(this._clickHandler),this._chart.subscribeCrosshairMove(this._moveHandler)}_clickHandler=t=>this._onClick(t);_moveHandler=t=>this._onMouseMove(t);beginDrawing(t){this._drawingType=t,this._isDrawing=!0}stopDrawing(){this._isDrawing=!1,this._activeDrawing=null}get drawings(){return this._drawings}addNewDrawing(t){this._series.attachPrimitive(t),this._drawings.push(t)}delete(t){if(null==t)return;const e=this._drawings.indexOf(t);-1!=e&&(this._drawings.splice(e,1),t.detach())}clearDrawings(){for(const t of this._drawings)t.detach();this._drawings=[]}_onClick(t){if(!this._isDrawing)return;const e=_._eventToPoint(t,this._series);if(e)if(null==this._activeDrawing){if(null==this._drawingType)return;this._activeDrawing=new this._drawingType(e,e),this._series.attachPrimitive(this._activeDrawing)}else{if(this._drawings.push(this._activeDrawing),this.stopDrawing(),!this._finishDrawingCallback)return;this._finishDrawingCallback()}}_onMouseMove(t){if(!t)return;for(const e of this._drawings)e._handleHoverInteraction(t);if(!this._isDrawing||!this._activeDrawing)return;const e=_._eventToPoint(t,this._series);e&&this._activeDrawing.updatePoints(null,e)}}class v{_options;constructor(t){this._options=t}}class y extends v{_p1;_p2;_text1;_text2;constructor(t,e,i,s,n){super(n),this._p1=t,this._p2=e,this._text1=i,this._text2=s}_getScaledCoordinates(t){return null===this._p1.x||null===this._p1.y||null===this._p2.x||null===this._p2.y?null:{x1:Math.round(this._p1.x*t.horizontalPixelRatio),y1:Math.round(this._p1.y*t.verticalPixelRatio),x2:Math.round(this._p2.x*t.horizontalPixelRatio),y2:Math.round(this._p2.y*t.verticalPixelRatio)}}_drawEndCircle(t,e,i){t.context.fillStyle="#000",t.context.beginPath(),t.context.arc(e,i,9,0,2*Math.PI),t.context.stroke(),t.context.fill()}}class g extends y{constructor(t,e,i,s,n){super(t,e,i,s,n)}draw(t){t.useBitmapCoordinateSpace((t=>{if(null===this._p1.x||null===this._p1.y||null===this._p2.x||null===this._p2.y)return;const e=t.context,i=this._getScaledCoordinates(t);i&&(e.lineWidth=this._options.width,e.strokeStyle=this._options.lineColor,e.beginPath(),e.moveTo(i.x1,i.y1),e.lineTo(i.x2,i.y2),e.stroke(),this._options.showCircles&&(this._drawEndCircle(t,i.x1,i.y1),this._drawEndCircle(t,i.x2,i.y2)))}))}}class w{_source;constructor(t){this._source=t}}class b extends w{_p1={x:null,y:null};_p2={x:null,y:null};_source;constructor(t){super(t),this._source=t}update(){const t=this._source.series,e=t.priceToCoordinate(this._source._p1.price),i=t.priceToCoordinate(this._source._p2.price),s=this._getX(this._source._p1),n=this._getX(this._source._p2);this._p1={x:s,y:e},this._p2={x:n,y:i}}_getX(t){const e=this._source.chart.timeScale();return t.time?e.timeToCoordinate(t.time):e.logicalToCoordinate(t.logical)}}class x extends b{constructor(t){super(t)}renderer(){return new g(this._p1,this._p2,""+this._source._p1.price.toFixed(1),""+this._source._p2.price.toFixed(1),this._source._options)}}class C extends _{_p1;_p2;_paneViews=[];constructor(t,e,i){super(),this._p1=t,this._p2=e,this._options={...c,...i}}setFirstPoint(t){this.updatePoints(t)}setSecondPoint(t){this.updatePoints(null,t)}updatePoints(...t){this._p1=t[0]||this._p1,this._p2=t[1]||this._p2,this.requestUpdate()}}class f extends C{_type="TrendLine";constructor(t,e,i){super(t,e,i),this._paneViews=[new x(this)]}_moveToState(t){switch(t){case u.NONE:document.body.style.cursor="default",this._options.showCircles=!1,this.requestUpdate(),this._unsubscribe("mousedown",this._handleMouseDownInteraction);break;case u.HOVERING:document.body.style.cursor="pointer",this._options.showCircles=!0,this.requestUpdate(),this._subscribe("mousedown",this._handleMouseDownInteraction),this._unsubscribe("mouseup",this._handleMouseDownInteraction),this.chart.applyOptions({handleScroll:!0});break;case u.DRAGGINGP1:case u.DRAGGINGP2:case u.DRAGGING:document.body.style.cursor="grabbing",this._subscribe("mouseup",this._handleMouseUpInteraction),this.chart.applyOptions({handleScroll:!1})}this._state=t}_onDrag(t){this._state!=u.DRAGGING&&this._state!=u.DRAGGINGP1||_._addDiffToPoint(this._p1,t.time,t.logical,t.price),this._state!=u.DRAGGING&&this._state!=u.DRAGGINGP2||_._addDiffToPoint(this._p2,t.time,t.logical,t.price)}_onMouseDown(){this._startDragPoint=null;const t=this._latestHoverPoint;if(!t)return;const e=this._paneViews[0]._p1,i=this._paneViews[0]._p2;if(!(e.x&&i.x&&e.y&&i.y))return this._moveToState(u.DRAGGING);Math.abs(t.x-e.x)<10&&Math.abs(t.y-e.y)<10?this._moveToState(u.DRAGGINGP1):Math.abs(t.x-i.x)<10&&Math.abs(t.y-i.y)<10?this._moveToState(u.DRAGGINGP2):this._moveToState(u.DRAGGING)}_mouseIsOverDrawing(t,e=4){if(!t.point)return!1;const i=this._paneViews[0]._p1.x,s=this._paneViews[0]._p1.y,n=this._paneViews[0]._p2.x,o=this._paneViews[0]._p2.y;if(!(i&&n&&s&&o))return!1;const r=t.point.x,a=t.point.y;if(r<=Math.min(i,n)-e||r>=Math.max(i,n)+e)return!1;return Math.abs((o-s)*r-(n-i)*a+n*s-o*i)/Math.sqrt((o-s)**2+(n-i)**2)<=e}}class D extends y{constructor(t,e,i,s,n){super(t,e,i,s,n)}draw(t){t.useBitmapCoordinateSpace((t=>{const e=t.context,i=this._getScaledCoordinates(t);if(!i)return;e.lineWidth=this._options.width,e.strokeStyle=this._options.lineColor,e.fillStyle=this._options.fillColor;const s=Math.min(i.x1,i.x2),n=Math.min(i.y1,i.y2),o=Math.abs(i.x1-i.x2),r=Math.abs(i.y1-i.y2);e.strokeRect(s,n,o,r),e.fillRect(s,n,o,r),this._options.showCircles&&(this._drawEndCircle(t,s,n),this._drawEndCircle(t,s+o,n),this._drawEndCircle(t,s+o,n+r),this._drawEndCircle(t,s,n+r))}))}}class E extends b{constructor(t){super(t)}renderer(){return new D(this._p1,this._p2,""+this._source._p1.price.toFixed(1),""+this._source._p2.price.toFixed(1),this._source._options)}}const k={fillEnabled:!0,fillColor:"rgba(255, 255, 255, 0.2)",...c};class L extends C{_type="Box";constructor(t,e,i){super(t,e,i),this._options={...this._options,...k},this._paneViews=[new E(this)]}_moveToState(t){switch(t){case u.NONE:document.body.style.cursor="default",this.applyOptions({showCircles:!1}),this._unsubscribe("mousedown",this._handleMouseDownInteraction);break;case u.HOVERING:document.body.style.cursor="pointer",this.applyOptions({showCircles:!0}),this._unsubscribe("mouseup",this._handleMouseUpInteraction),this._subscribe("mousedown",this._handleMouseDownInteraction),this.chart.applyOptions({handleScroll:!0});break;case u.DRAGGINGP1:case u.DRAGGINGP2:case u.DRAGGINGP3:case u.DRAGGINGP4:case u.DRAGGING:document.body.style.cursor="grabbing",document.body.addEventListener("mouseup",this._handleMouseUpInteraction),this._subscribe("mouseup",this._handleMouseUpInteraction),this.chart.applyOptions({handleScroll:!1})}this._state=t}_onDrag(t){this._state!=u.DRAGGING&&this._state!=u.DRAGGINGP1||_._addDiffToPoint(this._p1,t.time,t.logical,t.price),this._state!=u.DRAGGING&&this._state!=u.DRAGGINGP2||_._addDiffToPoint(this._p2,t.time,t.logical,t.price),this._state!=u.DRAGGING&&(this._state==u.DRAGGINGP3&&(_._addDiffToPoint(this._p1,t.time,t.logical,0),_._addDiffToPoint(this._p2,0,0,t.price)),this._state==u.DRAGGINGP4&&(_._addDiffToPoint(this._p1,0,0,t.price),_._addDiffToPoint(this._p2,t.time,t.logical,0)))}_onMouseDown(){this._startDragPoint=null;const t=this._latestHoverPoint,e=this._paneViews[0]._p1,i=this._paneViews[0]._p2;if(!(e.x&&i.x&&e.y&&i.y))return this._moveToState(u.DRAGGING);const s=10;Math.abs(t.x-e.x)l-p&&rd-p&&athis._onClick(t);_onClick(t){t.target&&(this.div.contains(t.target)||(this.div.style.display="none",document.body.removeEventListener("click",this._handleClick)))}_onRightClick(t){_.hoveredObject&&(t.preventDefault(),this.div.style.left=t.clientX+"px",this.div.style.top=t.clientY+"px",this.div.style.display="block",document.body.addEventListener("click",this._handleClick))}menuItem(t,e,i=null){const s=document.createElement("span");s.classList.add("context-menu-item"),this.div.appendChild(s);const n=document.createElement("span");if(n.innerText=t,n.style.pointerEvents="none",s.appendChild(n),i){let t=document.createElement("span");t.innerText="►",t.style.fontSize="8px",t.style.pointerEvents="none",s.appendChild(t)}if(s.addEventListener("mouseover",(()=>{this.hoverItem&&this.hoverItem.closeAction&&this.hoverItem.closeAction(),this.hoverItem={elem:n,action:e,closeAction:i}})),i){let t;s.addEventListener("mouseover",(()=>t=setTimeout((()=>e(s.getBoundingClientRect())),100))),s.addEventListener("mouseout",(()=>clearTimeout(t)))}else s.addEventListener("click",(t=>{e(t),this.div.style.display="none"}))}separator(){const t=document.createElement("div");t.style.width="90%",t.style.height="1px",t.style.margin="3px 0px",t.style.backgroundColor=window.pane.borderColor,this.div.appendChild(t)}}class S{static _styles=[{name:"Solid",var:t.Solid},{name:"Dotted",var:t.Dotted},{name:"Dashed",var:t.Dashed},{name:"Large Dashed",var:t.LargeDashed},{name:"Sparse Dotted",var:t.SparseDotted}];_div;_saveDrawings;constructor(t){this._saveDrawings=t,this._div=document.createElement("div"),this._div.classList.add("context-menu"),S._styles.forEach((t=>{this._div.appendChild(this._makeTextBox(t.name,t.var))})),window.containerDiv.appendChild(this._div)}_makeTextBox(t,e){const i=document.createElement("span");return i.classList.add("context-menu-item"),i.innerText=t,i.addEventListener("click",(()=>{_.lastHoveredObject?.applyOptions({lineStyle:e}),this._saveDrawings()})),i}openMenu(t){this._div.style.top=t.top-30+"px",this._div.style.left=t.right+"px",this._div.style.display="block",setTimeout((()=>document.addEventListener("mousedown",(t=>{this._div.contains(t.target)||this.closeMenu()}))),10)}closeMenu(){document.removeEventListener("click",this.closeMenu),this._div.style.display="none"}}class I{static 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"];_div;saveDrawings;opacity=0;_opacitySlider;_opacityLabel;rgba;constructor(t){this.saveDrawings=t,this._div=document.createElement("div"),this._div.classList.add("color-picker");let e=document.createElement("div");e.style.margin="10px",e.style.display="flex",e.style.flexWrap="wrap",I.colors.forEach((t=>e.appendChild(this.makeColorBox(t))));let i=document.createElement("div");i.style.backgroundColor=window.pane.borderColor,i.style.height="1px",i.style.width="130px";let s=document.createElement("div");s.style.margin="10px";let n=document.createElement("div");n.style.color="lightgray",n.style.fontSize="12px",n.innerText="Opacity",this._opacityLabel=document.createElement("div"),this._opacityLabel.style.color="lightgray",this._opacityLabel.style.fontSize="12px",this._opacitySlider=document.createElement("input"),this._opacitySlider.type="range",this._opacitySlider.value=(100*this.opacity).toString(),this._opacityLabel.innerText=this._opacitySlider.value+"%",this._opacitySlider.oninput=()=>{this._opacityLabel.innerText=this._opacitySlider.value+"%",this.opacity=parseInt(this._opacitySlider.value)/100,this.updateColor()},s.appendChild(n),s.appendChild(this._opacitySlider),s.appendChild(this._opacityLabel),this._div.appendChild(e),this._div.appendChild(i),this._div.appendChild(s),window.containerDiv.appendChild(this._div)}_updateOpacitySlider(){this._opacitySlider.value=(100*this.opacity).toString(),this._opacityLabel.innerText=this._opacitySlider.value+"%"}makeColorBox(t){const e=document.createElement("div");e.style.width="18px",e.style.height="18px",e.style.borderRadius="3px",e.style.margin="3px",e.style.boxSizing="border-box",e.style.backgroundColor=t,e.addEventListener("mouseover",(()=>e.style.border="2px solid lightgray")),e.addEventListener("mouseout",(()=>e.style.border="none"));const i=I.extractRGBA(t);return e.addEventListener("click",(()=>{this.rgba=i,this.updateColor()})),e}static extractRGBA(t){const e=document.createElement("div");e.style.color=t,document.body.appendChild(e);const i=getComputedStyle(e).color;document.body.removeChild(e);const s=i.match(/\d+/g)?.map(Number);if(!s)return[];let n=i.includes("rgba")?parseFloat(i.split(",")[3]):1;return[s[0],s[1],s[2],n]}updateColor(){if(!_.lastHoveredObject||!this.rgba)return;const t=`rgba(${this.rgba[0]}, ${this.rgba[1]}, ${this.rgba[2]}, ${this.opacity})`;_.lastHoveredObject.applyOptions({lineColor:t}),this.saveDrawings()}openMenu(t){_.lastHoveredObject&&(this.rgba=I.extractRGBA(_.lastHoveredObject._options.lineColor),this.opacity=this.rgba[3],this._updateOpacitySlider(),this._div.style.top=t.top-30+"px",this._div.style.left=t.right+"px",this._div.style.display="flex",setTimeout((()=>document.addEventListener("mousedown",(t=>{this._div.contains(t.target)||this.closeMenu()}))),10))}closeMenu(){document.body.removeEventListener("click",this.closeMenu),this._div.style.display="none"}}class M extends v{_point={x:null,y:null};constructor(t,e){super(e),this._point=t}draw(t){t.useBitmapCoordinateSpace((t=>{if(null==this._point.y)return;const e=t.context,i=Math.round(this._point.y*t.verticalPixelRatio),s=this._point.x?this._point.x*t.horizontalPixelRatio:0;e.lineWidth=this._options.width,e.strokeStyle=this._options.lineColor,e.beginPath(),e.moveTo(s,i),e.lineTo(t.bitmapSize.width,i),e.stroke()}))}}class T extends w{_source;_point={x:null,y:null};constructor(t){super(t),this._source=t}update(){const t=this._source._point,e=this._source.chart.timeScale(),i=this._source.series;this._point.x=t.time?e.timeToCoordinate(t.time):null,this._point.y=i.priceToCoordinate(t.price)}renderer(){return new M(this._point,this._source._options)}}class R extends _{_type="HorizontalLine";_paneViews;_point;_startDragPoint=null;constructor(t,e){super(e),this._point=t,this._point.time=null,this._paneViews=[new T(this)]}updatePoints(...t){for(const e of t)e&&(this._point.price=e.price);this.requestUpdate()}_moveToState(t){switch(t){case u.NONE:document.body.style.cursor="default",this._unsubscribe("mousedown",this._handleMouseDownInteraction);break;case u.HOVERING:document.body.style.cursor="pointer",this._unsubscribe("mouseup",this._handleMouseUpInteraction),this._subscribe("mousedown",this._handleMouseDownInteraction),this.chart.applyOptions({handleScroll:!0});break;case u.DRAGGING:document.body.style.cursor="grabbing",document.body.addEventListener("mouseup",this._handleMouseUpInteraction),this._subscribe("mouseup",this._handleMouseUpInteraction),this.chart.applyOptions({handleScroll:!1})}this._state=t}_onDrag(t){_._addDiffToPoint(this._point,0,0,t.price),this.requestUpdate()}_mouseIsOverDrawing(t,e=4){if(!t.point)return!1;const i=this.series.priceToCoordinate(this._point.price);return!!i&&Math.abs(i-t.point.y)s-e)}}class P{static TREND_SVG='';static HORZ_SVG='';static RAY_SVG='';static BOX_SVG='';div;activeIcon=null;buttons=[];_commandFunctions;_handlerID;_drawingTool;constructor(t,e,i,s){this._handlerID=t,this._commandFunctions=s,this._drawingTool=new m(e,i,(()=>this.removeActiveAndSave())),this.div=this._makeToolBox(),this._makeContextMenu(),s.push((t=>{if((t.metaKey||t.ctrlKey)&&"KeyZ"===t.code){const t=this._drawingTool.drawings.pop();return t&&this._drawingTool.delete(t),!0}return!1}))}toJSON(){const{...t}=this;return t}_makeToolBox(){let t=document.createElement("div");t.classList.add("toolbox"),this.buttons.push(this._makeToolBoxElement(f,"KeyT",P.TREND_SVG)),this.buttons.push(this._makeToolBoxElement(R,"KeyH",P.HORZ_SVG)),this.buttons.push(this._makeToolBoxElement(N,"KeyR",P.RAY_SVG)),this.buttons.push(this._makeToolBoxElement(L,"KeyB",P.BOX_SVG));for(const e of this.buttons)t.appendChild(e);return t}_makeToolBoxElement(t,e,i){const s=document.createElement("div");s.classList.add("toolbox-button");const n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("width","29"),n.setAttribute("height","29");const o=document.createElementNS("http://www.w3.org/2000/svg","g");o.innerHTML=i,o.setAttribute("fill",window.pane.color),n.appendChild(o),s.appendChild(n);const r={div:s,group:o,type:t};return s.addEventListener("click",(()=>this._onIconClick(r))),this._commandFunctions.push((t=>this._handlerID===window.handlerInFocus&&(!(!t.altKey||t.code!==e)&&(t.preventDefault(),this._onIconClick(r),!0)))),s}_onIconClick(t){this.activeIcon&&(this.activeIcon.div.classList.remove("active-toolbox-button"),window.setCursor("crosshair"),this._drawingTool?.stopDrawing(),this.activeIcon===t)?this.activeIcon=null:(this.activeIcon=t,this.activeIcon.div.classList.add("active-toolbox-button"),window.setCursor("crosshair"),this._drawingTool?.beginDrawing(this.activeIcon.type))}removeActiveAndSave(){window.setCursor("default"),this.activeIcon&&this.activeIcon.div.classList.remove("active-toolbox-button"),this.activeIcon=null,this.saveDrawings()}_makeContextMenu(){const t=new G,e=new I(this.saveDrawings),i=new S(this.saveDrawings);t.menuItem("Color Picker",(t=>e.openMenu(t)),(()=>{document.removeEventListener("click",e.closeMenu),e._div.style.display="none"})),t.menuItem("Style",(t=>i.openMenu(t)),(()=>{document.removeEventListener("click",i.closeMenu),i._div.style.display="none"})),t.separator(),t.menuItem("Delete Drawing",(()=>this._drawingTool.delete(_.lastHoveredObject)))}clearDrawings(){this._drawingTool.clearDrawings()}saveDrawings(){const t=[];for(const e of this._drawingTool.drawings)e instanceof C&&t.push({type:e._type,p1:e._p1,p2:e._p2,color:e._options.lineColor,style:e._options.lineStyle});const e=JSON.stringify(t);window.callbackFunction(`save_drawings${this._handlerID}_~_${e}`)}loadDrawings(t){t.forEach((t=>{const e={lineColor:t.color,lineStyle:t.style};switch(t.type){case"Box":this._drawingTool.addNewDrawing(new L(t.p1,t.p2,e));break;case"TrendLine":this._drawingTool.addNewDrawing(new f(t.p1,t.p2,e))}}))}}class A{_handler;_div;left;right;constructor(t){this._handler=t,this._div=document.createElement("div"),this._div.classList.add("topbar");const e=t=>{const e=document.createElement("div");return e.classList.add("topbar-container"),e.style.justifyContent=t,this._div.appendChild(e),e};this.left=e("flex-start"),this.right=e("flex-end")}makeSwitcher(t,e,i,s="left"){const n=document.createElement("div");let o;n.style.margin="4px 12px";const r={elem:n,callbackName:i,intervalElements:t.map((t=>{const i=document.createElement("button");i.classList.add("topbar-button"),i.classList.add("switcher-button"),i.style.margin="0px 2px",i.innerText=t,t==e&&(o=i,i.classList.add("active-switcher-button"));const s=A.getClientWidth(i);return i.style.minWidth=s+1+"px",i.addEventListener("click",(()=>r.onItemClicked(i))),n.appendChild(i),i})),onItemClicked:t=>{t!=o&&(o.classList.remove("active-switcher-button"),t.classList.add("active-switcher-button"),o=t,window.callbackFunction(`${r.callbackName}_~_${t.innerText}`))}};return this.appendWidget(n,s,!0),r}makeTextBoxWidget(t,e="left"){const i=document.createElement("div");return i.classList.add("topbar-textbox"),i.innerText=t,this.appendWidget(i,e,!0),i}makeMenu(t,e,i,s,n="right"){let o=document.createElement("div");o.classList.add("topbar-menu");let r=!1;t.forEach((t=>{let e=this.makeButton(t,null,!1,!1);e.elem.addEventListener("click",(()=>{a.elem.innerText=e.elem.innerText+" ↓",window.callbackFunction(`${s}_~_${e.elem.innerText}`),o.style.display="none",r=!1})),e.elem.style.margin="4px 4px",e.elem.style.padding="2px 2px",o.appendChild(e.elem)}));let a=this.makeButton(e+" ↓",null,i,!0,n);a.elem.addEventListener("click",(()=>{if(r=!r,!r)return void(o.style.display="none");let t=a.elem.getBoundingClientRect();o.style.display="flex",o.style.flexDirection="column";let e=t.x+t.width/2;o.style.left=e-o.clientWidth/2+"px",o.style.top=t.y+t.height+"px"})),document.body.appendChild(o)}makeButton(t,e,i,s=!0,n="left"){let o=document.createElement("button");o.classList.add("topbar-button"),o.innerText=t,document.body.appendChild(o),o.style.minWidth=o.clientWidth+1+"px",document.body.removeChild(o);let r={elem:o,callbackName:e};return e&&o.addEventListener("click",(()=>window.callbackFunction(`${r.callbackName}_~_${o.innerText}`))),s&&this.appendWidget(o,n,i),r}makeSeparator(t="left"){const e=document.createElement("div");e.classList.add("topbar-seperator");("left"==t?this.left:this.right).appendChild(e)}appendWidget(t,e,i){const s="left"==e?this.left:this.right;i?("left"==e&&s.appendChild(t),this.makeSeparator(e),"right"==e&&s.appendChild(t)):s.appendChild(t),this._handler.reSize()}static getClientWidth(t){document.body.appendChild(t);const e=t.clientWidth;return document.body.removeChild(t),e}}window.pane={...a},window.containerDiv=document.getElementById("container")||document.createElement("div"),window.setCursor=t=>{t&&(window.cursor=t),document.body.style.cursor=window.cursor},window.cursor="default",window.Table=r;class B{id;commandFunctions=[];wrapper;div;chart;scale;horizontal_lines=[];markers=[];precision=2;series;volumeSeries;legend;_topBar;toolBox;spinner;_seriesList=[];constructor(t,e,i,s,n){this.reSize=this.reSize.bind(this),this.id=t,this.scale={width:e,height:i},this.wrapper=document.createElement("div"),this.wrapper.classList.add("handler"),this.wrapper.style.float=s,this.div=document.createElement("div"),this.div.style.position="relative",this.wrapper.appendChild(this.div),window.containerDiv.append(this.wrapper),this.chart=this._createChart(),this.series=this.createCandlestickSeries(),this.volumeSeries=this.createVolumeSeries(),this.legend=new l(this),document.addEventListener("keydown",(t=>{for(let e=0;ewindow.handlerInFocus=this.id)),this.reSize(),n&&window.addEventListener("resize",(()=>this.reSize()))}reSize(){let t=0!==this.scale.height&&this._topBar?._div.offsetHeight||0;this.chart.resize(window.innerWidth*this.scale.width,window.innerHeight*this.scale.height-t),this.wrapper.style.width=100*this.scale.width+"%",this.wrapper.style.height=100*this.scale.height+"%",0===this.scale.height||0===this.scale.width?(this.legend.div.style.display="none",this.toolBox&&(this.toolBox.div.style.display="none")):(this.legend.div.style.display="flex",this.toolBox&&(this.toolBox.div.style.display="flex"))}_createChart(){return s(this.div,{width:window.innerWidth*this.scale.width,height:window.innerHeight*this.scale.height,layout:{textColor:window.pane.color,background:{color:"#000000",type:n.Solid},fontSize:12},rightPriceScale:{scaleMargins:{top:.3,bottom:.25}},timeScale:{timeVisible:!0,secondsVisible:!1},crosshair:{mode:o.Normal,vertLine:{labelBackgroundColor:"rgb(46, 46, 46)"},horzLine:{labelBackgroundColor:"rgb(55, 55, 55)"}},grid:{vertLines:{color:"rgba(29, 30, 38, 5)"},horzLines:{color:"rgba(29, 30, 58, 5)"}},handleScroll:{vertTouchDrag:!0}})}createCandlestickSeries(){const t="rgba(39, 157, 130, 100)",e="rgba(200, 97, 100, 100)",i=this.chart.addCandlestickSeries({upColor:t,borderUpColor:t,wickUpColor:t,downColor:e,borderDownColor:e,wickDownColor:e});return i.priceScale().applyOptions({scaleMargins:{top:.2,bottom:.2}}),i}createVolumeSeries(){const t=this.chart.addHistogramSeries({color:"#26a69a",priceFormat:{type:"volume"},priceScaleId:"volume_scale"});return t.priceScale().applyOptions({scaleMargins:{top:.8,bottom:0}}),t}createLineSeries(t,e){const i=this.chart.addLineSeries({...e});return this._seriesList.push(i),this.legend.makeSeriesRow(t,i),{name:t,series:i,horizontal_lines:[],markers:[]}}createToolBox(){this.toolBox=new P(this.id,this.chart,this.series,this.commandFunctions),this.div.appendChild(this.toolBox.div)}createTopBar(){return this._topBar=new A(this),this.wrapper.prepend(this._topBar._div),this._topBar}toJSON(){const{chart:t,...e}=this;return e}static syncCharts(t,e,i=!1){function s(t,e){e?(t.chart.setCrosshairPosition(e.value||e.close,e.time,t.series),t.legend.legendHandler(e,!0)):t.chart.clearCrosshairPosition()}function n(t,e){return e.time&&e.seriesData.get(t)||null}const o=e=>{e&&t.chart.timeScale().setVisibleLogicalRange(e)},r=t=>{t&&e.chart.timeScale().setVisibleLogicalRange(t)},a=i=>{s(e,n(t.series,i))},l=i=>{s(t,n(e.series,i))};let d=e;function h(t,e,s,n,o,r){t.wrapper.addEventListener("mouseover",(()=>{d!==t&&(d=t,e.chart.unsubscribeCrosshairMove(s),t.chart.subscribeCrosshairMove(n),i||(e.chart.timeScale().unsubscribeVisibleLogicalRangeChange(o),t.chart.timeScale().subscribeVisibleLogicalRangeChange(r)))}))}h(e,t,a,l,r,o),h(t,e,l,a,o,r),e.chart.subscribeCrosshairMove(l),i||e.chart.timeScale().subscribeVisibleLogicalRangeChange(o)}static makeSearchBox(t){const e=document.createElement("div");e.classList.add("searchbox"),e.style.display="none";const i=document.createElement("div");i.innerHTML='';const s=document.createElement("input");return s.type="text",e.appendChild(i),e.appendChild(s),t.div.appendChild(e),t.commandFunctions.push((i=>(console.log("1"),window.handlerInFocus===t.id&&(console.log(e.style),"none"===e.style.display?(console.log("3"),!!/^[a-zA-Z0-9]$/.test(i.key)&&(console.log("4"),e.style.display="flex",s.focus(),!0)):("Enter"===i.key||"Escape"===i.key)&&("Enter"===i.key&&window.callbackFunction(`search${t.id}_~_${s.value}`),e.style.display="none",s.value="",!0))))),s.addEventListener("input",(()=>s.value=s.value.toUpperCase())),{window:e,box:s}}static makeSpinner(t){t.spinner=document.createElement("div"),t.spinner.classList.add("spinner"),t.wrapper.appendChild(t.spinner);let e=0;!function i(){t.spinner&&(e+=10,t.spinner.style.transform=`translate(-50%, -50%) rotate(${e}deg)`,requestAnimationFrame(i))}()}static _styleMap={"--bg-color":"backgroundColor","--hover-bg-color":"hoverBackgroundColor","--click-bg-color":"clickBackgroundColor","--active-bg-color":"activeBackgroundColor","--muted-bg-color":"mutedBackgroundColor","--border-color":"borderColor","--color":"color","--active-color":"activeColor"};static setRootStyles(t){const e=document.documentElement.style;for(const[i,s]of Object.entries(this._styleMap))e.setProperty(i,t[s])}}window.Handler=B;export{B as Handler}; +import{LineStyle as t,isUTCTimestamp as e,isBusinessDay as i,createChart as s,ColorType as n,CrosshairMode as o}from"lightweight-charts";function r(t){if(void 0===t)throw new Error("Value is undefined");return t}class a{_chart=void 0;_series=void 0;requestUpdate(){this._requestUpdate&&this._requestUpdate()}_requestUpdate;attached({chart:t,series:e,requestUpdate:i}){this._chart=t,this._series=e,this._series.subscribeDataChanged(this._fireDataUpdated),this._requestUpdate=i,this.requestUpdate()}detached(){this._chart=void 0,this._series=void 0,this._requestUpdate=void 0}get chart(){return r(this._chart)}get series(){return r(this._series)}_fireDataUpdated(t){this.dataUpdated&&this.dataUpdated(t)}}const l={lineColor:"rgb(255, 255, 255)",lineStyle:t.Solid,width:4,showLabels:!0,showCircles:!1};function d(t){if(e(t))return 1e3*t;if(i(t))return new Date(t.year,t.month,t.day).valueOf();const[s,n,o]=t.split("-").map(parseInt);return new Date(s,n,o).valueOf()}var h;!function(t){t[t.NONE=0]="NONE",t[t.HOVERING=1]="HOVERING",t[t.DRAGGING=2]="DRAGGING",t[t.DRAGGINGP1=3]="DRAGGINGP1",t[t.DRAGGINGP2=4]="DRAGGINGP2",t[t.DRAGGINGP3=5]="DRAGGINGP3",t[t.DRAGGINGP4=6]="DRAGGINGP4"}(h||(h={}));class c extends a{_paneViews=[];_options;_state=h.NONE;_startDragPoint=null;_latestHoverPoint=null;static _mouseIsDown=!1;static hoveredObject=null;static lastHoveredObject=null;_listeners=[];constructor(t){super(),this._options={...l,...t}}updateAllViews(){this._paneViews.forEach((t=>t.update()))}paneViews(){return this._paneViews}applyOptions(t){this._options={...this._options,...t},this.requestUpdate()}detach(){this._options.lineColor="transparent",this.requestUpdate(),this.series.detachPrimitive(this);for(const t of this._listeners)document.body.removeEventListener(t.name,t.listener)}_subscribe(t,e){document.body.addEventListener(t,e),this._listeners.push({name:t,listener:e})}_unsubscribe(t,e){document.body.removeEventListener(t,e);const i=this._listeners.find((i=>i.name===t&&i.listener===e));this._listeners.splice(this._listeners.indexOf(i),1)}_handleHoverInteraction(t){if(this._latestHoverPoint=t.point,c._mouseIsDown)this._handleDragInteraction(t);else if(this._mouseIsOverDrawing(t)){if(this._state!=h.NONE)return;this._moveToState(h.HOVERING),c.hoveredObject=c.lastHoveredObject=this}else{if(this._state==h.NONE)return;this._moveToState(h.NONE),c.hoveredObject===this&&(c.hoveredObject=null)}}static _eventToPoint(t,e){if(!e||!t.point||!t.logical)return null;const i=e.coordinateToPrice(t.point.y);return null==i?null:{time:t.time||null,logical:t.logical,price:i.valueOf()}}static _getDiff(t,e){const i={time:null,logical:t.logical-e.logical,price:t.price-e.price};return t.time&&e.time&&(i.time=d(t.time)-d(e.time)),i}static _addDiffToPoint(t,e,i,s){null!=e&&null!=t.time?t.time=(d(t.time)+e)/1e3:t.time=null,t.logical=t.logical+i,t.price=t.price+s}_handleMouseDownInteraction=()=>{c._mouseIsDown||(c._mouseIsDown=!0,this._onMouseDown())};_handleMouseUpInteraction=()=>{c._mouseIsDown&&(c._mouseIsDown=!1,this._moveToState(h.HOVERING))};_handleDragInteraction(t){if(this._state!=h.DRAGGING&&this._state!=h.DRAGGINGP1&&this._state!=h.DRAGGINGP2&&this._state!=h.DRAGGINGP3&&this._state!=h.DRAGGINGP4)return;const e=c._eventToPoint(t,this.series);if(!e)return;this._startDragPoint=this._startDragPoint||e;const i=c._getDiff(e,this._startDragPoint);this._onDrag(i),this.requestUpdate(),this._startDragPoint=e}}class p{_options;constructor(t){this._options=t}}class u extends p{_p1;_p2;_text1;_text2;constructor(t,e,i,s,n){super(n),this._p1=t,this._p2=e,this._text1=i,this._text2=s}_getScaledCoordinates(t){return null===this._p1.x||null===this._p1.y||null===this._p2.x||null===this._p2.y?null:{x1:Math.round(this._p1.x*t.horizontalPixelRatio),y1:Math.round(this._p1.y*t.verticalPixelRatio),x2:Math.round(this._p2.x*t.horizontalPixelRatio),y2:Math.round(this._p2.y*t.verticalPixelRatio)}}_drawEndCircle(t,e,i){t.context.fillStyle="#000",t.context.beginPath(),t.context.arc(e,i,9,0,2*Math.PI),t.context.stroke(),t.context.fill()}}class _ extends p{_point={x:null,y:null};constructor(t,e){super(e),this._point=t}draw(t){t.useBitmapCoordinateSpace((t=>{if(null==this._point.y)return;const e=t.context,i=Math.round(this._point.y*t.verticalPixelRatio),s=this._point.x?this._point.x*t.horizontalPixelRatio:0;e.lineWidth=this._options.width,e.strokeStyle=this._options.lineColor,e.beginPath(),e.moveTo(s,i),e.lineTo(t.bitmapSize.width,i),e.stroke()}))}}class m{_source;constructor(t){this._source=t}}class v extends m{_p1={x:null,y:null};_p2={x:null,y:null};_source;constructor(t){super(t),this._source=t}update(){const t=this._source.series,e=t.priceToCoordinate(this._source._p1.price),i=t.priceToCoordinate(this._source._p2.price),s=this._getX(this._source._p1),n=this._getX(this._source._p2);this._p1={x:s,y:e},this._p2={x:n,y:i}}_getX(t){const e=this._source.chart.timeScale();return t.time?e.timeToCoordinate(t.time):e.logicalToCoordinate(t.logical)}}class g extends m{_source;_point={x:null,y:null};constructor(t){super(t),this._source=t}update(){const t=this._source._point,e=this._source.chart.timeScale(),i=this._source.series;this._point.x=t.time?e.timeToCoordinate(t.time):null,this._point.y=i.priceToCoordinate(t.price)}renderer(){return new _(this._point,this._source._options)}}class w extends c{_type="HorizontalLine";_paneViews;_point;_callbackName;_startDragPoint=null;constructor(t,e,i=null){super(e),this._point=t,this._point.time=null,this._paneViews=[new g(this)],this._callbackName=i}updatePoints(...t){for(const e of t)e&&(this._point.price=e.price);this.requestUpdate()}_moveToState(t){switch(t){case h.NONE:document.body.style.cursor="default",this._unsubscribe("mousedown",this._handleMouseDownInteraction);break;case h.HOVERING:document.body.style.cursor="pointer",this._unsubscribe("mouseup",this._childHandleMouseUpInteraction),this._subscribe("mousedown",this._handleMouseDownInteraction),this.chart.applyOptions({handleScroll:!0});break;case h.DRAGGING:document.body.style.cursor="grabbing",this._subscribe("mouseup",this._childHandleMouseUpInteraction),this.chart.applyOptions({handleScroll:!1})}this._state=t}_onDrag(t){c._addDiffToPoint(this._point,0,0,t.price),this.requestUpdate()}_mouseIsOverDrawing(t,e=4){if(!t.point)return!1;const i=this.series.priceToCoordinate(this._point.price);return!!i&&Math.abs(i-t.point.y){this._handleMouseUpInteraction(),this._callbackName&&(console.log(window.callbackFunction),window.callbackFunction(`${this._callbackName}_~_${this._point.price.toFixed(8)}`))}}class y{_div;callbackName;borderColor;borderWidth;table;rows={};headings;widths;alignments;footer;header;constructor(t,e,i,s,n,o,r=!1,a,l,d,h,c){this._div=document.createElement("div"),this.callbackName=null,this.borderColor=l,this.borderWidth=d,r?(this._div.style.position="absolute",this._div.style.cursor="move"):(this._div.style.position="relative",this._div.style.float=o),this._div.style.zIndex="2000",this.reSize(t,e),this._div.style.display="flex",this._div.style.flexDirection="column",this._div.style.borderRadius="5px",this._div.style.color="white",this._div.style.fontSize="12px",this._div.style.fontVariantNumeric="tabular-nums",this.table=document.createElement("table"),this.table.style.width="100%",this.table.style.borderCollapse="collapse",this._div.style.overflow="hidden",this.headings=i,this.widths=s.map((t=>100*t+"%")),this.alignments=n;let p=this.table.createTHead().insertRow();for(let t=0;t0?c[t]:a,e.style.color=h[t],p.appendChild(e)}let u,_,m=document.createElement("div");if(m.style.overflowY="auto",m.style.overflowX="hidden",m.style.backgroundColor=a,m.appendChild(this.table),this._div.appendChild(m),window.containerDiv.appendChild(this._div),!r)return;let v=t=>{this._div.style.left=t.clientX-u+"px",this._div.style.top=t.clientY-_+"px"},g=()=>{document.removeEventListener("mousemove",v),document.removeEventListener("mouseup",g)};this._div.addEventListener("mousedown",(t=>{u=t.clientX-this._div.offsetLeft,_=t.clientY-this._div.offsetTop,document.addEventListener("mousemove",v),document.addEventListener("mouseup",g)}))}divToButton(t,e){t.addEventListener("mouseover",(()=>t.style.backgroundColor="rgba(60, 60, 60, 0.6)")),t.addEventListener("mouseout",(()=>t.style.backgroundColor="transparent")),t.addEventListener("mousedown",(()=>t.style.backgroundColor="rgba(60, 60, 60)")),t.addEventListener("click",(()=>window.callbackFunction(e))),t.addEventListener("mouseup",(()=>t.style.backgroundColor="rgba(60, 60, 60, 0.6)"))}newRow(t,e=!1){let i=this.table.insertRow();i.style.cursor="default";for(let s=0;s\n \`\n `,n=`\n \n `,o=document.createElement("div");o.style.display="flex",o.style.alignItems="center";let r=document.createElement("div"),a=document.createElement("div");a.classList.add("legend-toggle-switch");let l=document.createElementNS("http://www.w3.org/2000/svg","svg");l.setAttribute("width","22"),l.setAttribute("height","16");let d=document.createElementNS("http://www.w3.org/2000/svg","g");d.innerHTML=s;let h=!0;a.addEventListener("click",(()=>{h?(h=!1,d.innerHTML=n,e.applyOptions({visible:!1})):(h=!0,e.applyOptions({visible:!0}),d.innerHTML=s)})),l.appendChild(d),a.appendChild(l),o.appendChild(r),o.appendChild(a),this.div.appendChild(o);const c=e.options().baseLineColor;this._lines.push({name:t,div:r,row:o,toggle:a,series:e,solid:c.startsWith("rgba")?c.replace(/[^,]+(?=\))/,"1"):c})}legendItemFormat(t,e){return t.toFixed(e).toString().padStart(8," ")}shorthandFormat(t){const e=Math.abs(t);return e>=1e6?(t/1e6).toFixed(1)+"M":e>=1e3?(t/1e3).toFixed(1)+"K":t.toString().padStart(8," ")}legendHandler(t,e=!1){if(!this.ohlcEnabled&&!this.linesEnabled&&!this.percentEnabled)return;const i=this.handler.series.options();if(!t.time)return this.candle.style.color="transparent",void(this.candle.innerHTML=this.candle.innerHTML.replace(i.upColor,"").replace(i.downColor,""));let s,n=null;if(e){const e=this.handler.chart.timeScale();let i=e.timeToCoordinate(t.time);i&&(n=e.coordinateToLogical(i.valueOf())),n&&(s=this.handler.series.dataByIndex(n.valueOf()))}else s=t.seriesData.get(this.handler.series);this.candle.style.color="";let o='';if(s){if(this.ohlcEnabled&&(o+=`O ${this.legendItemFormat(s.open,this.handler.precision)} `,o+=`| H ${this.legendItemFormat(s.high,this.handler.precision)} `,o+=`| L ${this.legendItemFormat(s.low,this.handler.precision)} `,o+=`| C ${this.legendItemFormat(s.close,this.handler.precision)} `),this.percentEnabled){let t=(s.close-s.open)/s.open*100,e=t>0?i.upColor:i.downColor,n=`${t>=0?"+":""}${t.toFixed(2)} %`;this.colorBasedOnCandle?o+=`| ${n}`:o+="| "+n}if(this.handler.volumeSeries){let e;e=n?this.handler.volumeSeries.dataByIndex(n):t.seriesData.get(this.handler.volumeSeries),e&&(o+=this.ohlcEnabled?`
V ${this.shorthandFormat(e.value)}`:"")}}this.candle.innerHTML=o+"
",this._lines.forEach((i=>{if(!this.linesEnabled)return void(i.row.style.display="none");let s,o;if(i.row.style.display="flex",s=e&&n?i.series.dataByIndex(n):t.seriesData.get(i.series),"Histogram"==i.series.seriesType())o=this.shorthandFormat(s.value);else{const t=i.series.options().priceFormat;o=this.legendItemFormat(s.value,t.precision)}i.div.innerHTML=` ${i.name} : ${o}`}))}}class C{_chart;_series;_finishDrawingCallback=null;_drawings=[];_activeDrawing=null;_isDrawing=!1;_drawingType=null;constructor(t,e,i=null){this._chart=t,this._series=e,this._finishDrawingCallback=i,this._chart.subscribeClick(this._clickHandler),this._chart.subscribeCrosshairMove(this._moveHandler)}_clickHandler=t=>this._onClick(t);_moveHandler=t=>this._onMouseMove(t);beginDrawing(t){this._drawingType=t,this._isDrawing=!0}stopDrawing(){this._isDrawing=!1,this._activeDrawing=null}get drawings(){return this._drawings}addNewDrawing(t){this._series.attachPrimitive(t),this._drawings.push(t)}delete(t){if(null==t)return;const e=this._drawings.indexOf(t);-1!=e&&(this._drawings.splice(e,1),t.detach())}clearDrawings(){for(const t of this._drawings)t.detach();this._drawings=[]}_onClick(t){if(!this._isDrawing)return;const e=c._eventToPoint(t,this._series);if(e)if(null==this._activeDrawing){if(null==this._drawingType)return;this._activeDrawing=new this._drawingType(e,e),this._series.attachPrimitive(this._activeDrawing)}else{if(this._drawings.push(this._activeDrawing),this.stopDrawing(),!this._finishDrawingCallback)return;this._finishDrawingCallback()}}_onMouseMove(t){if(!t)return;for(const e of this._drawings)e._handleHoverInteraction(t);if(!this._isDrawing||!this._activeDrawing)return;const e=c._eventToPoint(t,this._series);e&&this._activeDrawing.updatePoints(null,e)}}class f extends u{constructor(t,e,i,s,n){super(t,e,i,s,n)}draw(t){t.useBitmapCoordinateSpace((t=>{if(null===this._p1.x||null===this._p1.y||null===this._p2.x||null===this._p2.y)return;const e=t.context,i=this._getScaledCoordinates(t);i&&(e.lineWidth=this._options.width,e.strokeStyle=this._options.lineColor,e.beginPath(),e.moveTo(i.x1,i.y1),e.lineTo(i.x2,i.y2),e.stroke(),this._options.showCircles&&(this._drawEndCircle(t,i.x1,i.y1),this._drawEndCircle(t,i.x2,i.y2)))}))}}class D extends v{constructor(t){super(t)}renderer(){return new f(this._p1,this._p2,""+this._source._p1.price.toFixed(1),""+this._source._p2.price.toFixed(1),this._source._options)}}class E extends c{_p1;_p2;_paneViews=[];constructor(t,e,i){super(),this._p1=t,this._p2=e,this._options={...l,...i}}setFirstPoint(t){this.updatePoints(t)}setSecondPoint(t){this.updatePoints(null,t)}updatePoints(...t){this._p1=t[0]||this._p1,this._p2=t[1]||this._p2,this.requestUpdate()}}class k extends E{_type="TrendLine";constructor(t,e,i){super(t,e,i),this._paneViews=[new D(this)]}_moveToState(t){switch(t){case h.NONE:document.body.style.cursor="default",this._options.showCircles=!1,this.requestUpdate(),this._unsubscribe("mousedown",this._handleMouseDownInteraction);break;case h.HOVERING:document.body.style.cursor="pointer",this._options.showCircles=!0,this.requestUpdate(),this._subscribe("mousedown",this._handleMouseDownInteraction),this._unsubscribe("mouseup",this._handleMouseDownInteraction),this.chart.applyOptions({handleScroll:!0});break;case h.DRAGGINGP1:case h.DRAGGINGP2:case h.DRAGGING:document.body.style.cursor="grabbing",this._subscribe("mouseup",this._handleMouseUpInteraction),this.chart.applyOptions({handleScroll:!1})}this._state=t}_onDrag(t){this._state!=h.DRAGGING&&this._state!=h.DRAGGINGP1||c._addDiffToPoint(this._p1,t.time,t.logical,t.price),this._state!=h.DRAGGING&&this._state!=h.DRAGGINGP2||c._addDiffToPoint(this._p2,t.time,t.logical,t.price)}_onMouseDown(){this._startDragPoint=null;const t=this._latestHoverPoint;if(!t)return;const e=this._paneViews[0]._p1,i=this._paneViews[0]._p2;if(!(e.x&&i.x&&e.y&&i.y))return this._moveToState(h.DRAGGING);Math.abs(t.x-e.x)<10&&Math.abs(t.y-e.y)<10?this._moveToState(h.DRAGGINGP1):Math.abs(t.x-i.x)<10&&Math.abs(t.y-i.y)<10?this._moveToState(h.DRAGGINGP2):this._moveToState(h.DRAGGING)}_mouseIsOverDrawing(t,e=4){if(!t.point)return!1;const i=this._paneViews[0]._p1.x,s=this._paneViews[0]._p1.y,n=this._paneViews[0]._p2.x,o=this._paneViews[0]._p2.y;if(!(i&&n&&s&&o))return!1;const r=t.point.x,a=t.point.y;if(r<=Math.min(i,n)-e||r>=Math.max(i,n)+e)return!1;return Math.abs((o-s)*r-(n-i)*a+n*s-o*i)/Math.sqrt((o-s)**2+(n-i)**2)<=e}}class L extends u{constructor(t,e,i,s,n){super(t,e,i,s,n)}draw(t){t.useBitmapCoordinateSpace((t=>{const e=t.context,i=this._getScaledCoordinates(t);if(!i)return;e.lineWidth=this._options.width,e.strokeStyle=this._options.lineColor,e.fillStyle=this._options.fillColor;const s=Math.min(i.x1,i.x2),n=Math.min(i.y1,i.y2),o=Math.abs(i.x1-i.x2),r=Math.abs(i.y1-i.y2);e.strokeRect(s,n,o,r),e.fillRect(s,n,o,r),this._options.showCircles&&(this._drawEndCircle(t,s,n),this._drawEndCircle(t,s+o,n),this._drawEndCircle(t,s+o,n+r),this._drawEndCircle(t,s,n+r))}))}}class G extends v{constructor(t){super(t)}renderer(){return new L(this._p1,this._p2,""+this._source._p1.price.toFixed(1),""+this._source._p2.price.toFixed(1),this._source._options)}}const S={fillEnabled:!0,fillColor:"rgba(255, 255, 255, 0.2)",...l};class I extends E{_type="Box";constructor(t,e,i){super(t,e,i),this._options={...this._options,...S},this._paneViews=[new G(this)]}_moveToState(t){switch(t){case h.NONE:document.body.style.cursor="default",this.applyOptions({showCircles:!1}),this._unsubscribe("mousedown",this._handleMouseDownInteraction);break;case h.HOVERING:document.body.style.cursor="pointer",this.applyOptions({showCircles:!0}),this._unsubscribe("mouseup",this._handleMouseUpInteraction),this._subscribe("mousedown",this._handleMouseDownInteraction),this.chart.applyOptions({handleScroll:!0});break;case h.DRAGGINGP1:case h.DRAGGINGP2:case h.DRAGGINGP3:case h.DRAGGINGP4:case h.DRAGGING:document.body.style.cursor="grabbing",document.body.addEventListener("mouseup",this._handleMouseUpInteraction),this._subscribe("mouseup",this._handleMouseUpInteraction),this.chart.applyOptions({handleScroll:!1})}this._state=t}_onDrag(t){this._state!=h.DRAGGING&&this._state!=h.DRAGGINGP1||c._addDiffToPoint(this._p1,t.time,t.logical,t.price),this._state!=h.DRAGGING&&this._state!=h.DRAGGINGP2||c._addDiffToPoint(this._p2,t.time,t.logical,t.price),this._state!=h.DRAGGING&&(this._state==h.DRAGGINGP3&&(c._addDiffToPoint(this._p1,t.time,t.logical,0),c._addDiffToPoint(this._p2,0,0,t.price)),this._state==h.DRAGGINGP4&&(c._addDiffToPoint(this._p1,0,0,t.price),c._addDiffToPoint(this._p2,t.time,t.logical,0)))}_onMouseDown(){this._startDragPoint=null;const t=this._latestHoverPoint,e=this._paneViews[0]._p1,i=this._paneViews[0]._p2;if(!(e.x&&i.x&&e.y&&i.y))return this._moveToState(h.DRAGGING);const s=10;Math.abs(t.x-e.x)l-p&&rd-p&&athis._onClick(t);_onClick(t){t.target&&(this.div.contains(t.target)||(this.div.style.display="none",document.body.removeEventListener("click",this._handleClick)))}_onRightClick(t){c.hoveredObject&&(t.preventDefault(),this.div.style.left=t.clientX+"px",this.div.style.top=t.clientY+"px",this.div.style.display="block",document.body.addEventListener("click",this._handleClick))}menuItem(t,e,i=null){const s=document.createElement("span");s.classList.add("context-menu-item"),this.div.appendChild(s);const n=document.createElement("span");if(n.innerText=t,n.style.pointerEvents="none",s.appendChild(n),i){let t=document.createElement("span");t.innerText="►",t.style.fontSize="8px",t.style.pointerEvents="none",s.appendChild(t)}if(s.addEventListener("mouseover",(()=>{this.hoverItem&&this.hoverItem.closeAction&&this.hoverItem.closeAction(),this.hoverItem={elem:n,action:e,closeAction:i}})),i){let t;s.addEventListener("mouseover",(()=>t=setTimeout((()=>e(s.getBoundingClientRect())),100))),s.addEventListener("mouseout",(()=>clearTimeout(t)))}else s.addEventListener("click",(t=>{e(t),this.div.style.display="none"}))}separator(){const t=document.createElement("div");t.style.width="90%",t.style.height="1px",t.style.margin="3px 0px",t.style.backgroundColor=window.pane.borderColor,this.div.appendChild(t)}}class T{static _styles=[{name:"Solid",var:t.Solid},{name:"Dotted",var:t.Dotted},{name:"Dashed",var:t.Dashed},{name:"Large Dashed",var:t.LargeDashed},{name:"Sparse Dotted",var:t.SparseDotted}];_div;_saveDrawings;constructor(t){this._saveDrawings=t,this._div=document.createElement("div"),this._div.classList.add("context-menu"),T._styles.forEach((t=>{this._div.appendChild(this._makeTextBox(t.name,t.var))})),window.containerDiv.appendChild(this._div)}_makeTextBox(t,e){const i=document.createElement("span");return i.classList.add("context-menu-item"),i.innerText=t,i.addEventListener("click",(()=>{c.lastHoveredObject?.applyOptions({lineStyle:e}),this._saveDrawings()})),i}openMenu(t){this._div.style.top=t.top-30+"px",this._div.style.left=t.right+"px",this._div.style.display="block",setTimeout((()=>document.addEventListener("mousedown",(t=>{this._div.contains(t.target)||this.closeMenu()}))),10)}closeMenu(){document.removeEventListener("click",this.closeMenu),this._div.style.display="none"}}class N{static 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"];_div;saveDrawings;opacity=0;_opacitySlider;_opacityLabel;rgba;constructor(t){this.saveDrawings=t,this._div=document.createElement("div"),this._div.classList.add("color-picker");let e=document.createElement("div");e.style.margin="10px",e.style.display="flex",e.style.flexWrap="wrap",N.colors.forEach((t=>e.appendChild(this.makeColorBox(t))));let i=document.createElement("div");i.style.backgroundColor=window.pane.borderColor,i.style.height="1px",i.style.width="130px";let s=document.createElement("div");s.style.margin="10px";let n=document.createElement("div");n.style.color="lightgray",n.style.fontSize="12px",n.innerText="Opacity",this._opacityLabel=document.createElement("div"),this._opacityLabel.style.color="lightgray",this._opacityLabel.style.fontSize="12px",this._opacitySlider=document.createElement("input"),this._opacitySlider.type="range",this._opacitySlider.value=(100*this.opacity).toString(),this._opacityLabel.innerText=this._opacitySlider.value+"%",this._opacitySlider.oninput=()=>{this._opacityLabel.innerText=this._opacitySlider.value+"%",this.opacity=parseInt(this._opacitySlider.value)/100,this.updateColor()},s.appendChild(n),s.appendChild(this._opacitySlider),s.appendChild(this._opacityLabel),this._div.appendChild(e),this._div.appendChild(i),this._div.appendChild(s),window.containerDiv.appendChild(this._div)}_updateOpacitySlider(){this._opacitySlider.value=(100*this.opacity).toString(),this._opacityLabel.innerText=this._opacitySlider.value+"%"}makeColorBox(t){const e=document.createElement("div");e.style.width="18px",e.style.height="18px",e.style.borderRadius="3px",e.style.margin="3px",e.style.boxSizing="border-box",e.style.backgroundColor=t,e.addEventListener("mouseover",(()=>e.style.border="2px solid lightgray")),e.addEventListener("mouseout",(()=>e.style.border="none"));const i=N.extractRGBA(t);return e.addEventListener("click",(()=>{this.rgba=i,this.updateColor()})),e}static extractRGBA(t){const e=document.createElement("div");e.style.color=t,document.body.appendChild(e);const i=getComputedStyle(e).color;document.body.removeChild(e);const s=i.match(/\d+/g)?.map(Number);if(!s)return[];let n=i.includes("rgba")?parseFloat(i.split(",")[3]):1;return[s[0],s[1],s[2],n]}updateColor(){if(!c.lastHoveredObject||!this.rgba)return;const t=`rgba(${this.rgba[0]}, ${this.rgba[1]}, ${this.rgba[2]}, ${this.opacity})`;c.lastHoveredObject.applyOptions({lineColor:t}),this.saveDrawings()}openMenu(t){c.lastHoveredObject&&(this.rgba=N.extractRGBA(c.lastHoveredObject._options.lineColor),this.opacity=this.rgba[3],this._updateOpacitySlider(),this._div.style.top=t.top-30+"px",this._div.style.left=t.right+"px",this._div.style.display="flex",setTimeout((()=>document.addEventListener("mousedown",(t=>{this._div.contains(t.target)||this.closeMenu()}))),10))}closeMenu(){document.body.removeEventListener("click",this.closeMenu),this._div.style.display="none"}}class R extends w{_type="RayLine";constructor(t,e){super(t,e),this._point.time=t.time}updatePoints(...t){for(const e of t)e&&(this._point=e);this.requestUpdate()}_onDrag(t){c._addDiffToPoint(this._point,t.time,t.logical,t.price),this.requestUpdate()}_mouseIsOverDrawing(t,e=4){if(!t.point)return!1;const i=this.series.priceToCoordinate(this._point.price),s=this._point.time?this.chart.timeScale().timeToCoordinate(this._point.time):null;return!(!i||!s)&&(Math.abs(i-t.point.y)s-e)}}class P{static TREND_SVG='';static HORZ_SVG='';static RAY_SVG='';static BOX_SVG='';div;activeIcon=null;buttons=[];_commandFunctions;_handlerID;_drawingTool;constructor(t,e,i,s){this._handlerID=t,this._commandFunctions=s,this._drawingTool=new C(e,i,(()=>this.removeActiveAndSave())),this.div=this._makeToolBox(),this._makeContextMenu(),s.push((t=>{if((t.metaKey||t.ctrlKey)&&"KeyZ"===t.code){const t=this._drawingTool.drawings.pop();return t&&this._drawingTool.delete(t),!0}return!1}))}toJSON(){const{...t}=this;return t}_makeToolBox(){let t=document.createElement("div");t.classList.add("toolbox"),this.buttons.push(this._makeToolBoxElement(k,"KeyT",P.TREND_SVG)),this.buttons.push(this._makeToolBoxElement(w,"KeyH",P.HORZ_SVG)),this.buttons.push(this._makeToolBoxElement(R,"KeyR",P.RAY_SVG)),this.buttons.push(this._makeToolBoxElement(I,"KeyB",P.BOX_SVG));for(const e of this.buttons)t.appendChild(e);return t}_makeToolBoxElement(t,e,i){const s=document.createElement("div");s.classList.add("toolbox-button");const n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("width","29"),n.setAttribute("height","29");const o=document.createElementNS("http://www.w3.org/2000/svg","g");o.innerHTML=i,o.setAttribute("fill",window.pane.color),n.appendChild(o),s.appendChild(n);const r={div:s,group:o,type:t};return s.addEventListener("click",(()=>this._onIconClick(r))),this._commandFunctions.push((t=>this._handlerID===window.handlerInFocus&&(!(!t.altKey||t.code!==e)&&(t.preventDefault(),this._onIconClick(r),!0)))),s}_onIconClick(t){this.activeIcon&&(this.activeIcon.div.classList.remove("active-toolbox-button"),window.setCursor("crosshair"),this._drawingTool?.stopDrawing(),this.activeIcon===t)?this.activeIcon=null:(this.activeIcon=t,this.activeIcon.div.classList.add("active-toolbox-button"),window.setCursor("crosshair"),this._drawingTool?.beginDrawing(this.activeIcon.type))}removeActiveAndSave(){window.setCursor("default"),this.activeIcon&&this.activeIcon.div.classList.remove("active-toolbox-button"),this.activeIcon=null,this.saveDrawings()}_makeContextMenu(){const t=new M,e=new N(this.saveDrawings),i=new T(this.saveDrawings);t.menuItem("Color Picker",(t=>e.openMenu(t)),(()=>{document.removeEventListener("click",e.closeMenu),e._div.style.display="none"})),t.menuItem("Style",(t=>i.openMenu(t)),(()=>{document.removeEventListener("click",i.closeMenu),i._div.style.display="none"})),t.separator(),t.menuItem("Delete Drawing",(()=>this._drawingTool.delete(c.lastHoveredObject)))}addNewDrawing(t){this._drawingTool.addNewDrawing(t)}clearDrawings(){this._drawingTool.clearDrawings()}saveDrawings(){const t=[];for(const e of this._drawingTool.drawings)e instanceof E&&t.push({type:e._type,p1:e._p1,p2:e._p2,color:e._options.lineColor,style:e._options.lineStyle});const e=JSON.stringify(t);window.callbackFunction(`save_drawings${this._handlerID}_~_${e}`)}loadDrawings(t){t.forEach((t=>{const e={lineColor:t.color,lineStyle:t.style};switch(t.type){case"Box":this._drawingTool.addNewDrawing(new I(t.p1,t.p2,e));break;case"TrendLine":this._drawingTool.addNewDrawing(new k(t.p1,t.p2,e))}}))}}class A{_handler;_div;left;right;constructor(t){this._handler=t,this._div=document.createElement("div"),this._div.classList.add("topbar");const e=t=>{const e=document.createElement("div");return e.classList.add("topbar-container"),e.style.justifyContent=t,this._div.appendChild(e),e};this.left=e("flex-start"),this.right=e("flex-end")}makeSwitcher(t,e,i,s="left"){const n=document.createElement("div");let o;n.style.margin="4px 12px";const r={elem:n,callbackName:i,intervalElements:t.map((t=>{const i=document.createElement("button");i.classList.add("topbar-button"),i.classList.add("switcher-button"),i.style.margin="0px 2px",i.innerText=t,t==e&&(o=i,i.classList.add("active-switcher-button"));const s=A.getClientWidth(i);return i.style.minWidth=s+1+"px",i.addEventListener("click",(()=>r.onItemClicked(i))),n.appendChild(i),i})),onItemClicked:t=>{t!=o&&(o.classList.remove("active-switcher-button"),t.classList.add("active-switcher-button"),o=t,window.callbackFunction(`${r.callbackName}_~_${t.innerText}`))}};return this.appendWidget(n,s,!0),r}makeTextBoxWidget(t,e="left"){const i=document.createElement("div");return i.classList.add("topbar-textbox"),i.innerText=t,this.appendWidget(i,e,!0),i}makeMenu(t,e,i,s,n="right"){let o=document.createElement("div");o.classList.add("topbar-menu");let r=!1;t.forEach((t=>{let e=this.makeButton(t,null,!1,!1);e.elem.addEventListener("click",(()=>{a.elem.innerText=e.elem.innerText+" ↓",window.callbackFunction(`${s}_~_${e.elem.innerText}`),o.style.display="none",r=!1})),e.elem.style.margin="4px 4px",e.elem.style.padding="2px 2px",o.appendChild(e.elem)}));let a=this.makeButton(e+" ↓",null,i,!0,n);a.elem.addEventListener("click",(()=>{if(r=!r,!r)return void(o.style.display="none");let t=a.elem.getBoundingClientRect();o.style.display="flex",o.style.flexDirection="column";let e=t.x+t.width/2;o.style.left=e-o.clientWidth/2+"px",o.style.top=t.y+t.height+"px"})),document.body.appendChild(o)}makeButton(t,e,i,s=!0,n="left"){let o=document.createElement("button");o.classList.add("topbar-button"),o.innerText=t,document.body.appendChild(o),o.style.minWidth=o.clientWidth+1+"px",document.body.removeChild(o);let r={elem:o,callbackName:e};return e&&o.addEventListener("click",(()=>window.callbackFunction(`${r.callbackName}_~_${o.innerText}`))),s&&this.appendWidget(o,n,i),r}makeSeparator(t="left"){const e=document.createElement("div");e.classList.add("topbar-seperator");("left"==t?this.left:this.right).appendChild(e)}appendWidget(t,e,i){const s="left"==e?this.left:this.right;i?("left"==e&&s.appendChild(t),this.makeSeparator(e),"right"==e&&s.appendChild(t)):s.appendChild(t),this._handler.reSize()}static getClientWidth(t){document.body.appendChild(t);const e=t.clientWidth;return document.body.removeChild(t),e}}window.pane={...b},window.containerDiv=document.getElementById("container")||document.createElement("div"),window.setCursor=t=>{t&&(window.cursor=t),document.body.style.cursor=window.cursor},window.cursor="default",window.Table=y,window.HorizontalLine=w;class B{id;commandFunctions=[];wrapper;div;chart;scale;precision=2;series;volumeSeries;legend;_topBar;toolBox;spinner;_seriesList=[];constructor(t,e,i,s,n){this.reSize=this.reSize.bind(this),this.id=t,this.scale={width:e,height:i},this.wrapper=document.createElement("div"),this.wrapper.classList.add("handler"),this.wrapper.style.float=s,this.div=document.createElement("div"),this.div.style.position="relative",this.wrapper.appendChild(this.div),window.containerDiv.append(this.wrapper),this.chart=this._createChart(),this.series=this.createCandlestickSeries(),this.volumeSeries=this.createVolumeSeries(),this.legend=new x(this),document.addEventListener("keydown",(t=>{for(let e=0;ewindow.handlerInFocus=this.id)),this.reSize(),n&&window.addEventListener("resize",(()=>this.reSize()))}reSize(){let t=0!==this.scale.height&&this._topBar?._div.offsetHeight||0;this.chart.resize(window.innerWidth*this.scale.width,window.innerHeight*this.scale.height-t),this.wrapper.style.width=100*this.scale.width+"%",this.wrapper.style.height=100*this.scale.height+"%",0===this.scale.height||0===this.scale.width?(this.legend.div.style.display="none",this.toolBox&&(this.toolBox.div.style.display="none")):(this.legend.div.style.display="flex",this.toolBox&&(this.toolBox.div.style.display="flex"))}_createChart(){return s(this.div,{width:window.innerWidth*this.scale.width,height:window.innerHeight*this.scale.height,layout:{textColor:window.pane.color,background:{color:"#000000",type:n.Solid},fontSize:12},rightPriceScale:{scaleMargins:{top:.3,bottom:.25}},timeScale:{timeVisible:!0,secondsVisible:!1},crosshair:{mode:o.Normal,vertLine:{labelBackgroundColor:"rgb(46, 46, 46)"},horzLine:{labelBackgroundColor:"rgb(55, 55, 55)"}},grid:{vertLines:{color:"rgba(29, 30, 38, 5)"},horzLines:{color:"rgba(29, 30, 58, 5)"}},handleScroll:{vertTouchDrag:!0}})}createCandlestickSeries(){const t="rgba(39, 157, 130, 100)",e="rgba(200, 97, 100, 100)",i=this.chart.addCandlestickSeries({upColor:t,borderUpColor:t,wickUpColor:t,downColor:e,borderDownColor:e,wickDownColor:e});return i.priceScale().applyOptions({scaleMargins:{top:.2,bottom:.2}}),i}createVolumeSeries(){const t=this.chart.addHistogramSeries({color:"#26a69a",priceFormat:{type:"volume"},priceScaleId:"volume_scale"});return t.priceScale().applyOptions({scaleMargins:{top:.8,bottom:0}}),t}createLineSeries(t,e){const i=this.chart.addLineSeries({...e});return this._seriesList.push(i),this.legend.makeSeriesRow(t,i),{name:t,series:i}}createToolBox(){this.toolBox=new P(this.id,this.chart,this.series,this.commandFunctions),this.div.appendChild(this.toolBox.div)}createTopBar(){return this._topBar=new A(this),this.wrapper.prepend(this._topBar._div),this._topBar}toJSON(){const{chart:t,...e}=this;return e}static syncCharts(t,e,i=!1){function s(t,e){e?(t.chart.setCrosshairPosition(e.value||e.close,e.time,t.series),t.legend.legendHandler(e,!0)):t.chart.clearCrosshairPosition()}function n(t,e){return e.time&&e.seriesData.get(t)||null}const o=e=>{e&&t.chart.timeScale().setVisibleLogicalRange(e)},r=t=>{t&&e.chart.timeScale().setVisibleLogicalRange(t)},a=i=>{s(e,n(t.series,i))},l=i=>{s(t,n(e.series,i))};let d=e;function h(t,e,s,n,o,r){t.wrapper.addEventListener("mouseover",(()=>{d!==t&&(d=t,e.chart.unsubscribeCrosshairMove(s),t.chart.subscribeCrosshairMove(n),i||(e.chart.timeScale().unsubscribeVisibleLogicalRangeChange(o),t.chart.timeScale().subscribeVisibleLogicalRangeChange(r)))}))}h(e,t,a,l,r,o),h(t,e,l,a,o,r),e.chart.subscribeCrosshairMove(l),i||e.chart.timeScale().subscribeVisibleLogicalRangeChange(o)}static makeSearchBox(t){const e=document.createElement("div");e.classList.add("searchbox"),e.style.display="none";const i=document.createElement("div");i.innerHTML='';const s=document.createElement("input");return s.type="text",e.appendChild(i),e.appendChild(s),t.div.appendChild(e),t.commandFunctions.push((i=>(console.log("1"),window.handlerInFocus===t.id&&(console.log(e.style),"none"===e.style.display?(console.log("3"),!!/^[a-zA-Z0-9]$/.test(i.key)&&(console.log("4"),e.style.display="flex",s.focus(),!0)):("Enter"===i.key||"Escape"===i.key)&&("Enter"===i.key&&window.callbackFunction(`search${t.id}_~_${s.value}`),e.style.display="none",s.value="",!0))))),s.addEventListener("input",(()=>s.value=s.value.toUpperCase())),{window:e,box:s}}static makeSpinner(t){t.spinner=document.createElement("div"),t.spinner.classList.add("spinner"),t.wrapper.appendChild(t.spinner);let e=0;!function i(){t.spinner&&(e+=10,t.spinner.style.transform=`translate(-50%, -50%) rotate(${e}deg)`,requestAnimationFrame(i))}()}static _styleMap={"--bg-color":"backgroundColor","--hover-bg-color":"hoverBackgroundColor","--click-bg-color":"clickBackgroundColor","--active-bg-color":"activeBackgroundColor","--muted-bg-color":"mutedBackgroundColor","--border-color":"borderColor","--color":"color","--active-color":"activeColor"};static setRootStyles(t){const e=document.documentElement.style;for(const[i,s]of Object.entries(this._styleMap))e.setProperty(i,t[s])}}window.Handler=B;export{B as Handler}; diff --git a/src/box/box.ts b/src/box/box.ts index ec84b89..56cf5e7 100644 --- a/src/box/box.ts +++ b/src/box/box.ts @@ -22,132 +22,132 @@ const defaultBoxOptions = { export class Box extends TwoPointDrawing { - _type = "Box"; + _type = "Box"; - constructor( - p1: Point, - p2: Point, - options?: Partial - ) { - super(p1, p2, options); + constructor( + p1: Point, + p2: Point, + options?: Partial + ) { + super(p1, p2, options); this._options = { ...this._options, ...defaultBoxOptions, } - this._paneViews = [new BoxPaneView(this)]; - } + this._paneViews = [new BoxPaneView(this)]; + } - // autoscaleInfo(startTimePoint: Logical, endTimePoint: Logical): AutoscaleInfo | null { - // const p1Index = this._pointIndex(this._p1); - // const p2Index = this._pointIndex(this._p2); - // if (p1Index === null || p2Index === null) return null; - // if (endTimePoint < p1Index || startTimePoint > p2Index) return null; - // return { - // priceRange: { - // minValue: this._minPrice, - // maxValue: this._maxPrice, - // }, - // }; - // } + // autoscaleInfo(startTimePoint: Logical, endTimePoint: Logical): AutoscaleInfo | null { + // const p1Index = this._pointIndex(this._p1); + // const p2Index = this._pointIndex(this._p2); + // if (p1Index === null || p2Index === null) return null; + // if (endTimePoint < p1Index || startTimePoint > p2Index) return null; + // return { + // priceRange: { + // minValue: this._minPrice, + // maxValue: this._maxPrice, + // }, + // }; + // } - _moveToState(state: InteractionState) { - switch(state) { - case InteractionState.NONE: - document.body.style.cursor = "default"; + _moveToState(state: InteractionState) { + switch(state) { + case InteractionState.NONE: + document.body.style.cursor = "default"; this.applyOptions({showCircles: false}); this._unsubscribe("mousedown", this._handleMouseDownInteraction); - break; + break; - case InteractionState.HOVERING: - document.body.style.cursor = "pointer"; - this.applyOptions({showCircles: true}); + case InteractionState.HOVERING: + document.body.style.cursor = "pointer"; + this.applyOptions({showCircles: true}); this._unsubscribe("mouseup", this._handleMouseUpInteraction); this._subscribe("mousedown", this._handleMouseDownInteraction) - this.chart.applyOptions({handleScroll: true}); - break; + this.chart.applyOptions({handleScroll: true}); + break; - case InteractionState.DRAGGINGP1: - case InteractionState.DRAGGINGP2: - case InteractionState.DRAGGINGP3: - case InteractionState.DRAGGINGP4: - case InteractionState.DRAGGING: - document.body.style.cursor = "grabbing"; - document.body.addEventListener("mouseup", this._handleMouseUpInteraction); + case InteractionState.DRAGGINGP1: + case InteractionState.DRAGGINGP2: + case InteractionState.DRAGGINGP3: + case InteractionState.DRAGGINGP4: + case InteractionState.DRAGGING: + document.body.style.cursor = "grabbing"; + document.body.addEventListener("mouseup", this._handleMouseUpInteraction); this._subscribe("mouseup", this._handleMouseUpInteraction); - this.chart.applyOptions({handleScroll: false}); - break; - } - this._state = state; - } + this.chart.applyOptions({handleScroll: false}); + break; + } + this._state = state; + } - _onDrag(diff: any) { - if (this._state == InteractionState.DRAGGING || this._state == InteractionState.DRAGGINGP1) { - Drawing._addDiffToPoint(this._p1, diff.time, diff.logical, diff.price); - } - if (this._state == InteractionState.DRAGGING || this._state == InteractionState.DRAGGINGP2) { - Drawing._addDiffToPoint(this._p2, diff.time, diff.logical, diff.price); - } - if (this._state != InteractionState.DRAGGING) { - if (this._state == InteractionState.DRAGGINGP3) { - Drawing._addDiffToPoint(this._p1, diff.time, diff.logical, 0); - Drawing._addDiffToPoint(this._p2, 0, 0, diff.price); - } - if (this._state == InteractionState.DRAGGINGP4) { - Drawing._addDiffToPoint(this._p1, 0, 0, diff.price); - Drawing._addDiffToPoint(this._p2, diff.time, diff.logical, 0); - } - } - } + _onDrag(diff: any) { + if (this._state == InteractionState.DRAGGING || this._state == InteractionState.DRAGGINGP1) { + Drawing._addDiffToPoint(this._p1, diff.time, diff.logical, diff.price); + } + if (this._state == InteractionState.DRAGGING || this._state == InteractionState.DRAGGINGP2) { + Drawing._addDiffToPoint(this._p2, diff.time, diff.logical, diff.price); + } + if (this._state != InteractionState.DRAGGING) { + if (this._state == InteractionState.DRAGGINGP3) { + Drawing._addDiffToPoint(this._p1, diff.time, diff.logical, 0); + Drawing._addDiffToPoint(this._p2, 0, 0, diff.price); + } + if (this._state == InteractionState.DRAGGINGP4) { + Drawing._addDiffToPoint(this._p1, 0, 0, diff.price); + Drawing._addDiffToPoint(this._p2, diff.time, diff.logical, 0); + } + } + } - protected _onMouseDown() { + protected _onMouseDown() { this._startDragPoint = null; - const hoverPoint = this._latestHoverPoint; - const p1 = this._paneViews[0]._p1; - const p2 = this._paneViews[0]._p2; + const hoverPoint = this._latestHoverPoint; + const p1 = this._paneViews[0]._p1; + const p2 = this._paneViews[0]._p2; - if (!p1.x || !p2.x || !p1.y || !p2.y) return this._moveToState(InteractionState.DRAGGING); + if (!p1.x || !p2.x || !p1.y || !p2.y) return this._moveToState(InteractionState.DRAGGING); - const tolerance = 10; - if (Math.abs(hoverPoint.x-p1.x) < tolerance && Math.abs(hoverPoint.y-p1.y) < tolerance) { - this._moveToState(InteractionState.DRAGGINGP1) - } - else if (Math.abs(hoverPoint.x-p2.x) < tolerance && Math.abs(hoverPoint.y-p2.y) < tolerance) { - this._moveToState(InteractionState.DRAGGINGP2) - } - else if (Math.abs(hoverPoint.x-p1.x) < tolerance && Math.abs(hoverPoint.y-p2.y) < tolerance) { - this._moveToState(InteractionState.DRAGGINGP3) - } - else if (Math.abs(hoverPoint.x-p2.x) < tolerance && Math.abs(hoverPoint.y-p1.y) < tolerance) { - this._moveToState(InteractionState.DRAGGINGP4) - } - else { - this._moveToState(InteractionState.DRAGGING); - } - } + const tolerance = 10; + if (Math.abs(hoverPoint.x-p1.x) < tolerance && Math.abs(hoverPoint.y-p1.y) < tolerance) { + this._moveToState(InteractionState.DRAGGINGP1) + } + else if (Math.abs(hoverPoint.x-p2.x) < tolerance && Math.abs(hoverPoint.y-p2.y) < tolerance) { + this._moveToState(InteractionState.DRAGGINGP2) + } + else if (Math.abs(hoverPoint.x-p1.x) < tolerance && Math.abs(hoverPoint.y-p2.y) < tolerance) { + this._moveToState(InteractionState.DRAGGINGP3) + } + else if (Math.abs(hoverPoint.x-p2.x) < tolerance && Math.abs(hoverPoint.y-p1.y) < tolerance) { + this._moveToState(InteractionState.DRAGGINGP4) + } + else { + this._moveToState(InteractionState.DRAGGING); + } + } - protected _mouseIsOverDrawing(param: MouseEventParams, tolerance = 4) { - if (!param.point) return false; + protected _mouseIsOverDrawing(param: MouseEventParams, tolerance = 4) { + if (!param.point) return false; - const x1 = this._paneViews[0]._p1.x; - const y1 = this._paneViews[0]._p1.y; - const x2 = this._paneViews[0]._p2.x; - const y2 = this._paneViews[0]._p2.y; - if (!x1 || !x2 || !y1 || !y2 ) return false; + const x1 = this._paneViews[0]._p1.x; + const y1 = this._paneViews[0]._p1.y; + const x2 = this._paneViews[0]._p2.x; + const y2 = this._paneViews[0]._p2.y; + if (!x1 || !x2 || !y1 || !y2 ) return false; - const mouseX = param.point.x; - const mouseY = param.point.y; + const mouseX = param.point.x; + const mouseY = param.point.y; - const mainX = Math.min(x1, x2); - const mainY = Math.min(y1, y2); + const mainX = Math.min(x1, x2); + const mainY = Math.min(y1, y2); - const width = Math.abs(x1-x2); - const height = Math.abs(y1-y2); + const width = Math.abs(x1-x2); + const height = Math.abs(y1-y2); - const halfTolerance = tolerance/2; + const halfTolerance = tolerance/2; - return mouseX > mainX-halfTolerance && mouseX < mainX+width+halfTolerance && - mouseY > mainY-halfTolerance && mouseY < mainY+height+halfTolerance; - } + return mouseX > mainX-halfTolerance && mouseX < mainX+width+halfTolerance && + mouseY > mainY-halfTolerance && mouseY < mainY+height+halfTolerance; + } } diff --git a/src/box/pane-renderer.ts b/src/box/pane-renderer.ts index 5b68cfc..f43e7e65 100644 --- a/src/box/pane-renderer.ts +++ b/src/box/pane-renderer.ts @@ -6,37 +6,37 @@ import { BoxOptions } from "./box"; export class BoxPaneRenderer extends TwoPointDrawingPaneRenderer { declare _options: BoxOptions; - constructor(p1: ViewPoint, p2: ViewPoint, text1: string, text2: string, options: BoxOptions) { - super(p1, p2, text1, text2, options) - } + constructor(p1: ViewPoint, p2: ViewPoint, text1: string, text2: string, options: BoxOptions) { + super(p1, p2, text1, text2, options) + } - draw(target: CanvasRenderingTarget2D) { - target.useBitmapCoordinateSpace(scope => { - - const ctx = scope.context; + draw(target: CanvasRenderingTarget2D) { + target.useBitmapCoordinateSpace(scope => { - const scaled = this._getScaledCoordinates(scope); + const ctx = scope.context; - if (!scaled) return; - - ctx.lineWidth = this._options.width; - ctx.strokeStyle = this._options.lineColor; + const scaled = this._getScaledCoordinates(scope); + + if (!scaled) return; + + ctx.lineWidth = this._options.width; + ctx.strokeStyle = this._options.lineColor; ctx.fillStyle = this._options.fillColor; - const mainX = Math.min(scaled.x1, scaled.x2); - const mainY = Math.min(scaled.y1, scaled.y2); - const width = Math.abs(scaled.x1-scaled.x2); - const height = Math.abs(scaled.y1-scaled.y2); - - ctx.strokeRect(mainX, mainY, width, height); - ctx.fillRect(mainX, mainY, width, height); - - if (!this._options.showCircles) return; - this._drawEndCircle(scope, mainX, mainY); - this._drawEndCircle(scope, mainX+width, mainY); - this._drawEndCircle(scope, mainX+width, mainY+height); - this._drawEndCircle(scope, mainX, mainY+height); + const mainX = Math.min(scaled.x1, scaled.x2); + const mainY = Math.min(scaled.y1, scaled.y2); + const width = Math.abs(scaled.x1-scaled.x2); + const height = Math.abs(scaled.y1-scaled.y2); - }); - } + ctx.strokeRect(mainX, mainY, width, height); + ctx.fillRect(mainX, mainY, width, height); + + if (!this._options.showCircles) return; + this._drawEndCircle(scope, mainX, mainY); + this._drawEndCircle(scope, mainX+width, mainY); + this._drawEndCircle(scope, mainX+width, mainY+height); + this._drawEndCircle(scope, mainX, mainY+height); + + }); + } } \ No newline at end of file diff --git a/src/box/pane-view.ts b/src/box/pane-view.ts index 611f8bb..dab2720 100644 --- a/src/box/pane-view.ts +++ b/src/box/pane-view.ts @@ -3,17 +3,17 @@ import { BoxPaneRenderer } from './pane-renderer'; import { TwoPointDrawingPaneView } from '../drawing/pane-view'; export class BoxPaneView extends TwoPointDrawingPaneView { - constructor(source: Box) { - super(source) - } + constructor(source: Box) { + super(source) + } - renderer() { - return new BoxPaneRenderer( - this._p1, - this._p2, - '' + this._source._p1.price.toFixed(1), - '' + this._source._p2.price.toFixed(1), - this._source._options as BoxOptions, - ); - } + renderer() { + return new BoxPaneRenderer( + this._p1, + this._p2, + '' + this._source._p1.price.toFixed(1), + '' + this._source._p2.price.toFixed(1), + this._source._options as BoxOptions, + ); + } } \ No newline at end of file diff --git a/src/drawing/data-source.ts b/src/drawing/data-source.ts index 527dd43..b6bdac9 100644 --- a/src/drawing/data-source.ts +++ b/src/drawing/data-source.ts @@ -1,23 +1,23 @@ import { - IChartApi, - ISeriesApi, - Logical, - SeriesOptionsMap, - Time, + IChartApi, + ISeriesApi, + Logical, + SeriesOptionsMap, + Time, } from 'lightweight-charts'; import { DrawingOptions } from './options'; export interface Point { - time: Time | null; - logical: Logical; - price: number; + time: Time | null; + logical: Logical; + price: number; } export interface DrawingDataSource { - chart: IChartApi; - series: ISeriesApi; - options: DrawingOptions; - p1: Point; - p2: Point; + chart: IChartApi; + series: ISeriesApi; + options: DrawingOptions; + p1: Point; + p2: Point; } diff --git a/src/drawing/drawing-tool.ts b/src/drawing/drawing-tool.ts index 70f0df8..2b9b19e 100644 --- a/src/drawing/drawing-tool.ts +++ b/src/drawing/drawing-tool.ts @@ -1,97 +1,97 @@ import { - IChartApi, - ISeriesApi, - MouseEventParams, - SeriesType, + IChartApi, + ISeriesApi, + MouseEventParams, + SeriesType, } from 'lightweight-charts'; import { Drawing } from './drawing'; export class DrawingTool { - private _chart: IChartApi; - private _series: ISeriesApi; - private _finishDrawingCallback: Function | null = null; + private _chart: IChartApi; + private _series: ISeriesApi; + private _finishDrawingCallback: Function | null = null; - private _drawings: Drawing[] = []; - private _activeDrawing: Drawing | null = null; - private _isDrawing: boolean = false; - private _drawingType: (new (...args: any[]) => Drawing) | null = null; + private _drawings: Drawing[] = []; + private _activeDrawing: Drawing | null = null; + private _isDrawing: boolean = false; + private _drawingType: (new (...args: any[]) => Drawing) | null = null; - constructor(chart: IChartApi, series: ISeriesApi, finishDrawingCallback: Function | null = null) { - this._chart = chart; - this._series = series; - this._finishDrawingCallback = finishDrawingCallback; + constructor(chart: IChartApi, series: ISeriesApi, finishDrawingCallback: Function | null = null) { + this._chart = chart; + this._series = series; + this._finishDrawingCallback = finishDrawingCallback; - this._chart.subscribeClick(this._clickHandler); - this._chart.subscribeCrosshairMove(this._moveHandler); - } + this._chart.subscribeClick(this._clickHandler); + this._chart.subscribeCrosshairMove(this._moveHandler); + } - private _clickHandler = (param: MouseEventParams) => this._onClick(param); - private _moveHandler = (param: MouseEventParams) => this._onMouseMove(param); + private _clickHandler = (param: MouseEventParams) => this._onClick(param); + private _moveHandler = (param: MouseEventParams) => this._onMouseMove(param); - beginDrawing(DrawingType: new (...args: any[]) => Drawing) { - this._drawingType = DrawingType; - this._isDrawing = true; - } + beginDrawing(DrawingType: new (...args: any[]) => Drawing) { + this._drawingType = DrawingType; + this._isDrawing = true; + } - stopDrawing() { - this._isDrawing = false; - this._activeDrawing = null; - } + stopDrawing() { + this._isDrawing = false; + this._activeDrawing = null; + } - get drawings() { - return this._drawings; - } + get drawings() { + return this._drawings; + } - addNewDrawing(drawing: Drawing) { - this._series.attachPrimitive(drawing); - this._drawings.push(drawing); - } + addNewDrawing(drawing: Drawing) { + this._series.attachPrimitive(drawing); + this._drawings.push(drawing); + } - delete(d: Drawing | null) { - if (d == null) return; - const idx = this._drawings.indexOf(d); - if (idx == -1) return; - this._drawings.splice(idx, 1) - d.detach(); - } + delete(d: Drawing | null) { + if (d == null) return; + const idx = this._drawings.indexOf(d); + if (idx == -1) return; + this._drawings.splice(idx, 1) + d.detach(); + } - clearDrawings() { - for (const d of this._drawings) d.detach(); - this._drawings = []; - } + clearDrawings() { + for (const d of this._drawings) d.detach(); + this._drawings = []; + } - private _onClick(param: MouseEventParams) { - if (!this._isDrawing) return; + private _onClick(param: MouseEventParams) { + if (!this._isDrawing) return; - const point = Drawing._eventToPoint(param, this._series); - if (!point) return; + const point = Drawing._eventToPoint(param, this._series); + if (!point) return; - if (this._activeDrawing == null) { - if (this._drawingType == null) return; + if (this._activeDrawing == null) { + if (this._drawingType == null) return; - this._activeDrawing = new this._drawingType(point, point); - this._series.attachPrimitive(this._activeDrawing); - } - else { - this._drawings.push(this._activeDrawing); - this.stopDrawing(); + this._activeDrawing = new this._drawingType(point, point); + this._series.attachPrimitive(this._activeDrawing); + } + else { + this._drawings.push(this._activeDrawing); + this.stopDrawing(); - if (!this._finishDrawingCallback) return; - this._finishDrawingCallback(); - } - } + if (!this._finishDrawingCallback) return; + this._finishDrawingCallback(); + } + } - private _onMouseMove(param: MouseEventParams) { - if (!param) return; + private _onMouseMove(param: MouseEventParams) { + if (!param) return; - for (const t of this._drawings) t._handleHoverInteraction(param); + for (const t of this._drawings) t._handleHoverInteraction(param); - if (!this._isDrawing || !this._activeDrawing) return; + if (!this._isDrawing || !this._activeDrawing) return; - const point = Drawing._eventToPoint(param, this._series); - if (!point) return; - this._activeDrawing.updatePoints(null, point); - // this._activeDrawing.setSecondPoint(point); - } + const point = Drawing._eventToPoint(param, this._series); + if (!point) return; + this._activeDrawing.updatePoints(null, point); + // this._activeDrawing.setSecondPoint(point); + } } \ No newline at end of file diff --git a/src/drawing/drawing.ts b/src/drawing/drawing.ts index 1c73026..59820e8 100644 --- a/src/drawing/drawing.ts +++ b/src/drawing/drawing.ts @@ -7,176 +7,179 @@ import { DrawingOptions, defaultOptions } from './options'; import { convertTime } from '../helpers/time'; export enum InteractionState { - NONE, - HOVERING, - DRAGGING, - DRAGGINGP1, - DRAGGINGP2, - DRAGGINGP3, - DRAGGINGP4, + NONE, + HOVERING, + DRAGGING, + DRAGGINGP1, + DRAGGINGP2, + DRAGGINGP3, + DRAGGINGP4, } interface DiffPoint { - time: number | null; - logical: number; - price: number; + time: number | null; + logical: number; + price: number; } export abstract class Drawing extends PluginBase { - _paneViews: DrawingPaneView[] = []; - _options: DrawingOptions; + _paneViews: DrawingPaneView[] = []; + _options: DrawingOptions; - abstract _type: string; + abstract _type: string; - protected _state: InteractionState = InteractionState.NONE; + protected _state: InteractionState = InteractionState.NONE; - protected _startDragPoint: Point | null = null; - protected _latestHoverPoint: any | null = null; + protected _startDragPoint: Point | null = null; + protected _latestHoverPoint: any | null = null; - protected static _mouseIsDown: boolean = false; + protected static _mouseIsDown: boolean = false; - public static hoveredObject: Drawing | null = null; - public static lastHoveredObject: Drawing | null = null; + public static hoveredObject: Drawing | null = null; + public static lastHoveredObject: Drawing | null = null; - protected _listeners: any[] = []; + protected _listeners: any[] = []; - constructor( - options?: Partial - ) { - super() - this._options = { - ...defaultOptions, - ...options, - }; - } + constructor( + options?: Partial + ) { + super() + this._options = { + ...defaultOptions, + ...options, + }; + } - updateAllViews() { - this._paneViews.forEach(pw => pw.update()); - } + updateAllViews() { + this._paneViews.forEach(pw => pw.update()); + } - paneViews() { - return this._paneViews; - } + paneViews() { + return this._paneViews; + } - applyOptions(options: Partial) { - this._options = { - ...this._options, - ...options, - } - this.requestUpdate(); - } + applyOptions(options: Partial) { + this._options = { + ...this._options, + ...options, + } + this.requestUpdate(); + } - public abstract updatePoints(...points: (Point | null)[]): void; + public abstract updatePoints(...points: (Point | null)[]): void; - detach() { - this.series.detachPrimitive(this); - for (const s of this._listeners) { - document.body.removeEventListener(s.name, s.listener) - } - } + detach() { + this._options.lineColor = 'transparent'; + this.requestUpdate(); + this.series.detachPrimitive(this); + for (const s of this._listeners) { + document.body.removeEventListener(s.name, s.listener); + } - protected _subscribe(name: keyof DocumentEventMap, listener: any) { - document.body.addEventListener(name, listener); - this._listeners.push({name: name, listener: listener}); - } + } - protected _unsubscribe(name: keyof DocumentEventMap, callback: any) { - document.body.removeEventListener(name, callback); + protected _subscribe(name: keyof DocumentEventMap, listener: any) { + document.body.addEventListener(name, listener); + this._listeners.push({name: name, listener: listener}); + } - const toRemove = this._listeners.find((x) => x.name === name && x.listener === callback) - this._listeners.splice(this._listeners.indexOf(toRemove), 1); - } + protected _unsubscribe(name: keyof DocumentEventMap, callback: any) { + document.body.removeEventListener(name, callback); - _handleHoverInteraction(param: MouseEventParams) { - this._latestHoverPoint = param.point; - if (!Drawing._mouseIsDown) { - if (this._mouseIsOverDrawing(param)) { - if (this._state != InteractionState.NONE) return; - this._moveToState(InteractionState.HOVERING); - Drawing.hoveredObject = Drawing.lastHoveredObject = this; - } - else { - if (this._state == InteractionState.NONE) return; - this._moveToState(InteractionState.NONE); - if (Drawing.hoveredObject === this) Drawing.hoveredObject = null; - } - return; - } - this._handleDragInteraction(param); - } + const toRemove = this._listeners.find((x) => x.name === name && x.listener === callback) + this._listeners.splice(this._listeners.indexOf(toRemove), 1); + } - public static _eventToPoint(param: MouseEventParams, series: ISeriesApi) { - if (!series || !param.point || !param.logical) return null; - const barPrice = series.coordinateToPrice(param.point.y); - if (barPrice == null) return null; - return { - time: param.time || null, - logical: param.logical, - price: barPrice.valueOf(), - } - } + _handleHoverInteraction(param: MouseEventParams) { + this._latestHoverPoint = param.point; + if (!Drawing._mouseIsDown) { + if (this._mouseIsOverDrawing(param)) { + if (this._state != InteractionState.NONE) return; + this._moveToState(InteractionState.HOVERING); + Drawing.hoveredObject = Drawing.lastHoveredObject = this; + } + else { + if (this._state == InteractionState.NONE) return; + this._moveToState(InteractionState.NONE); + if (Drawing.hoveredObject === this) Drawing.hoveredObject = null; + } + return; + } + this._handleDragInteraction(param); + } - protected static _getDiff(p1: Point, p2: Point): DiffPoint { - const diff: DiffPoint = { - time: null, - logical: p1.logical-p2.logical, - price: p1.price-p2.price, - } - if (p1.time && p2.time) { - diff.time = convertTime(p1.time)-convertTime(p2.time); - } - return diff; - } + public static _eventToPoint(param: MouseEventParams, series: ISeriesApi) { + if (!series || !param.point || !param.logical) return null; + const barPrice = series.coordinateToPrice(param.point.y); + if (barPrice == null) return null; + return { + time: param.time || null, + logical: param.logical, + price: barPrice.valueOf(), + } + } - protected static _addDiffToPoint(point: Point, timeDiff: number | null, logicalDiff: number, priceDiff: number) { - if (timeDiff != null && point.time != null) { - point.time = (convertTime(point.time)+timeDiff)/1000 as Time; - } - else { - point.time = null; - } - point.logical = point.logical + logicalDiff as Logical; - point.price = point.price+priceDiff; - } + protected static _getDiff(p1: Point, p2: Point): DiffPoint { + const diff: DiffPoint = { + time: null, + logical: p1.logical-p2.logical, + price: p1.price-p2.price, + } + if (p1.time && p2.time) { + diff.time = convertTime(p1.time)-convertTime(p2.time); + } + return diff; + } - protected _handleMouseDownInteraction = () => { - if (Drawing._mouseIsDown) return; - Drawing._mouseIsDown = true; - this._onMouseDown(); - } + protected static _addDiffToPoint(point: Point, timeDiff: number | null, logicalDiff: number, priceDiff: number) { + if (timeDiff != null && point.time != null) { + point.time = (convertTime(point.time)+timeDiff)/1000 as Time; + } + else { + point.time = null; + } + point.logical = point.logical + logicalDiff as Logical; + point.price = point.price+priceDiff; + } - protected _handleMouseUpInteraction = () => { - if (!Drawing._mouseIsDown) return; - Drawing._mouseIsDown = false; - this._moveToState(InteractionState.HOVERING); - } + protected _handleMouseDownInteraction = () => { + if (Drawing._mouseIsDown) return; + Drawing._mouseIsDown = true; + this._onMouseDown(); + } - private _handleDragInteraction(param: MouseEventParams): void { - if (this._state != InteractionState.DRAGGING && - this._state != InteractionState.DRAGGINGP1 && - this._state != InteractionState.DRAGGINGP2 && - this._state != InteractionState.DRAGGINGP3 && - this._state != InteractionState.DRAGGINGP4) { - return; - } - const mousePoint = Drawing._eventToPoint(param, this.series); - if (!mousePoint) return; - this._startDragPoint = this._startDragPoint || mousePoint; + protected _handleMouseUpInteraction = () => { + if (!Drawing._mouseIsDown) return; + Drawing._mouseIsDown = false; + this._moveToState(InteractionState.HOVERING); + } - const diff = Drawing._getDiff(mousePoint, this._startDragPoint); - this._onDrag(diff); - this.requestUpdate(); + private _handleDragInteraction(param: MouseEventParams): void { + if (this._state != InteractionState.DRAGGING && + this._state != InteractionState.DRAGGINGP1 && + this._state != InteractionState.DRAGGINGP2 && + this._state != InteractionState.DRAGGINGP3 && + this._state != InteractionState.DRAGGINGP4) { + return; + } + const mousePoint = Drawing._eventToPoint(param, this.series); + if (!mousePoint) return; + this._startDragPoint = this._startDragPoint || mousePoint; - this._startDragPoint = mousePoint; - } + const diff = Drawing._getDiff(mousePoint, this._startDragPoint); + this._onDrag(diff); + this.requestUpdate(); - protected abstract _onMouseDown(): void; - protected abstract _onDrag(diff: any): void; // TODO any? - protected abstract _moveToState(state: InteractionState): void; - protected abstract _mouseIsOverDrawing(param: MouseEventParams): boolean; + this._startDragPoint = mousePoint; + } - // toJSON() { - // const {series, chart, ...serialized} = this; - // return serialized; - // } + protected abstract _onMouseDown(): void; + protected abstract _onDrag(diff: any): void; // TODO any? + protected abstract _moveToState(state: InteractionState): void; + protected abstract _mouseIsOverDrawing(param: MouseEventParams): boolean; + + // toJSON() { + // const {series, chart, ...serialized} = this; + // return serialized; + // } } diff --git a/src/drawing/options.ts b/src/drawing/options.ts index 2631240..d2e8bab 100644 --- a/src/drawing/options.ts +++ b/src/drawing/options.ts @@ -1,18 +1,18 @@ import { LineStyle } from "lightweight-charts"; export interface DrawingOptions { - lineColor: string; - lineStyle: LineStyle - width: number; - showLabels: boolean; - showCircles: boolean, + lineColor: string; + lineStyle: LineStyle + width: number; + showLabels: boolean; + showCircles: boolean, } export const defaultOptions: DrawingOptions = { - lineColor: 'rgb(255, 255, 255)', - lineStyle: LineStyle.Solid, - width: 4, - showLabels: true, - showCircles: false, + lineColor: 'rgb(255, 255, 255)', + lineStyle: LineStyle.Solid, + width: 4, + showLabels: true, + showCircles: false, }; \ No newline at end of file diff --git a/src/drawing/pane-renderer.ts b/src/drawing/pane-renderer.ts index 6d3a2b8..5183e49 100644 --- a/src/drawing/pane-renderer.ts +++ b/src/drawing/pane-renderer.ts @@ -4,64 +4,64 @@ import { DrawingOptions } from "./options"; import { BitmapCoordinatesRenderingScope, CanvasRenderingTarget2D } from "fancy-canvas"; export abstract class DrawingPaneRenderer implements ISeriesPrimitivePaneRenderer { - _options: DrawingOptions; + _options: DrawingOptions; - constructor(options: DrawingOptions) { - this._options = options; - } + constructor(options: DrawingOptions) { + this._options = options; + } - abstract draw(target: CanvasRenderingTarget2D): void; + abstract draw(target: CanvasRenderingTarget2D): void; } export abstract class TwoPointDrawingPaneRenderer extends DrawingPaneRenderer { - _p1: ViewPoint; - _p2: ViewPoint; - _text1: string; - _text2: string; + _p1: ViewPoint; + _p2: ViewPoint; + _text1: string; + _text2: string; - constructor(p1: ViewPoint, p2: ViewPoint, text1: string, text2: string, options: DrawingOptions) { - super(options); - this._p1 = p1; - this._p2 = p2; - this._text1 = text1; - this._text2 = text2; - } + constructor(p1: ViewPoint, p2: ViewPoint, text1: string, text2: string, options: DrawingOptions) { + super(options); + this._p1 = p1; + this._p2 = p2; + this._text1 = text1; + this._text2 = text2; + } - abstract draw(target: CanvasRenderingTarget2D): void; + abstract draw(target: CanvasRenderingTarget2D): void; - _getScaledCoordinates(scope: BitmapCoordinatesRenderingScope) { - if (this._p1.x === null || this._p1.y === null || - this._p2.x === null || this._p2.y === null) return null; - return { - x1: Math.round(this._p1.x * scope.horizontalPixelRatio), - y1: Math.round(this._p1.y * scope.verticalPixelRatio), - x2: Math.round(this._p2.x * scope.horizontalPixelRatio), - y2: Math.round(this._p2.y * scope.verticalPixelRatio), - } - } + _getScaledCoordinates(scope: BitmapCoordinatesRenderingScope) { + if (this._p1.x === null || this._p1.y === null || + this._p2.x === null || this._p2.y === null) return null; + return { + x1: Math.round(this._p1.x * scope.horizontalPixelRatio), + y1: Math.round(this._p1.y * scope.verticalPixelRatio), + x2: Math.round(this._p2.x * scope.horizontalPixelRatio), + y2: Math.round(this._p2.y * scope.verticalPixelRatio), + } + } - // _drawTextLabel(scope: BitmapCoordinatesRenderingScope, text: string, x: number, y: number, left: boolean) { - // scope.context.font = '24px Arial'; - // scope.context.beginPath(); - // const offset = 5 * scope.horizontalPixelRatio; - // const textWidth = scope.context.measureText(text); - // const leftAdjustment = left ? textWidth.width + offset * 4 : 0; - // scope.context.fillStyle = this._options.labelBackgroundColor; - // scope.context.roundRect(x + offset - leftAdjustment, y - 24, textWidth.width + offset * 2, 24 + offset, 5); - // scope.context.fill(); - // scope.context.beginPath(); - // scope.context.fillStyle = this._options.labelTextColor; - // scope.context.fillText(text, x + offset * 2 - leftAdjustment, y); - // } + // _drawTextLabel(scope: BitmapCoordinatesRenderingScope, text: string, x: number, y: number, left: boolean) { + // scope.context.font = '24px Arial'; + // scope.context.beginPath(); + // const offset = 5 * scope.horizontalPixelRatio; + // const textWidth = scope.context.measureText(text); + // const leftAdjustment = left ? textWidth.width + offset * 4 : 0; + // scope.context.fillStyle = this._options.labelBackgroundColor; + // scope.context.roundRect(x + offset - leftAdjustment, y - 24, textWidth.width + offset * 2, 24 + offset, 5); + // scope.context.fill(); + // scope.context.beginPath(); + // scope.context.fillStyle = this._options.labelTextColor; + // scope.context.fillText(text, x + offset * 2 - leftAdjustment, y); + // } - _drawEndCircle(scope: BitmapCoordinatesRenderingScope, x: number, y: number) { - const radius = 9 - scope.context.fillStyle = '#000'; - scope.context.beginPath(); - scope.context.arc(x, y, radius, 0, 2 * Math.PI); - scope.context.stroke(); - scope.context.fill(); - // scope.context.strokeStyle = this._options.lineColor; - } + _drawEndCircle(scope: BitmapCoordinatesRenderingScope, x: number, y: number) { + const radius = 9 + scope.context.fillStyle = '#000'; + scope.context.beginPath(); + scope.context.arc(x, y, radius, 0, 2 * Math.PI); + scope.context.stroke(); + scope.context.fill(); + // scope.context.strokeStyle = this._options.lineColor; + } } \ No newline at end of file diff --git a/src/drawing/pane-view.ts b/src/drawing/pane-view.ts index cd7b9b0..8f2a772 100644 --- a/src/drawing/pane-view.ts +++ b/src/drawing/pane-view.ts @@ -17,39 +17,39 @@ export abstract class DrawingPaneView implements ISeriesPrimitivePaneView { } export interface ViewPoint { - x: Coordinate | null; - y: Coordinate | null; + x: Coordinate | null; + y: Coordinate | null; } export abstract class TwoPointDrawingPaneView extends DrawingPaneView { - _p1: ViewPoint = { x: null, y: null }; - _p2: ViewPoint = { x: null, y: null }; + _p1: ViewPoint = { x: null, y: null }; + _p2: ViewPoint = { x: null, y: null }; - _source: TwoPointDrawing; + _source: TwoPointDrawing; - constructor(source: TwoPointDrawing) { - super(source); - this._source = source; - } + constructor(source: TwoPointDrawing) { + super(source); + this._source = source; + } - update() { - const series = this._source.series; - const y1 = series.priceToCoordinate(this._source._p1.price); - const y2 = series.priceToCoordinate(this._source._p2.price); - const x1 = this._getX(this._source._p1); - const x2 = this._getX(this._source._p2); - this._p1 = { x: x1, y: y1 }; - this._p2 = { x: x2, y: y2 }; - if (!x1 || !x2 || !y1 || !y2) return; - } + update() { + const series = this._source.series; + const y1 = series.priceToCoordinate(this._source._p1.price); + const y2 = series.priceToCoordinate(this._source._p2.price); + const x1 = this._getX(this._source._p1); + const x2 = this._getX(this._source._p2); + this._p1 = { x: x1, y: y1 }; + this._p2 = { x: x2, y: y2 }; + if (!x1 || !x2 || !y1 || !y2) return; + } - abstract renderer(): DrawingPaneRenderer; + abstract renderer(): DrawingPaneRenderer; - _getX(p: Point) { - const timeScale = this._source.chart.timeScale(); - if (!p.time) { - return timeScale.logicalToCoordinate(p.logical); - } - return timeScale.timeToCoordinate(p.time); - } + _getX(p: Point) { + const timeScale = this._source.chart.timeScale(); + if (!p.time) { + return timeScale.logicalToCoordinate(p.logical); + } + return timeScale.timeToCoordinate(p.time); + } } diff --git a/src/drawing/two-point-drawing.ts b/src/drawing/two-point-drawing.ts index 523af85..13db602 100644 --- a/src/drawing/two-point-drawing.ts +++ b/src/drawing/two-point-drawing.ts @@ -5,35 +5,35 @@ import { TwoPointDrawingPaneView } from './pane-view'; export abstract class TwoPointDrawing extends Drawing { - _p1: Point; - _p2: Point; - _paneViews: TwoPointDrawingPaneView[] = []; + _p1: Point; + _p2: Point; + _paneViews: TwoPointDrawingPaneView[] = []; - constructor( - p1: Point, - p2: Point, - options?: Partial - ) { - super() - this._p1 = p1; - this._p2 = p2; - this._options = { - ...defaultOptions, - ...options, - }; - } + constructor( + p1: Point, + p2: Point, + options?: Partial + ) { + super() + this._p1 = p1; + this._p2 = p2; + this._options = { + ...defaultOptions, + ...options, + }; + } - setFirstPoint(point: Point) { - this.updatePoints(point); - } + setFirstPoint(point: Point) { + this.updatePoints(point); + } - setSecondPoint(point: Point) { - this.updatePoints(null, point); - } + setSecondPoint(point: Point) { + this.updatePoints(null, point); + } - public updatePoints(...points: (Point|null)[]) { - this._p1 = points[0] || this._p1; - this._p2 = points[1] || this._p2; - this.requestUpdate(); - } + public updatePoints(...points: (Point|null)[]) { + this._p1 = points[0] || this._p1; + this._p2 = points[1] || this._p2; + this.requestUpdate(); + } } diff --git a/src/general/global-params.ts b/src/general/global-params.ts index afcd547..256162e 100644 --- a/src/general/global-params.ts +++ b/src/general/global-params.ts @@ -1,3 +1,4 @@ +import { HorizontalLine } from "../horizontal-line/horizontal-line"; import { Table } from "./table"; export interface GlobalParams extends Window { @@ -9,6 +10,7 @@ export interface GlobalParams extends Window { cursor: string; Handler: any; Table: typeof Table; + HorizontalLine: typeof HorizontalLine; } interface paneStyle { @@ -46,6 +48,7 @@ export function globalParamInit() { } window.cursor = 'default'; window.Table = Table; + window.HorizontalLine = HorizontalLine; } diff --git a/src/general/handler.ts b/src/general/handler.ts index 8beabbd..78d4b8e 100644 --- a/src/general/handler.ts +++ b/src/general/handler.ts @@ -2,6 +2,7 @@ import { ColorType, CrosshairMode, DeepPartial, + HistogramStyleOptions, IChartApi, ISeriesApi, LineStyleOptions, @@ -9,14 +10,12 @@ import { LogicalRangeChangeEventHandler, MouseEventHandler, MouseEventParams, - SeriesMarker, SeriesOptionsCommon, SeriesType, Time, createChart } from "lightweight-charts"; -import { HorizontalLine } from "../horizontal-line/horizontal-line"; import { GlobalParams, globalParamInit } from "./global-params"; import { Legend } from "./legend"; import { ToolBox } from "./toolbox"; @@ -41,8 +40,6 @@ export class Handler { public chart: IChartApi; public scale: Scale; - public horizontal_lines: HorizontalLine[] = []; - public markers: SeriesMarker<"">[] = []; public precision: number = 2; public series: ISeriesApi; @@ -181,8 +178,16 @@ export class Handler { return { name: name, series: line, - horizontal_lines: [], - markers: [], + } + } + + createHistogramSeries(name: string, options: DeepPartial) { + const line = this.chart.addHistogramSeries({...options}); + this._seriesList.push(line); + this.legend.makeSeriesRow(name, line) + return { + name: name, + series: line, } } @@ -215,8 +220,8 @@ export class Handler { } function getPoint(series: ISeriesApi, param: MouseEventParams) { - if (!param.time) return null; - return param.seriesData.get(series) || null; + if (!param.time) return null; + return param.seriesData.get(series) || null; } const setChildRange = (timeRange: LogicalRange | null) => { if(timeRange) childChart.chart.timeScale().setVisibleLogicalRange(timeRange); } diff --git a/src/general/toolbox.ts b/src/general/toolbox.ts index 6a522ab..f577f07 100644 --- a/src/general/toolbox.ts +++ b/src/general/toolbox.ts @@ -160,6 +160,10 @@ export class ToolBox { // }) // } + addNewDrawing(d: Drawing) { + this._drawingTool.addNewDrawing(d); + } + clearDrawings() { this._drawingTool.clearDrawings(); } diff --git a/src/horizontal-line/horizontal-line.ts b/src/horizontal-line/horizontal-line.ts index 141bad9..bb2b84e 100644 --- a/src/horizontal-line/horizontal-line.ts +++ b/src/horizontal-line/horizontal-line.ts @@ -6,22 +6,26 @@ import { Point } from "../drawing/data-source"; import { Drawing, InteractionState } from "../drawing/drawing"; import { DrawingOptions } from "../drawing/options"; import { HorizontalLinePaneView } from "./pane-view"; +import { GlobalParams } from "../general/global-params"; + + +declare const window: GlobalParams; export class HorizontalLine extends Drawing { _type = 'HorizontalLine'; _paneViews: HorizontalLinePaneView[]; _point: Point; + private _callbackName: string | null; protected _startDragPoint: Point | null = null; - constructor(point: Point, options: DeepPartial) { + constructor(point: Point, options: DeepPartial, callbackName=null) { super(options) this._point = point; this._point.time = null; // time is null for horizontal lines this._paneViews = [new HorizontalLinePaneView(this)]; - // TODO ids should be stored in an object dictionary so u can access the lines - // this.handler.horizontal_lines.push(this) TODO fix this in handler ? + this._callbackName = callbackName; } public updatePoints(...points: (Point | null)[]) { @@ -38,15 +42,14 @@ export class HorizontalLine extends Drawing { case InteractionState.HOVERING: document.body.style.cursor = "pointer"; - this._unsubscribe("mouseup", this._handleMouseUpInteraction); + this._unsubscribe("mouseup", this._childHandleMouseUpInteraction); this._subscribe("mousedown", this._handleMouseDownInteraction) this.chart.applyOptions({handleScroll: true}); break; case InteractionState.DRAGGING: document.body.style.cursor = "grabbing"; - document.body.addEventListener("mouseup", this._handleMouseUpInteraction); - this._subscribe("mouseup", this._handleMouseUpInteraction); + this._subscribe("mouseup", this._childHandleMouseUpInteraction); this.chart.applyOptions({handleScroll: false}); break; } @@ -71,4 +74,11 @@ export class HorizontalLine extends Drawing { if (!hoverPoint) return; return this._moveToState(InteractionState.DRAGGING); } + + protected _childHandleMouseUpInteraction = () => { + this._handleMouseUpInteraction(); + if (!this._callbackName) return; + console.log(window.callbackFunction); + window.callbackFunction(`${this._callbackName}_~_${this._point.price.toFixed(8)}`); + } } \ No newline at end of file diff --git a/src/trend-line/pane-renderer.ts b/src/trend-line/pane-renderer.ts index 2999322..e64cfc3 100644 --- a/src/trend-line/pane-renderer.ts +++ b/src/trend-line/pane-renderer.ts @@ -5,36 +5,36 @@ import { TwoPointDrawingPaneRenderer } from "../drawing/pane-renderer"; import { DrawingOptions } from "../drawing/options"; export class TrendLinePaneRenderer extends TwoPointDrawingPaneRenderer { - constructor(p1: ViewPoint, p2: ViewPoint, text1: string, text2: string, options: DrawingOptions) { - super(p1, p2, text1, text2, options); - } + constructor(p1: ViewPoint, p2: ViewPoint, text1: string, text2: string, options: DrawingOptions) { + super(p1, p2, text1, text2, options); + } - draw(target: CanvasRenderingTarget2D) { - target.useBitmapCoordinateSpace(scope => { - if ( - this._p1.x === null || - this._p1.y === null || - this._p2.x === null || - this._p2.y === null - ) - return; - const ctx = scope.context; + draw(target: CanvasRenderingTarget2D) { + target.useBitmapCoordinateSpace(scope => { + if ( + this._p1.x === null || + this._p1.y === null || + this._p2.x === null || + this._p2.y === null + ) + return; + const ctx = scope.context; - const scaled = this._getScaledCoordinates(scope); - if (!scaled) return; + const scaled = this._getScaledCoordinates(scope); + if (!scaled) return; - ctx.lineWidth = this._options.width; - ctx.strokeStyle = this._options.lineColor; - ctx.beginPath(); - ctx.moveTo(scaled.x1, scaled.y1); - ctx.lineTo(scaled.x2, scaled.y2); - ctx.stroke(); - // this._drawTextLabel(scope, this._text1, x1Scaled, y1Scaled, true); - // this._drawTextLabel(scope, this._text2, x2Scaled, y2Scaled, false); + ctx.lineWidth = this._options.width; + ctx.strokeStyle = this._options.lineColor; + ctx.beginPath(); + ctx.moveTo(scaled.x1, scaled.y1); + ctx.lineTo(scaled.x2, scaled.y2); + ctx.stroke(); + // this._drawTextLabel(scope, this._text1, x1Scaled, y1Scaled, true); + // this._drawTextLabel(scope, this._text2, x2Scaled, y2Scaled, false); - if (!this._options.showCircles) return; - this._drawEndCircle(scope, scaled.x1, scaled.y1); - this._drawEndCircle(scope, scaled.x2, scaled.y2); - }); - } + if (!this._options.showCircles) return; + this._drawEndCircle(scope, scaled.x1, scaled.y1); + this._drawEndCircle(scope, scaled.x2, scaled.y2); + }); + } } \ No newline at end of file diff --git a/src/trend-line/pane-view.ts b/src/trend-line/pane-view.ts index 9d07c74..68702c2 100644 --- a/src/trend-line/pane-view.ts +++ b/src/trend-line/pane-view.ts @@ -4,22 +4,22 @@ import { TrendLinePaneRenderer } from './pane-renderer'; import { TwoPointDrawingPaneView } from '../drawing/pane-view'; export interface ViewPoint { - x: Coordinate | null; - y: Coordinate | null; + x: Coordinate | null; + y: Coordinate | null; } export class TrendLinePaneView extends TwoPointDrawingPaneView { - constructor(source: TrendLine) { - super(source) - } + constructor(source: TrendLine) { + super(source) + } - renderer() { - return new TrendLinePaneRenderer( - this._p1, - this._p2, - '' + this._source._p1.price.toFixed(1), - '' + this._source._p2.price.toFixed(1), - this._source._options - ); - } + renderer() { + return new TrendLinePaneRenderer( + this._p1, + this._p2, + '' + this._source._p1.price.toFixed(1), + '' + this._source._p2.price.toFixed(1), + this._source._options + ); + } } \ No newline at end of file diff --git a/src/trend-line/trend-line.ts b/src/trend-line/trend-line.ts index 94d6fea..bd5e393 100644 --- a/src/trend-line/trend-line.ts +++ b/src/trend-line/trend-line.ts @@ -1,5 +1,5 @@ import { - MouseEventParams, + MouseEventParams, } from 'lightweight-charts'; @@ -11,99 +11,99 @@ import { TwoPointDrawing } from '../drawing/two-point-drawing'; export class TrendLine extends TwoPointDrawing { - _type = "TrendLine" + _type = "TrendLine" - constructor( - p1: Point, - p2: Point, - options?: Partial - ) { - super(p1, p2, options) - this._paneViews = [new TrendLinePaneView(this)]; - } + constructor( + p1: Point, + p2: Point, + options?: Partial + ) { + super(p1, p2, options) + this._paneViews = [new TrendLinePaneView(this)]; + } - _moveToState(state: InteractionState) { - switch(state) { + _moveToState(state: InteractionState) { + switch(state) { - case InteractionState.NONE: - document.body.style.cursor = "default"; - this._options.showCircles = false; - this.requestUpdate(); - this._unsubscribe("mousedown", this._handleMouseDownInteraction); - break; + case InteractionState.NONE: + document.body.style.cursor = "default"; + this._options.showCircles = false; + this.requestUpdate(); + this._unsubscribe("mousedown", this._handleMouseDownInteraction); + break; - case InteractionState.HOVERING: - document.body.style.cursor = "pointer"; - this._options.showCircles = true; - this.requestUpdate(); - this._subscribe("mousedown", this._handleMouseDownInteraction); - this._unsubscribe("mouseup", this._handleMouseDownInteraction); - this.chart.applyOptions({handleScroll: true}); - break; + case InteractionState.HOVERING: + document.body.style.cursor = "pointer"; + this._options.showCircles = true; + this.requestUpdate(); + this._subscribe("mousedown", this._handleMouseDownInteraction); + this._unsubscribe("mouseup", this._handleMouseDownInteraction); + this.chart.applyOptions({handleScroll: true}); + break; - case InteractionState.DRAGGINGP1: - case InteractionState.DRAGGINGP2: - case InteractionState.DRAGGING: - document.body.style.cursor = "grabbing"; - this._subscribe("mouseup", this._handleMouseUpInteraction); - this.chart.applyOptions({handleScroll: false}); - break; - } - this._state = state; - } + case InteractionState.DRAGGINGP1: + case InteractionState.DRAGGINGP2: + case InteractionState.DRAGGING: + document.body.style.cursor = "grabbing"; + this._subscribe("mouseup", this._handleMouseUpInteraction); + this.chart.applyOptions({handleScroll: false}); + break; + } + this._state = state; + } - _onDrag(diff: any) { - if (this._state == InteractionState.DRAGGING || this._state == InteractionState.DRAGGINGP1) { - Drawing._addDiffToPoint(this._p1, diff.time, diff.logical, diff.price); - } - if (this._state == InteractionState.DRAGGING || this._state == InteractionState.DRAGGINGP2) { - Drawing._addDiffToPoint(this._p2, diff.time, diff.logical, diff.price); - } - } + _onDrag(diff: any) { + if (this._state == InteractionState.DRAGGING || this._state == InteractionState.DRAGGINGP1) { + Drawing._addDiffToPoint(this._p1, diff.time, diff.logical, diff.price); + } + if (this._state == InteractionState.DRAGGING || this._state == InteractionState.DRAGGINGP2) { + Drawing._addDiffToPoint(this._p2, diff.time, diff.logical, diff.price); + } + } - protected _onMouseDown() { + protected _onMouseDown() { this._startDragPoint = null; - const hoverPoint = this._latestHoverPoint; + const hoverPoint = this._latestHoverPoint; if (!hoverPoint) return; - const p1 = this._paneViews[0]._p1; - const p2 = this._paneViews[0]._p2; + const p1 = this._paneViews[0]._p1; + const p2 = this._paneViews[0]._p2; - if (!p1.x || !p2.x || !p1.y || !p2.y) return this._moveToState(InteractionState.DRAGGING); - - const tolerance = 10; - if (Math.abs(hoverPoint.x-p1.x) < tolerance && Math.abs(hoverPoint.y-p1.y) < tolerance) { - this._moveToState(InteractionState.DRAGGINGP1) - } - else if (Math.abs(hoverPoint.x-p2.x) < tolerance && Math.abs(hoverPoint.y-p2.y) < tolerance) { - this._moveToState(InteractionState.DRAGGINGP2) - } - else { - this._moveToState(InteractionState.DRAGGING); - } - } + if (!p1.x || !p2.x || !p1.y || !p2.y) return this._moveToState(InteractionState.DRAGGING); - protected _mouseIsOverDrawing(param: MouseEventParams, tolerance = 4) { + const tolerance = 10; + if (Math.abs(hoverPoint.x-p1.x) < tolerance && Math.abs(hoverPoint.y-p1.y) < tolerance) { + this._moveToState(InteractionState.DRAGGINGP1) + } + else if (Math.abs(hoverPoint.x-p2.x) < tolerance && Math.abs(hoverPoint.y-p2.y) < tolerance) { + this._moveToState(InteractionState.DRAGGINGP2) + } + else { + this._moveToState(InteractionState.DRAGGING); + } + } - if (!param.point) return false;; + protected _mouseIsOverDrawing(param: MouseEventParams, tolerance = 4) { - const x1 = this._paneViews[0]._p1.x; - const y1 = this._paneViews[0]._p1.y; - const x2 = this._paneViews[0]._p2.x; - const y2 = this._paneViews[0]._p2.y; - if (!x1 || !x2 || !y1 || !y2 ) return false; + if (!param.point) return false;; - const mouseX = param.point.x; - const mouseY = param.point.y; + const x1 = this._paneViews[0]._p1.x; + const y1 = this._paneViews[0]._p1.y; + const x2 = this._paneViews[0]._p2.x; + const y2 = this._paneViews[0]._p2.y; + if (!x1 || !x2 || !y1 || !y2 ) return false; - if (mouseX <= Math.min(x1, x2) - tolerance || - mouseX >= Math.max(x1, x2) + tolerance) { - return false; - } + const mouseX = param.point.x; + const mouseY = param.point.y; - const distance = Math.abs((y2 - y1) * mouseX - (x2 - x1) * mouseY + x2 * y1 - y2 * x1 - ) / Math.sqrt((y2 - y1) ** 2 + (x2 - x1) ** 2); + if (mouseX <= Math.min(x1, x2) - tolerance || + mouseX >= Math.max(x1, x2) + tolerance) { + return false; + } - return distance <= tolerance - } + const distance = Math.abs((y2 - y1) * mouseX - (x2 - x1) * mouseY + x2 * y1 - y2 * x1 + ) / Math.sqrt((y2 - y1) ** 2 + (x2 - x1) ** 2); + + return distance <= tolerance + } }