668 lines
24 KiB
JavaScript
668 lines
24 KiB
JavaScript
// noinspection JSPotentiallyInvalidUsageOfThis
|
|
|
|
import {invokeCallback, mixin} from "@/common.js";
|
|
import {chart, createShape, deleteShapeId, dragging, draggingShapeIds, drawShape, widget} from "@/charts/chart.js";
|
|
import Color from "color";
|
|
import {dirtyItems, dirtyPoints, nearestOhlcStart} from "@/charts/chart-misc.js";
|
|
import {defined, toPrecision} from "@/misc.js";
|
|
|
|
|
|
//
|
|
// Usage of Shapes:
|
|
// const shape = new Shape(ShapeType.HLine)
|
|
// shape.draw()
|
|
// shape.points
|
|
// > [{time:17228394, price:42638.83}]
|
|
//
|
|
// shape.model stores the shape-specific control points, as defined by the shape subclass, and may be a vue ref.
|
|
// Use the shape.model.* fields in components to get reactive effects with the chart, or pass an onModel callback
|
|
// to the constructor
|
|
//
|
|
|
|
|
|
export const ShapeType = {
|
|
// this "enum" is a record of the TradingView keystrings
|
|
|
|
/*
|
|
The `name` field must match the user-facing name in the TradingView toolbar UI
|
|
TradingView drawing tool codes:
|
|
https://www.tradingview.com/charting-library-docs/latest/api/modules/Charting_Library/#supportedlinetools
|
|
text anchored_text note anchored_note signpost double_curve arc icon emoji sticker arrow_up arrow_down arrow_left arrow_right price_label price_note arrow_marker flag vertical_line horizontal_line cross_line horizontal_ray trend_line info_line trend_angle arrow ray extended parallel_channel disjoint_angle flat_bottom anchored_vwap pitchfork schiff_pitchfork_modified schiff_pitchfork balloon comment inside_pitchfork pitchfan gannbox gannbox_square gannbox_fixed gannbox_fan fib_retracement fib_trend_ext fib_speed_resist_fan fib_timezone fib_trend_time fib_circles fib_spiral fib_speed_resist_arcs fib_channel xabcd_pattern cypher_pattern abcd_pattern callout triangle_pattern 3divers_pattern head_and_shoulders fib_wedge elliott_impulse_wave elliott_triangle_wave elliott_triple_combo elliott_correction elliott_double_combo cyclic_lines time_cycles sine_line long_position short_position forecast date_range price_range date_and_price_range bars_pattern ghost_feed projection rectangle rotated_rectangle circle ellipse triangle polyline path curve cursor dot arrow_cursor eraser measure zoom brush highlighter regression_trend fixed_range_volume_profile
|
|
*/
|
|
Segment: {name: 'Trend Line', code: 'trend_line'},
|
|
Ray: {name: 'Ray', code: 'ray', drawingProp: 'linetoolray'},
|
|
Line: {name: 'Extended Line', code: 'extended', drawingProp: 'linetoolray'},
|
|
HRay: {name: 'Horizontal Ray', code: 'horizontal_ray'},
|
|
HLine: {name: 'Horizontal Line', code: 'horizontal_line', drawingProp: 'linetoolhorzline'},
|
|
VLine: {name: 'Vertical Line', code: 'vertical_line', drawingProp: 'linetoolvertline'},
|
|
PriceRange: {name: 'Price Range', code: 'price_range'},
|
|
DateRange: {name: 'Date Range', code: 'date_range', drawingProp: 'linetooldaterange'},
|
|
}
|
|
|
|
|
|
export function allocationText(buy, weight, amount, baseSymbol, amountSymbol = null, parts = 1, separator = ' ') {
|
|
const hasAmount = amount !== null && amount !== undefined && amount > 0
|
|
if (hasAmount)
|
|
amount = Number(amount)
|
|
const hasWeight = weight !== null && weight !== undefined && weight !== 1
|
|
if (hasWeight)
|
|
weight = Number(weight)
|
|
let text = buy === undefined ? '' : buy ? 'Buy ' : 'Sell '
|
|
if (hasWeight)
|
|
text += `${(weight * 100).toFixed(1)}%`
|
|
const hasSymbol = baseSymbol !== null && baseSymbol !== undefined
|
|
if (hasAmount && hasSymbol) {
|
|
if (hasWeight)
|
|
text += separator
|
|
if (amountSymbol!==null && amountSymbol!==baseSymbol)
|
|
text += `${baseSymbol} worth ${toPrecision(amount,3)} ${amountSymbol}`
|
|
else
|
|
text += `${toPrecision(amount,3)} ${baseSymbol}`
|
|
}
|
|
if (parts > 1)
|
|
text += separator + `in ${Math.round(parts)} parts`
|
|
return text
|
|
}
|
|
|
|
|
|
export class Shape {
|
|
|
|
constructor(type, onModel=null, onDelete=null, props=null, readonly=false, overrides={}) {
|
|
// the Shape object manages synchronizing internal data with a corresponding TradingView shape
|
|
|
|
// each shape in the class hierarchy overrides setModel() to cause effects in TradingView
|
|
// TV callbacks are returned to the on*() handlers, primarily onPoints() and onProps(). Handlers
|
|
// of TV callbacks should call updateModel() with just the kv's they want to change. Any changes
|
|
// are then passed onto the onModel() callback.
|
|
|
|
// the model object has various attributes defined by each shape subclass. each subclass's constructor
|
|
// must call setModel(model) after first calling super.constructor() and optionally declaring any model
|
|
// defaults on this.model.
|
|
|
|
// Shape: {
|
|
// lineColor, textColor, color, // lineColor and textColor default to color
|
|
// }
|
|
|
|
this.debug = false
|
|
this.id = null // TradingView shapeId, or null if no TV shape created yet (drawing mode)
|
|
this.type = type // ShapeType
|
|
this.model = {} // set to nothing at first
|
|
this.tvCallbacks = null
|
|
this.ourPoints = null
|
|
this.tvPoints = null
|
|
this.creationOptions = readonly ?
|
|
{disableSave:true, disableUndo:true, disableSelection:true, lock:true} :
|
|
{disableSave:true, disableUndo:true, disableSelection:false}
|
|
this.creationOptions.overrides = overrides
|
|
this.ourProps = {}
|
|
if (props !== null)
|
|
this.ourProps = mixin(props, this.ourProps)
|
|
this.tvProps = {}
|
|
if (onModel !== null)
|
|
this.onModel = onModel
|
|
if (onDelete !== null )
|
|
this.onDelete = onDelete
|
|
|
|
//
|
|
// Model values handled by this base class
|
|
//
|
|
|
|
this.model.color = null
|
|
// if allocation is set, a percentage is displayed
|
|
this.model.allocation = null
|
|
// if maxAllocation is set, the line color (but not text color) is shaded down based on allocation
|
|
this.model.maxAllocation = null
|
|
// both amount and amountSymbol must be set in order to display amount text
|
|
this.model.amount = null
|
|
this.model.baseSymbol = null
|
|
this.model.amountSymbol = null
|
|
this.model.extraText = null
|
|
this.model.textLocation = null // defaults to 'above' if not set
|
|
|
|
// LEAF SUBCLASSES MUST CALL setModel(model) AFTER ALL CONSTRUCTION.
|
|
}
|
|
|
|
//
|
|
// primary interface methods
|
|
//
|
|
|
|
setModel(model) {
|
|
if (defined(model.color))
|
|
this.model.color = model.color
|
|
if (defined(model.allocation))
|
|
this.model.allocation = model.allocation
|
|
if (defined(model.maxAllocation))
|
|
this.model.maxAllocation = model.maxAllocation
|
|
if (defined(model.amount))
|
|
this.model.amount = model.amount
|
|
if (defined(model.amountSymbol))
|
|
this.model.amountSymbol = model.amountSymbol
|
|
if (defined(model.baseSymbol))
|
|
this.model.baseSymbol = model.baseSymbol
|
|
if (defined(model.extraText))
|
|
this.model.extraText = model.extraText
|
|
if (defined(model.breakout))
|
|
this.model.breakout = model.breakout
|
|
if (defined(model.textLocation))
|
|
this.model.textLocation = model.textLocation
|
|
if (defined(model.buy))
|
|
this.model.buy = model.buy
|
|
|
|
const newProps = {}
|
|
|
|
// text color
|
|
const color = this.model.color ? this.model.color : '#0044ff'
|
|
newProps.textcolor = color
|
|
|
|
// line color
|
|
if (defined(this.model.allocation) && defined(this.model.maxAllocation)) {
|
|
const w = this.model.allocation / this.model.maxAllocation
|
|
if (!w)
|
|
newProps.linecolor = 'rgba(0,0,0,0)'
|
|
else {
|
|
// weighted line color
|
|
let c = new Color(color).rgb()
|
|
// the perceptual color of a weight follows Stevens's Power Law
|
|
// https://en.wikipedia.org/wiki/Stevens's_power_law
|
|
newProps.linecolor = c.alpha(Math.pow(w,0.67)).string()
|
|
}
|
|
}
|
|
else
|
|
newProps.linecolor = color
|
|
|
|
// text label
|
|
let text = allocationText(this.model.buy, this.model.allocation, this.model.amount, this.model.baseSymbol, this.model.amountSymbol)
|
|
if (this.model.breakout)
|
|
text += ' ' + (this.model.textLocation==='above' ? '▲Breakout▲' : '▼Breakout▼')
|
|
if (this.model.extraText)
|
|
text += ' '+this.model.extraText
|
|
if (this.debug) text = `${this.id} ` + text
|
|
if (!text.length)
|
|
newProps.showLabel = false
|
|
else {
|
|
newProps.text = text
|
|
newProps.showLabel = true
|
|
newProps.textLocation = this.model.textLocation
|
|
newProps.vertLabelsAlign =
|
|
this.model.textLocation === 'above' ? 'bottom' :
|
|
this.model.textLocation === 'below' ? 'top' : null;
|
|
}
|
|
|
|
if (this.debug && this.id)
|
|
console.log('newProps', this.id, chart.getShapeById(this.id).getProperties(), newProps)
|
|
this.setProps(newProps)
|
|
}
|
|
|
|
onModel(model, oldModel, changedKeys) {} // model was changed by a TradingView user action
|
|
|
|
updateModel(changes) {
|
|
const oldModel = {...this.model}
|
|
if (this.debug) console.log('updateModel', this.id, changes)
|
|
const changedKeys = []
|
|
for (const k of Object.keys(changes)) {
|
|
if( changes[k] !== undefined && changes[k] !== this.model[k] ) {
|
|
changedKeys.push(k)
|
|
this.model[k] = changes[k]
|
|
}
|
|
}
|
|
if (changedKeys.length)
|
|
this.onModel(this.model, oldModel, changedKeys)
|
|
}
|
|
|
|
colorProps() {
|
|
if (!this.model.color&&!this.model.lineColor&&!this.model.textColor) return null
|
|
const o = {}
|
|
const p = this.type.drawingProp
|
|
const lc = this.model.lineColor ? this.model.lineColor : this.model.color;
|
|
const tc = this.model.textColor ? this.model.textColor : this.model.color;
|
|
const tl = this.model.textLocation ? this.model.textLocation : 'above';
|
|
if (lc)
|
|
o[p+".linecolor"] = lc
|
|
if (tc)
|
|
o[p+".textcolor"] = tc
|
|
if (tl)
|
|
o[p+".textlocation"] = tl
|
|
return o
|
|
}
|
|
|
|
draw() {
|
|
// have the user draw this shape
|
|
if (this.debug) console.log(`draw ${this.type.name}`, this.model)
|
|
if (this.id)
|
|
throw Error(`Shape already exists ${this.id}`)
|
|
const or = this.drawingOverrides();
|
|
// if (this.debug) console.log('drawing overrides', or)
|
|
widget.applyOverrides(or)
|
|
drawShape(this.type, new ShapeTVCallbacks(this))
|
|
}
|
|
|
|
// return an object with property defaults, e.g. { "linetoolhorzline.linecolor": "#7f11e0" }
|
|
// https://www.tradingview.com/charting-library-docs/latest/api/modules/Charting_Library#drawingoverrides
|
|
drawingOverrides() {
|
|
return mixin(this.colorProps(), this.creationOptions)
|
|
}
|
|
|
|
create() {
|
|
if (this.id !== null) return
|
|
// programatically create the shape using the current this.points
|
|
if( this.ourPoints && this.ourPoints.length ) {
|
|
this.doCreate(this.ourPoints, this.ourProps, this.creationOptions)
|
|
}
|
|
}
|
|
|
|
doCreate(points, props, options={}) {
|
|
// createShape(this.type, this.points, {overrides:this.props}, new ShapeTVCallbacks(this))
|
|
options = mixin(options, this.drawingOverrides())
|
|
options['overrides'] = props
|
|
this.tvPoints = this.pointsToTvOhlcStart(points)
|
|
this.tvCallbacks = new ShapeTVCallbacks(this);
|
|
const id = createShape(this.type, this.tvPoints, options, this.tvCallbacks)
|
|
// todo set id?
|
|
if (this.debug) console.log('created', id, this.type.name, this.tvPoints)
|
|
}
|
|
|
|
onCreate(points, props) {
|
|
// the user has finished creating all the control points. drawing mode is exited and the initial shape is created.
|
|
this.setPoints(points)
|
|
this.setProps(props)
|
|
}
|
|
|
|
|
|
createOrDraw() {
|
|
if(this.id) return
|
|
if(this.ourPoints && this.ourPoints.length)
|
|
this.create()
|
|
else
|
|
this.draw()
|
|
}
|
|
|
|
|
|
tvShape() {
|
|
try {
|
|
return this.id === null ? null : chart.getShapeById(this.id);
|
|
}
|
|
catch (e) {
|
|
console.error(`Could not get chart shape ${this.id}`)
|
|
return null
|
|
}
|
|
}
|
|
|
|
|
|
setPoints(points) {
|
|
// setting points to null will delete the shape from the chart. setting points to a valid value will cause the
|
|
// shape to be drawn.
|
|
if (this.debug) console.error('setPoints', this.id, this.ourPoints, points)
|
|
this.ourPoints = points
|
|
if (points === null || !points.length)
|
|
this.delete()
|
|
else {
|
|
if (this.id === null)
|
|
this.create()
|
|
else {
|
|
points = this.pointsToTvOhlcStart(points)
|
|
if (dirtyPoints(this.tvPoints, points)) {
|
|
/*
|
|
setPoints(e) {
|
|
if (this._source.isFixed())
|
|
return;
|
|
const t = o(this._source);
|
|
if (t !== e.length)
|
|
throw new Error(`Wrong points count. Required: ${t}, provided: ${e.length}`);
|
|
const i = this._pointsConverter.apiPointsToDataSource(e);
|
|
this._model.startChangingLinetool(this._source),
|
|
this._model.changeLinePoints(this._source, i),
|
|
this._model.endChangingLinetool(!0),
|
|
this._source.createServerPoints()
|
|
}
|
|
*/
|
|
const s = this.tvShape();
|
|
const lbe = s._model._lineBeingEdited
|
|
const lpbc = s._model._linePointBeingChanged
|
|
const lpbe = s._model._linePointBeingEdited
|
|
if (lpbc!==null)
|
|
s._model.endChangingLinetool(!0)
|
|
// s._source.createServerPoints() // todo necessary?
|
|
s.setPoints(points)
|
|
if (lpbc!==null)
|
|
s._model.startChangingLinetool(lbe, lpbc, lpbe)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// diagonals need to override this to adjust their price as well.
|
|
pointsToTvOhlcStart(points, periodSeconds = null) {
|
|
return points === null ? null : points.map((p) => {
|
|
return {time: nearestOhlcStart(p.time, periodSeconds), price: p.price}
|
|
})
|
|
}
|
|
|
|
onPoints(points) {} // the control points of an existing shape were changed
|
|
|
|
onDrag(points) { console.log('shape ondrag'); this.onPoints(points) }
|
|
|
|
setProps(props) {
|
|
if (!props || Object.keys(props).length===0) return
|
|
if (this.debug) console.log('setProps', this.id, props)
|
|
this.ourProps = mixin(props, this.ourProps)
|
|
if(this.id) {
|
|
const p = dirtyItems(this.tvProps, props)
|
|
this.tvProps = this.tvProps === null ? p : mixin(p, this.tvProps)
|
|
if (this.debug) console.log('setting tv props', this.tvProps)
|
|
this.tvShape().setProperties(p)
|
|
}
|
|
}
|
|
|
|
onProps(props) { // the display properties of an existing shape were changed
|
|
if (this.debug) console.log('shape onProps', this.id, this.model, props)
|
|
if (props.textcolor && typeof props.textcolor !== 'object' && props.textcolor !== this.tvProps.textcolor)
|
|
this.updateModel({color:props.textcolor})
|
|
else if (props.linecolor && typeof props.linecolor !== 'object' && props.linecolor !== this.tvProps.linecolor)
|
|
this.updateModel({color:props.linecolor})
|
|
}
|
|
|
|
beingDragged() {
|
|
return draggingShapeIds.indexOf(this.id) !== -1
|
|
}
|
|
|
|
|
|
delete() {
|
|
if (this.debug) console.log('shape.delete', this.id)
|
|
this.ourPoints = null
|
|
this.tvPoints = null
|
|
this.tvProps = null
|
|
if (this.id === null) return
|
|
if (this.tvCallbacks !== null) {
|
|
this.tvCallbacks.enabled = false
|
|
this.tvCallbacks = null
|
|
}
|
|
deleteShapeId(this.id)
|
|
this.id = null
|
|
}
|
|
|
|
|
|
//
|
|
// Overridable shape callbacks initiated by TradingView
|
|
//
|
|
|
|
onDraw() {} // start drawing a new shape for the builder
|
|
onRedraw() {} // the mouse moved while in drawing mode
|
|
onUndraw() {} // drawing was canceled by clicking on a different tool
|
|
onAddPoint() {} // the user clicked a point while drawing (that point is added to the points list)
|
|
onMove(points) {} // the shape was moved by dragging a drawing element not the control point
|
|
onHide(props) {}
|
|
onShow(props) {}
|
|
onClick() {} // the shape was selected
|
|
onDelete() {}
|
|
|
|
}
|
|
|
|
|
|
class ShapeTVCallbacks {
|
|
// These methods are called by TradingView and provide some default handling before invoking our own Shape callbacks
|
|
|
|
constructor(shape) {
|
|
this.shape = shape
|
|
this.enabled = true // gets disabled when the shape is deleted, squelching any further callbacks
|
|
}
|
|
|
|
onCreate(shapeId, _tvShape, points, props) {
|
|
if (!this.enabled) return // possible when shape is deleted and re-created
|
|
this.shape.id = shapeId
|
|
invokeCallback(this.shape, 'onCreate', points, props)
|
|
invokeCallback(this.shape, 'onPoints', points)
|
|
}
|
|
|
|
onPoints(shapeId, _tvShape, points) {
|
|
if (!this.enabled) return // possible when shape is deleted and re-created
|
|
if (this.shape.debug) console.log('tvcb onPoints', shapeId, points)
|
|
if (!dragging || this.shape.beingDragged())
|
|
this.shape.onPoints(points)
|
|
this.shape.tvPoints = points
|
|
}
|
|
|
|
onProps(shapeId, _tvShape, props) {
|
|
if (!this.enabled) return // possible when shape is deleted and re-created
|
|
if (this.shape.debug) console.log('tvOnProps', shapeId, props)
|
|
if (!dragging) // do not listen to props during redraw
|
|
this.shape.onProps(props)
|
|
this.shape.tvProps = props
|
|
}
|
|
|
|
onDraw() {
|
|
if (!this.enabled) return // possible when shape is deleted and re-created
|
|
invokeCallback(this.shape, 'onDraw')
|
|
}
|
|
|
|
onRedraw() {
|
|
if (!this.enabled) return // possible when shape is deleted and re-created
|
|
invokeCallback(this.shape, 'onRedraw')
|
|
}
|
|
|
|
onUndraw() {
|
|
if (!this.enabled) return // possible when shape is deleted and re-created
|
|
invokeCallback(this.shape, 'onUndraw')
|
|
}
|
|
|
|
onAddPoint() {
|
|
if (!this.enabled) return // possible when shape is deleted and re-created
|
|
invokeCallback(this.shape, 'onAddPoint')
|
|
}
|
|
|
|
onMove(shapeId, _tvShape, points) {
|
|
if (!this.enabled) return // possible when shape is deleted and re-created
|
|
invokeCallback(this.shape, 'onMove',points)
|
|
}
|
|
|
|
onDrag(shapeId, _tvShape, points) {
|
|
if (!this.enabled) return // possible when shape is deleted and re-created
|
|
if (this.shape.debug) console.log('onDrag', shapeId)
|
|
invokeCallback(this.shape, 'onDrag', points)
|
|
}
|
|
|
|
onHide(shapeId, _tvShape, props) {
|
|
if (!this.enabled) return // possible when shape is deleted and re-created
|
|
invokeCallback(this.shape, 'onHide',props)
|
|
}
|
|
|
|
onShow(shapeId, _tvShape, props) {
|
|
if (!this.enabled) return // possible when shape is deleted and re-created
|
|
invokeCallback(this.shape, 'onShow',props)
|
|
}
|
|
|
|
onClick(shapeId, _tvShape) {
|
|
if (!this.enabled) return // possible when shape is deleted and re-created
|
|
invokeCallback(this.shape, 'onClick')
|
|
}
|
|
|
|
onDelete(shapeId) {
|
|
this.enabled = false
|
|
this.shape.id = null
|
|
invokeCallback(this.shape, 'onDelete', shapeId)
|
|
}
|
|
}
|
|
|
|
|
|
export class Line extends Shape {
|
|
}
|
|
|
|
|
|
export class HLine extends Line {
|
|
constructor(model, onModel=null, onDelete=null, props=null, readonly=false) {
|
|
super(ShapeType.HLine, onModel, onDelete, props, readonly, {
|
|
// todo this isnt working
|
|
linestyle: 0,
|
|
linewidth: 2,
|
|
italic: false,
|
|
})
|
|
|
|
// Model
|
|
this.model.price = null
|
|
|
|
this.setModel(model) // call setModel at the end
|
|
}
|
|
|
|
delete() {
|
|
this.model.price = null
|
|
super.delete()
|
|
}
|
|
|
|
|
|
setModel(model) {
|
|
super.setModel(model)
|
|
if (model.price === null) {
|
|
this.model.price = null
|
|
this.delete()
|
|
}
|
|
else if (model.price !== this.model.price) {
|
|
this.model.price = model.price
|
|
this.setPoints([{time:0,price:this.model.price}])
|
|
}
|
|
}
|
|
|
|
onPoints(points) {
|
|
super.onPoints(points);
|
|
const price = points[0].price;
|
|
this.updateModel({price})
|
|
}
|
|
|
|
}
|
|
|
|
|
|
export class VLine extends Line {
|
|
constructor(model, onModel=null, onDelete=null, props=null) {
|
|
super(ShapeType.VLine, onModel, onDelete, props)
|
|
|
|
// Model
|
|
this.model.time = null
|
|
|
|
this.setModel(model) // call setModel at the end
|
|
}
|
|
|
|
|
|
delete() {
|
|
this.model.time = null
|
|
super.delete()
|
|
}
|
|
|
|
|
|
setModel(model) {
|
|
// if (this.debug) console.error('VLine setModel', this.id, {...this.model}, model)
|
|
super.setModel(model)
|
|
if (model.time === null)
|
|
this.delete()
|
|
else if (model.time !== this.model.time) {
|
|
this.model.time = model.time
|
|
this.setPoints([{time: model.time, price: 0}])
|
|
}
|
|
}
|
|
|
|
onPoints(points) {
|
|
const time = points[0].time;
|
|
if ( time !== this.tvPoints[0].time ) {
|
|
super.onPoints(points);
|
|
this.updateModel({time: time})
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
export class DLine extends Line {
|
|
constructor(model, onModel=null, onDelete=null, props=null, readonly=false) {
|
|
super(ShapeType.Ray, onModel, onDelete, props, readonly,{
|
|
// todo style overrides
|
|
})
|
|
|
|
// Model
|
|
this.model.pointA = null // {time:..., price:...}
|
|
this.model.pointB = null
|
|
this.model.extendLeft = true
|
|
this.model.extendRight = true
|
|
|
|
this.setModel(model) // call setModel at the end
|
|
}
|
|
|
|
|
|
drawingOverrides() {
|
|
const result = super.drawingOverrides();
|
|
result.extendLeft = this.model.extendLeft
|
|
result.extendRight = this.model.extendRight
|
|
return result
|
|
}
|
|
|
|
|
|
delete() {
|
|
this.model.pointA = null
|
|
this.model.pointB = null
|
|
super.delete();
|
|
}
|
|
|
|
setModel(model) {
|
|
super.setModel(model)
|
|
if (model.pointA === null && model.pointB === null)
|
|
this.delete()
|
|
else {
|
|
this.setProps({extendLeft:model.extendLeft, extendRight: model.extendRight})
|
|
if ('pointA' in model && 'pointB' in model) {
|
|
const newPoints = [model.pointA, model.pointB];
|
|
const oldPoints = [this.model.pointA, this.model.pointB];
|
|
if (dirtyPoints(oldPoints, newPoints)) {
|
|
this.model.pointA = newPoints[0]
|
|
this.model.pointB = newPoints[1]
|
|
this.setPoints(newPoints)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
onPoints(points) {
|
|
let dirty = this.tvPoints === null
|
|
for (let i=0; !dirty && i<points.length; i++) {
|
|
if( points[i].time !== this.tvPoints[i].time || points[i].price !== this.tvPoints[i].price )
|
|
dirty = true
|
|
}
|
|
if (dirty) {
|
|
super.onPoints(points);
|
|
this.updateModel({pointA: points[0], pointB: points[1]})
|
|
}
|
|
}
|
|
|
|
onProps(props) {
|
|
super.onProps(props);
|
|
this.updateModel({extendLeft: props.extendLeft, extendRight: props.extendRight})
|
|
}
|
|
|
|
|
|
/* todo tim
|
|
pointsToTvOhlcStart(points, periodSeconds = null) {
|
|
if (points === null) return null
|
|
const [v,w] = points
|
|
const [b,m] = computeInterceptSlope(v.time, v.price, w.time, w.price)
|
|
points.map((p) => {
|
|
let {time, price} = p
|
|
const aligned = nearestOhlcStart(time, periodSeconds);
|
|
if (time !== aligned) {
|
|
time = aligned
|
|
price = b + m * aligned
|
|
}
|
|
return {time, price}
|
|
})
|
|
}
|
|
*/
|
|
}
|
|
|
|
|
|
export class DateRange extends Shape {
|
|
constructor(model, onModel=null, onDelete=null, props=null) {
|
|
super(ShapeType.DateRange, onModel, onDelete, props)
|
|
}
|
|
|
|
|
|
setModel(model) {
|
|
super.setModel(model);
|
|
if (model.startTime !== this.model.startTime || model.endTime !== this.model.endTime)
|
|
this.setPoints([{time: model.startTime}, {time: model.endTime}])
|
|
}
|
|
}
|