diff --git a/src/charts/shape.js b/src/charts/shape.js index a47bd55..90ac5c7 100644 --- a/src/charts/shape.js +++ b/src/charts/shape.js @@ -3,8 +3,8 @@ import {invokeCallback, mixin} from "@/common.js"; import {chart, createShape, deleteShapeId, dragging, draggingShapeIds, drawShape, widget} from "@/charts/chart.js"; import {unique} from "@/misc.js"; -import {useChartOrderStore} from "@/orderbuild.js"; -import model from "color"; +import {allocationText, useChartOrderStore} from "@/orderbuild.js"; +import Color from "color"; // @@ -72,11 +72,20 @@ export class Shape { if (onDelete !== null ) this.onDelete = onDelete + // // Model values handled by this base class - this.model.color = null - this.model.lineColor = null - this.model.textColor = null + // + 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.amountSymbol = null + + // LEAF SUBCLASSES MUST CALL setModel(model) AFTER ALL CONSTRUCTION. } // @@ -84,14 +93,53 @@ export class Shape { // setModel(model) { - if (model.textColor || model.lineColor) { - if (model.textColor) - this.model.textColor = model.textColor - if (model.lineColor) - this.model.lineColor = model.lineColor - this.setProps(this.colorProps()) + console.log('shape setModel', model) + + if (model.color) + this.model.color = model.color + if (model.allocation !== null && model.allocation !== undefined) + this.model.allocation = model.allocation + if (model.maxAllocation !== null && model.maxAllocation !== undefined) + this.model.maxAllocation = model.maxAllocation + if (model.amount !== null && model.amount !== undefined) + this.model.amount = model.amount + if (model.amountSymbol) + this.model.amountSymbol = model.amountSymbol + + const newProps = {} + + // text color + const color = this.model.color ? this.model.color : '#0044ff' + newProps.textcolor = color + + // line color + if (this.model.allocation && 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() + } } - // todo text + else + newProps.linecolor = color + + // text label + const text = allocationText(this.model.allocation, this.model.amount, this.model.amountSymbol) + if (!text.length) + newProps.showLabel = false + else { + newProps.text = text + newProps.showLabel = true + } + + if (this.debug && this.id) + console.log('newProps', chart.getShapeById(this.id).getProperties(), newProps) + this.setProps(newProps) } onModel(model, changedKeys) {} // model was changed by a TradingView user action @@ -181,10 +229,19 @@ export class Shape { else if (dirtyPoints(this.tvPoints, points)) { const s = this.tvShape(); if (this.debug) console.log('adjusting tv points', s, this.tvPoints, points) - if (!this.beingDragged()) { + if (!dragging) { if (this.debug) console.log('not dragging. use setPoints.') s.setPoints(points) } + else { + if (this.debug) console.log('dragging. use QUIET setPoints.') + // quiet setPoints doesn't disturb tool editing mode + const i = s._pointsConverter.apiPointsToDataSource(points) + // s._model.startChangingLinetool(this._source) + s._model.changeLinePoints(s._source, i) + // s._model.endChangingLinetool(!0) + s._source.createServerPoints() + } } } } @@ -198,12 +255,17 @@ export class Shape { 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 - this.updateModel({lineColor:props.linecolor, textColor:props.textcolor}) + console.log('shape onProps', this.model, props) + if (props.textcolor && props.textcolor !== this.model.color) + this.updateModel({color:props.textcolor}) + else if (props.linecolor && props.linecolor !== this.model.color) + this.updateModel({color:props.linecolor}) } beingDragged() { @@ -261,14 +323,14 @@ function dirtyKeys(propsA, propsB) { if (propsA===null) return [...Object.keys(propsB)] const keys = unique([...Object.keys(propsA), ...Object.keys(propsB)]) - return keys.filter((k)=> !(k in propsA) || propsA[k] !== undefined && propsA[k] !== propsB[k]) + return keys.filter((k)=> propsB[k] !== undefined && (!(k in propsA) || propsA[k] !== propsB[k])) } function dirtyItems(a, b) { const result = {} - for (const k of dirtyKeys(this.tvProps, props)) - result[k] = props[k] + for (const k of dirtyKeys(a,b)) + result[k] = b[k] return result } @@ -294,7 +356,7 @@ class ShapeTVCallbacks { } onProps(shapeId, _tvShape, props) { - // if (this.shape.debug) console.log('tvOnProps', props) + if (this.shape.debug) console.log('tvOnProps', props) if (this.creating) { // todo still useful? this.creating = false return @@ -378,9 +440,12 @@ export class HLine extends Line { setModel(model) { - console.log('hline setModel', model) super.setModel(model) - if (model.price !== undefined && model.price !== this.model.price) { + 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}]) } @@ -416,6 +481,7 @@ export class VLine extends Line { // Model this.model.time = null + this.debug=true this.setModel(model) // call setModel at the end } @@ -427,23 +493,23 @@ export class VLine extends Line { setModel(model) { - if (this.debug) console.log('vline setModel', this.model.time, model ) super.setModel(model) - if (model.time !== undefined && model.time !== this.model.time) { + if (model.time === null) { + this.model.time = null + this.delete() + } + else { this.model.time = model.time const time = nearestOhlcStart(model.time); - if (this.debug) console.log('vline setPoints', this.id, time) this.setPoints([{time, price:0}]) } } onPoints(points) { - if (this.debug) console.log('vline onPoints', this.ourPoints, points) super.onPoints(points); const orig = this.ourPoints && this.ourPoints.length ? this.ourPoints[0].time : null const time = points[0].time; if (!timeAdjustmentTooSmall(orig, time)) { - if (this.debug) console.log('updateModel', time) this.updateModel({time: time}) } } diff --git a/src/components/chart/LimitBuilder.vue b/src/components/chart/LimitBuilder.vue index f3977c2..7d023c3 100644 --- a/src/components/chart/LimitBuilder.vue +++ b/src/components/chart/LimitBuilder.vue @@ -175,6 +175,7 @@ function getModelValue(model) { } function setModelValue(model, value) { + console.log('setModelValue->', {...model}, value) if (model.price !== value) model.price = value } diff --git a/src/components/chart/RungBuilder.vue b/src/components/chart/RungBuilder.vue index 826999e..1df897a 100644 --- a/src/components/chart/RungBuilder.vue +++ b/src/components/chart/RungBuilder.vue @@ -160,7 +160,7 @@ const color = computed({ set(v) { const maxLightness = 60 const c = new Color(v).hsl() - props.builder.color = c.saturation <= maxLightness ? v : c.lightness(maxLightness).string() + props.builder.color = c.saturation <= maxLightness ? v : c.lightness(maxLightness).rgb().string() } }) const colors = computed( ()=> { @@ -177,7 +177,7 @@ const colorStyle = computed(() => { function allocText(weight) { const alloc = props.builder.allocation if (alloc===null) return '' - return allocationText(props.order.amount, weight * alloc, amountSymbol.value); + return allocationText(weight * alloc, props.order.amount * alloc, amountSymbol.value); } @@ -214,11 +214,12 @@ function translateOnDrag(shape) { const shapeA = createShape(valueA.value, {color: defaultColor}, function (model) { + console.log('shapeA onModel', model) const value = props.getModelValue(model); if (value!==null && value!==undefined) valueA.value = value; if (model.color) - props.builder.color = model.color; + color.value = model.color }, deleteSelf) @@ -231,25 +232,21 @@ const shapeB = createShape(valueB.value, {color:defaultColor}, if (value!==null && value!==undefined) valueB.value = value; if (model.color) - props.builder.color = model.color; + color.value = model.color }, deleteSelf) function interiorOnModel(model) { - const v = model.textColor || model.lineColor || model.color - if (v) - color.value = v + if (model.color) + color.value = model.color } let interiorShapes = [] -function createInteriorShape(price, weight) { - const model = {text: allocText(weight), color: props.builder.color}; - const shape = createShape(price, model, interiorOnModel, deleteSelf) - shape.debug = true +function createInteriorShape(index) { + const shape = new props.shape(makeModel(index), interiorOnModel, deleteSelf) translateOnDrag(shape) interiorShapes.push(shape) - // shape.create() // should happen automatically when a model with valid points is set } @@ -260,6 +257,23 @@ function removeInteriorShape() { } } +function makeModel(index) { + const ws = weights.value + const w = ws[index] + const alloc = props.builder.allocation * ws[index]; + console.log('makeModel', index, ws, w, alloc) + const result = { + color: color.value, + allocation: alloc, + maxAllocation: Math.max(...weights.value), + amount: props.order.amount * alloc, + amountSymbol: amountSymbol.value, + } + props.setModelValue(result, values.value[index]) + console.log('made model', result) + return result +} + function adjustShapes() { // this is where all the shapes are created or adjusted console.log('adjustShapes()', valueA.value, valueB.value) @@ -280,13 +294,7 @@ function adjustShapes() { // // SINGLE SHAPE // - if (!shapeA.beingDragged()) { - const model = {text: allocText(ws[0]), color: colorStrings[0]}; - console.log('single shape A setModelValue', model, vs[0]) - props.setModelValue(model, vs[0]) - console.log('shapeA setModel', model, shapeA.id) - shapeA.setModel(model) - } + shapeA.setModel(makeModel(0)) shapeB.delete() if (interiorShapes.length) { for( const shape of interiorShapes ) @@ -298,19 +306,8 @@ function adjustShapes() { // // VALUE RANGE // - if (!shapeA.beingDragged()) { - const model = {text: allocText(ws[0]), color: colorStrings[0]}; - console.log('shape A not dragged setModelValue', model, vs[0]) - props.setModelValue(model, vs[0]) - shapeA.setModel(model) - } - if (!shapeB.beingDragged()) { - const last = colorStrings.length - 1 - const model = {text: allocText(ws[last]), color: colorStrings[last]}; - console.log('shape B not dragged setModelValue', model, vs[last]) - props.setModelValue(model, vs[last]) - shapeB.setModel(model) - } + shapeA.setModel(makeModel(0)) + shapeB.setModel(makeModel(vs.length-1)) const numInterior = Math.max(0,vs.length-2); // trim excess interior shapes while( interiorShapes.length > numInterior ) @@ -320,13 +317,9 @@ function adjustShapes() { const v = vs[i] const w = ws[i]; if (i-1 === interiorShapes.length) - createInteriorShape(v, w) - else if (!interiorShapes[i-1].beingDragged()) { - const model = {text: allocText(w), color: colorStrings[i]}; - console.log('interior setModelValue', model, v) - props.setModelValue(model, v) - interiorShapes[i-1].setModel(model) - } + createInteriorShape(i) + else + interiorShapes[i-1].setModel(makeModel(i)) } } return '' diff --git a/src/misc.js b/src/misc.js index d7e1cee..3402bec 100644 --- a/src/misc.js +++ b/src/misc.js @@ -178,9 +178,9 @@ export function uuid() { } export function lightenColor(color, lightness = 85, alpha = null) { - let c = new Color(color).hsl().lightness(lightness) + let c = new Color(color).hsl().lightness(lightness).rgb() if (alpha !== null) - c = c.rgb().alpha(alpha) + c = c.alpha(alpha) return c.string() } diff --git a/src/orderbuild.js b/src/orderbuild.js index 033d096..7728c3e 100644 --- a/src/orderbuild.js +++ b/src/orderbuild.js @@ -267,10 +267,19 @@ export function weightColors(weights, color) { return adj.map((a) => a.string()) } -export function allocationText(amount, weight, symbol) { - // console.log('weight', weight, alloc, props.amount) - const a = amount * weight - return `${(weight * 100).toFixed(1)}% = ${a.toLocaleString('fullwide')} ${symbol}` +export function allocationText(weight, amount, symbol) { + let text = '' + const hasWeight = weight!==null && weight!==undefined + if (hasWeight) + text += `${(weight * 100).toFixed(1)}%` + const hasAmount = amount!==null && amount!==undefined + const hasSymbol = symbol!==null && symbol!==undefined + if (hasAmount && hasSymbol) { + if (hasWeight) + text += ' = ' + text += `${amount.toLocaleString('fullwide')} ${symbol}` + } + return text } export function deleteBuilder(order, builder) {