diff --git a/src/charts/chart.js b/src/charts/chart.js
index df29b84..097b36b 100644
--- a/src/charts/chart.js
+++ b/src/charts/chart.js
@@ -140,7 +140,7 @@ export function createShape(shapeType, points, options={}, ...callbacks) {
shapeId = chart.createMultipointShape(points, options)
if( callbacks.length )
shapeCallbacks[shapeId] = callbacks
- console.log(`created ${shapeType.name}`, shapeId)
+ // console.log(`created ${shapeType.name}`, shapeId)
const props = chart.getShapeById(shapeId).getProperties()
invokeCallbacks(callbacks, 'onCreate', shapeId, points, props)
return shapeId
@@ -160,7 +160,7 @@ const shapeCallbacks = {}
function onSelectedLineToolChanged() {
const tool = widget.selectedLineTool();
- console.log('line tool changed', tool)
+ // console.log('line tool changed', tool)
if( tool !== 'cursor' ) // 'cursor' cannot be selected manually and only happens just before the 'create' event is issued
cancelDrawing();
}
@@ -206,10 +206,13 @@ function handleCrosshairMovement(point) {
let drawingEventQueue = []
let eventLock = false // this is set to true before events are processed, in order to avoid any loops
-
+let propsEvents = {}
function handleDrawingEvent(id, event) {
- if (eventLock) return
+ if (eventLock && event !== 'properties_changed') { // properties has its own locking mechanism
+ console.log('ignoring event', id, event)
+ return
+ }
// it's important to decouple our actions from the TV thread. we must wait until the TV loop is completed
// before interacting with the chart
if (drawingEventQueue.length===0)
@@ -219,70 +222,80 @@ function handleDrawingEvent(id, event) {
function drawingEventWorker() {
- const queue = drawingEventQueue
- drawingEventQueue = []
- for (const [id,event] of queue)
- doHandleDrawingEvent(id,event)
-}
-
-function doHandleDrawingEvent(id, event) {
- // console.log('drawing event', arguments)
eventLock = true
try {
- const shape = event === 'remove' ? null : chart.getShapeById(id);
- if (event === 'create') {
- const co = useChartOrderStore();
- const callbacks = co.drawingCallbacks
- console.log('drawing callbacks', callbacks)
- if (callbacks !== null) {
- shapeCallbacks[id] = callbacks
- co.drawing = false
- const points = shape.getPoints()
- const props = shape.getProperties()
- invokeCallbacks(shapeCallbacks[id], 'onCreate', id, points, props)
- invokeCallbacks(shapeCallbacks[id], 'onPoints', id, points)
- invokeCallbacks(shapeCallbacks[id], 'onProps', id, props)
- }
- } else if (event === 'points_changed') {
- if (id in shapeCallbacks) {
- const points = shape.getPoints()
- // console.log('points', points)
- invokeCallbacks(shapeCallbacks[id], 'onPoints', id, points)
- }
- } else if (event === 'properties_changed') {
- if (id in shapeCallbacks) {
- const props = shape.getProperties()
- // console.log('props', props)
- invokeCallbacks(shapeCallbacks[id], 'onProps', id, props)
- }
- } else if (event === 'move') {
- if (id in shapeCallbacks) {
- invokeCallbacks(shapeCallbacks[id], 'onMove', id)
- }
- } else if (event === 'remove') {
- if (id in shapeCallbacks) {
- invokeCallbacks(shapeCallbacks[id], 'onDelete', id)
- }
- } else if (event === 'click') {
- if (id in shapeCallbacks) {
- invokeCallbacks(shapeCallbacks[id], 'onClick', id)
- }
- } else if (event === 'hide') {
- if (id in shapeCallbacks) {
- invokeCallbacks(shapeCallbacks[id], 'onHide', id)
- }
- } else if (event === 'show') {
- if (id in shapeCallbacks) {
- invokeCallbacks(shapeCallbacks[id], 'onShow', id)
- }
- } else
- console.log('unknown drawing event', event)
+ const queue = drawingEventQueue
+ drawingEventQueue = []
+ propsEvents = {}
+ // console.log('events locked', queue)
+ for (const [id, event] of queue) {
+ if (event === 'properties_changed')
+ propsEvents[id] = event
+ else
+ doHandleDrawingEvent(id, event)
+ }
+ for (const [k,v] of Object.entries(propsEvents))
+ doHandleDrawingEvent(k, v)
}
finally {
eventLock = false
+ // console.log('events unlocked')
}
}
+function doHandleDrawingEvent(id, event) {
+ // console.log('drawing event', id, event)
+ const shape = event === 'remove' ? null : chart.getShapeById(id);
+ if (event === 'create') {
+ const co = useChartOrderStore();
+ const callbacks = co.drawingCallbacks
+ // console.log('drawing callbacks', callbacks)
+ if (callbacks !== null) {
+ shapeCallbacks[id] = callbacks
+ co.drawing = false
+ const points = shape.getPoints()
+ const props = shape.getProperties()
+ invokeCallbacks(shapeCallbacks[id], 'onCreate', id, points, props)
+ invokeCallbacks(shapeCallbacks[id], 'onPoints', id, points)
+ invokeCallbacks(shapeCallbacks[id], 'onProps', id, props)
+ }
+ } else if (event === 'points_changed') {
+ if (id in shapeCallbacks) {
+ const points = shape.getPoints()
+ // console.log('points', points)
+ invokeCallbacks(shapeCallbacks[id], 'onPoints', id, points)
+ }
+ } else if (event === 'properties_changed') {
+ const props = shape.getProperties()
+ if (id in shapeCallbacks)
+ invokeCallbacks(shapeCallbacks[id], 'onProps', id, props)
+ else
+ // otherwise it's an event on a shape we don't "own"
+ console.log('warning: ignoring setProperties on TV shape', id, props)
+ } else if (event === 'move') {
+ if (id in shapeCallbacks) {
+ invokeCallbacks(shapeCallbacks[id], 'onMove', id)
+ }
+ } else if (event === 'remove') {
+ if (id in shapeCallbacks) {
+ invokeCallbacks(shapeCallbacks[id], 'onDelete', id)
+ }
+ } else if (event === 'click') {
+ if (id in shapeCallbacks) {
+ invokeCallbacks(shapeCallbacks[id], 'onClick', id)
+ }
+ } else if (event === 'hide') {
+ if (id in shapeCallbacks) {
+ invokeCallbacks(shapeCallbacks[id], 'onHide', id)
+ }
+ } else if (event === 'show') {
+ if (id in shapeCallbacks) {
+ invokeCallbacks(shapeCallbacks[id], 'onShow', id)
+ }
+ } else
+ console.log('unknown drawing event', event)
+}
+
export function deleteShapeId(id) {
if( id in shapeCallbacks )
delete shapeCallbacks[id]
diff --git a/src/charts/datafeed.js b/src/charts/datafeed.js
index 60a57bb..82cbec7 100644
--- a/src/charts/datafeed.js
+++ b/src/charts/datafeed.js
@@ -6,6 +6,8 @@ import {
import {jBars} from './jBars.js';
import {metadata} from "@/version.js";
import FlexSearch from "flexsearch";
+import {useChartOrderStore} from "@/orderbuild.js";
+
const lastBarsCache = new Map();
@@ -158,6 +160,7 @@ export default {
onResolveErrorCallback('cannot resolve symbol');
return;
}
+ useChartOrderStore().selectedSymbol = symbolItem
// Symbol information object
const symbolInfo = {
ticker: symbolItem.full_name,
diff --git a/src/charts/shape.js b/src/charts/shape.js
index a300026..d68d16f 100644
--- a/src/charts/shape.js
+++ b/src/charts/shape.js
@@ -37,18 +37,19 @@ export const ShapeType = {
class Shape {
- constructor(type, model={}, onModel=null, onDelete=null) {
+ constructor(type, model={}, onModel=null, onDelete=null, props=null) {
// the Shape object manages synchronizing internal data with a corresponding TradingView shape
this.id = null // TradingView shapeId, or null if no TV shape created yet (drawing mode)
this.type = type // ShapeType
this.model = model // subclass-specific
- this.points = null // TradingView points for the given shape
- this.props = null
+ this.points = null
+ this.points = this.pointsFromModel()
+ console.log('construct points', this.points)
+ this.props = props === null ? this.propsFromModel() : mixin(props, this.propsFromModel())
if (onModel !== null)
this.onModel = onModel
if (onDelete !== null )
this.onDelete = onDelete
- this.lock = 0 // used to prevent callbacks when we are the ones forcing the chart change
this.create()
}
@@ -67,15 +68,13 @@ class Shape {
create() {
if (this.id !== null) return
// programatically create the shape using the current this.points
- const points = this.pointsFromModel()
- if( points && points.length ) {
- this.doCreate(points)
+ if( this.points && this.points.length ) {
+ this.doCreate()
}
}
- doCreate(points) {
- const props = this.propsFromModel()
- createShape(this.type, points, {overrides:props}, new ShapeTVCallbacks(this))
+ doCreate() {
+ createShape(this.type, this.points, {overrides:this.props}, new ShapeTVCallbacks(this))
}
createOrDraw() {
@@ -96,7 +95,7 @@ class Shape {
setModel(model, props=null) {
for( const [k,v] of Object.entries(model))
this.model[k] = v
- this.setPoints(this.pointsFromModel());
+ this.setPointsIfDirty(this.pointsFromModel());
let p
const mp = this.propsFromModel();
if (props===null) {
@@ -111,6 +110,7 @@ class Shape {
setPoints(points) {
+ this.points = points
if (points !== null && points.length) {
if (this.id === null)
this.doCreate(points)
@@ -129,13 +129,19 @@ class Shape {
}
}
}
+ // todo delete if points is null or empty?
+ }
+
+
+ setPointsIfDirty(points) {
+ if (dirtyPoints(this.points, points))
+ this.setPoints(points)
}
setProps(props) {
- if(this.id) {
+ if(this.id)
this.tvShape().setProperties(props)
- }
}
@@ -169,6 +175,15 @@ class Shape {
// Model synchronization
//
+ onDirtyModel(toModel) {
+ const old = {...this.model}
+ toModel.call(this)
+ const dirty = dirtyKeys(old, this.model)
+ // console.log('onDirtyModel', old, this.model, dirty)
+ if (dirty.length)
+ this.onModel(this.model)
+ }
+
pointsFromModel() {return null}
pointsToModel() {} // set the model using this.points
propsFromModel() {return null}
@@ -198,6 +213,23 @@ class Shape {
}
+function dirtyPoints(pointsA, pointsB) {
+ if (pointsB===null)
+ return pointsA !== null
+ if (pointsA===null)
+ return pointsB.length > 0
+ if (pointsA.length!==pointsB.length)
+ return true
+ for (const i in pointsA) {
+ const a = pointsA[i]
+ const b = pointsB[i]
+ if ( a.time !== b.time || a.price !== b.price )
+ return true
+ }
+ return false
+}
+
+
// B is modifying A
function dirtyProps(propsA, propsB) {
if (propsB===null)
@@ -212,6 +244,16 @@ function dirtyProps(propsA, propsB) {
}
+// B is modifying A
+function dirtyKeys(propsA, propsB) {
+ if (propsB===null)
+ return propsA === null ? [] : [...Object.keys(propsA)]
+ if (propsA===null)
+ return [...Object.keys(propsB)]
+ return [...(new Set(Object.keys(propsB)).union(new Set(Object.keys(propsA))))].filter((k)=> !(k in propsA) || propsA[k] !== propsB[k])
+}
+
+
class ShapeTVCallbacks {
// These methods are called by TradingView and provide some default handling before invoking our own Shape callbacks
@@ -230,16 +272,13 @@ class ShapeTVCallbacks {
onPoints(shapeId, points) {
this.shape.points = points
this.shape.onPoints(points)
- this.shape.pointsToModel()
- this.shape.onModel(this.shape.model)
+ this.shape.onDirtyModel(this.shape.pointsToModel)
}
onProps(shapeId, props) {
- console.log('props', shapeId, props)
this.shape.props = props
this.shape.onProps(props)
- this.shape.propsToModel()
- this.shape.onModel(this.shape.model)
+ this.shape.onDirtyModel(this.shape.propsToModel)
}
onDraw() {
@@ -296,8 +335,8 @@ class ShapeTVCallbacks {
export class HLine extends Shape {
- constructor(model, onModel=null, onDelete=null) {
- super(ShapeType.HLine, model, onModel, onDelete)
+ constructor(model, onModel=null, onDelete=null, props=null) {
+ super(ShapeType.HLine, model, onModel, onDelete, props)
}
pointsFromModel() {
diff --git a/src/components/chart/BuilderFactory.vue b/src/components/chart/BuilderFactory.vue
index c0026cb..e417074 100644
--- a/src/components/chart/BuilderFactory.vue
+++ b/src/components/chart/BuilderFactory.vue
@@ -1,5 +1,5 @@
-