import Widget from "ui/core/widget";
import Constraint from "util/constraint";
import ConstraintSpec from "util/constraint-def";
import MathUtil from "util/util-math";
/**
* Class representing a Graph widget.
* @class
* @implements {Widget}
*/
class Graph extends Widget {
/**
* @constructor
* @param {object} container - DOM container for the widget.
* @param {object} [o] - Options.
* @param {number} [o.minXVal=0] - Minimum X value.
* @param {number} [o.minYVal=0] - Minimum Y value.
* @param {number} [o.maxXVal=100] - Maximum X value.
* @param {number} [o.maxYVal=100] - Maximum Y value.
* @param {number} [o.maxNumVertices=-1] - Maximum number of vertices.
* @param {number} [o.quantizeX=0.1] - X-quantization ("grid") value.
* @param {number} [o.quantizeY=0.1] - Y-quantization ("grid") value.
* @param {number} [o.xDecimalPrecision=1] - Number of decimal places for output of the X values.
* @param {number} [o.yDecimalPrecision=1] - Number of decimal places for output of the Y values.
* @param {boolean} [o.isEditable=true] - Is the graph editable?
* @param {string} [o.vertexColor="#f40"] - Color of vertex points.
* @param {string} [o.lineColor="#484848"] - Color of lines connecting the vertices.
* @param {string} [o.backgroundColor="#fff"] - Background color.
* @param {number} [o.lineWidth=2] - Width of the connecting lines.
* @param {number} [o.vertexRadius=4] - Radius of the vertex points.
* @param {number} [o.mouseSensitivity=1.2] - Mouse sensitivity (how much moving the mouse affects the interaction).
*/
constructor(container, o) {
super(container, o);
return this;
}
/* ===========================================================================
* PUBLIC API
*/
/**
* Sets the options.
* @public @override
*/
setOptions(o) {
o = o || {};
super.setOptions(o);
}
/**
* Returns the state as an array of [x, y] pairs.
* @public @override
*/
getVal() {
return this.state.vertices.map(vtx => [vtx.x, vtx.y]);
}
/**
* Sets the state as an array of [x, y] vertex pairs.
* Same as setVal(), but will not trigger observer callback methods.
* @public @override
* @param {array} - An array of [x, y] points
*/
setInternalVal(vertexArray) {
let vertices = vertexArray.map(xyPair => { return {x: xyPair[0], y: xyPair[1]}; });
this.setInternalState({ vertices: vertices });
}
/**
* Sets the state as an array of [x, y] vertex pairs.
* Same as setInternalVal(), but will trigger observer callback methods.
* @public @override
* @param {array} - An array of [x, y] points.
*/
setVal(vertexArray) {
let vertices = vertexArray.map(xyPair => { return {x: xyPair[0], y: xyPair[1]}; });
this.setState({ vertices: vertices });
}
/**
* Sets the value of a particular vertex, selected by its index.
* Note: will not trigger observer notifications.
* @public
* @param {number} val - Value to set.
* @param {number} idx - Index of the vertex to set the value for.
* @returns {number} - Index of the vertex that has been set, or -1 if no such vertex exists.
*/
setVertexVal(val, idx) {
if (idx >= 0 && idx < this.state.vertices.length) {
let vertices = this.state.vertices.map(vtx => vtx);
vertices[idx].y = val;
this.setInternalState({ vertices: vertices });
return idx;
} else {
return -1;
}
}
/**
* Returns the number of vertices set on this graph.
* @public
* @return {number} - Number of vertices.
*/
getNumVertices() {
return this.state.vertices.length;
}
/**
* Adds new vertices to the state.
* Each vertex is represented as x and y values, as well as optional boolean flags
* specifying whether the x, y, or both values should be fixed (unchangeble).
* The x and y values may also take the strings "min", "max" to specify that the coordinates
* should be tied to the minimum or maximum possible x or y values for the graph.
* @public
* @param {...object} vtx - Object representing the new vertex to add.
* @param {number} [vtx.x=minXVal] - X coordinate for the new vertex.
* @param {number} [vtx.y=minYVal] - Y coordinate for the new vertex.
* @param {boolean} [vtx.isXFixed=false] - Is the X coordinate fixed (unable to move)?
* @param {boolean} [vtx.isYFixed=false] - Is the Y coordinate fixed (unable to move)?
*/
addVertex(...vtx) {
for (let i = 0; i < vtx.length; i++) {
let newVtx = vtx[i];
newVtx = (typeof newVtx !== 'undefined') ? newVtx : {};
newVtx.x = (typeof newVtx.x !== 'undefined') ? newVtx.x : this.o.minXVal;
newVtx.y = (typeof newVtx.y !== 'undefined') ? newVtx.y : this.o.minYVal;
newVtx.isXFixed = (typeof newVtx.isXFixed !== 'undefined') ? newVtx.isXFixed : false;
newVtx.isYFixed = (typeof newVtx.isYFixed !== 'undefined') ? newVtx.isYFixed : false;
newVtx.xAnchor = "";
newVtx.yAnchor = "";
if (newVtx.x === "max") {
newVtx.isXFixed = true;
newVtx.x = this.o.maxXVal;
newVtx.xAnchor = "max";
} else if (newVtx.x === "min") {
newVtx.isXFixed = true;
newVtx.x = this.o.minXVal;
newVtx.xAnchor = "min";
}
if (newVtx.y === "max") {
newVtx.isYFixed = true;
newVtx.y = this.o.maxYVal;
newVtx.yAnchor = "max";
} else if (newVtx.x === "min") {
newVtx.isYFixed = true;
newVtx.y = this.o.minYVal;
newVtx.yAnchor = "min";
}
let newVertices = this.getState().vertices.map(x=>x);
newVertices.push(newVtx);
newVertices.sort((a, b) => a.x - b.x);
this.setState({
vertices: newVertices
});
}
}
/**
* Adds a vertex with fixed x and y coordinates.
* @param {object} vtx - Vertex coordinates in format {x, y}
* @param {number} vtx.x - X coordinate of the vertex.
* @param {number} vtx.y - Y coordinate of the vertex.
*/
addFixedVertex(...vtx) {
for (let i = 0; i < vtx.length; i++) {
let newVtx = vtx[i];
this.addVertex({ x: newVtx.x, y: newVtx.y, isXFixed: true, isYFixed: true });
}
}
/**
* Adds a vertex with fixed y coordinate.
* @param {object} vtx - Vertex coordinates in format {x, y}
* @param {number} vtx.x - X coordinate of the vertex.
* @param {number} vtx.y - Y coordinate of the vertex.
*/
addFixedXVertex(...vtx) {
for (let i = 0; i < vtx.length; i++) {
let newVtx = vtx[i];
this.addVertex({ x: newVtx.x, y: newVtx.y, isXFixed: true, isYFixed: false });
}
}
/**
* Adds a vertex with fixed y coordinate.
* @param {object} vtx - Vertex coordinates in format {x, y}
* @param {number} vtx.x - X coordinate of the vertex.
* @param {number} vtx.y - Y coordinate of the vertex.
*/
addFixedYVertex(...vtx) {
for (let i = 0; i < vtx.length; i++) {
let newVtx = vtx[i];
this.addVertex({ x: newVtx.x, y: newVtx.y, isXFixed: false, isYFixed: true });
}
}
/* ============================================================================================= */
/* INITIALIZATION METHODS
/* ============================================================================================= */
/**
* Initializes the options.
* @override
* @private
*/
_initOptions(o) {
// set defaults
this.o = {};
this.o.minXVal = 0;
this.o.minYVal = 0;
this.o.maxXVal = 100;
this.o.maxYVal = 100;
this.o.maxNumVertices = -1;
this.o.quantizeX = 0.1;
this.o.quantizeY = 0.1;
this.o.xDecimalPrecision = 1;
this.o.yDecimalPrecision = 1;
this.o.isEditable = true;
this.o.vertexColor = "#f40";
this.o.fixedVertexColor = this.o.vertexColor;
this.o.lineColor = "#484848";
this.o.backgroundColor = "#fff";
this.o.vertexRadius = 4;
this.o.lineWidth = 2;
this.o.mouseSensitivity = 1.2;
// override defaults with provided options
super._initOptions(o);
}
/**
* Initializes state constraints.
* @override
* @private
*/
_initStateConstraints() {
const _this = this;
this.stateConstraints = new ConstraintSpec({
vertices: [{
x: new Constraint({
min: _this.o.minXVal,
max: _this.o.maxXVal,
transform: (num) => {
return MathUtil.quantize(num, _this.o.quantizeX);
}
}),
y: new Constraint({
min: _this.o.minYVal,
max: _this.o.maxYVal,
transform: (num) => {
return MathUtil.quantize(num, _this.o.quantizeY);
}
})
}]
});
}
/**
* Initializes state.
* @override
* @private
*/
_initState() {
this.state = {
// verices contains an array of vertices
// each vertex is an object of form
// {
// x: numbber,
// y: number,
// isXFixed: boolean,
// isYFixed: boolean,
// xAnchor: string,
// yAnchor: string
// }
// isXFixed and isYFixed are boolean values that tell if a given
// vertex may be moved in the x and y planes
vertices: []
};
}
/**
* Initializes the svg elements.
* @override
* @private
*/
_initSvgEls() {
const _this = this;
this.svgEls = {
panel: document.createElementNS(this.SVG_NS, "rect"),
vertices: [],
lines: []
};
this.svgEls.panel.setAttribute("width", this._getWidth());
this.svgEls.panel.setAttribute("height", this._getHeight());
this.svgEls.panel.setAttribute("fill", this.o.backgroundColor);
this.svgEls.panel.setAttribute("stroke", this.o.lineColor);
this._appendSvgEls();
this._update();
}
/**
* Initializes mouse and touch event handlers.
* @override
* @private
*/
_initHandlers() {
const _this = this;
let targetVtx = null;
let targetLine = null;
let vtxPos0 = {}; // original poisition of two vertices affected by a line move
let x0 = 0;
let y0 = 0;
let x1 = 0;
let y1 = 0;
let dx = 0;
let dy = 0;
this.handlers = {
touchPanel: function touchPanel(ev) {
ev.preventDefault();
let xPos = ev.clientX - _this._getLeft();
let yPos = ev.clientY - _this._getTop();
let vertexState = _this._calcVertexState({x: xPos, y: yPos});
_this.addVertex(vertexState);
},
touchVertex: function touchVertex(ev) {
ev.preventDefault();
targetVtx = ev.target;
document.addEventListener("mousemove", _this.handlers.moveVertex);
document.addEventListener("touchmove", _this.handlers.moveVertex);
ev.target.addEventListener("mouseup", _this.handlers.deleteVertex);
ev.target.addEventListener("touchend", _this.handlers.deleteVertex);
},
touchLine: function touchLine(ev) {
ev.preventDefault();
targetLine = ev.target;
x0 = ev.clientX - _this._getLeft();
y0 = ev.clientY - _this._getTop();
vtxPos0 = null;
document.addEventListener("mousemove", _this.handlers.moveLine);
document.addEventListener("touchmove", _this.handlers.moveLine);
},
moveLine: function moveLine(ev) {
ev.preventDefault();
document.addEventListener("mouseup", _this.handlers.endMoveLine);
document.addEventListener("touchend", _this.handlers.endMoveLine);
x1 = ev.clientX - _this._getLeft();
y1 = ev.clientY - _this._getTop();
// delta position (change in position)
let dPos = {
x: x1 - x0,
y: y1 - y0
};
vtxPos0 = _this._moveLine(targetLine, dPos, vtxPos0);
},
endMoveLine: function endMoveLine(ev) {
ev.preventDefault();
vtxPos0 = null;
document.removeEventListener("mousemove", _this.handlers.moveLine);
document.removeEventListener("touchmove", _this.handlers.moveLine);
},
deleteVertex: function deleteVertex(ev) {
ev.preventDefault();
document.removeEventListener("mousemove", _this.handlers.moveVertex);
document.removeEventListener("touchmove", _this.handlers.moveVertex);
_this._deleteVertex(ev.target);
ev.target.removeEventListener("mouseup", _this.handlers.deleteVertex);
ev.target.removeEventListener("touchend", _this.handlers.deleteVertex);
},
moveVertex: function moveVertex(ev) {
ev.preventDefault();
targetVtx.removeEventListener("mouseup", _this.handlers.deleteVertex);
targetVtx.removeEventListener("touchend", _this.handlers.deleteVertex);
document.addEventListener("mouseup", _this.handlers.endMoveVertex);
document.addEventListener("touchend", _this.handlers.endMoveVertex);
let xPos = ev.clientX - _this._getLeft();
let yPos = ev.clientY - _this._getTop();
_this._moveVertex(targetVtx, {x: xPos, y: yPos});
},
endMoveVertex: function endMoveVertex(ev) {
ev.preventDefault();
document.removeEventListener("mousemove", _this.handlers.moveVertex);
document.removeEventListener("touchmove", _this.handlers.moveVertex);
}
};
this.svgEls.panel.addEventListener("mousedown", _this.handlers.touchPanel);
this.svgEls.panel.addEventListener("touchdown", _this.handlers.touchPanel);
this.svgEls.vertices.forEach(vtx => {
vtx.addEventListener("mousedown", _this.handlers.touchVertex);
vtx.addEventListener("touchdown", _this.handlers.touchVertex);
});
this.svgEls.lines.forEach(line => {
line.addEventListener("mousedown", _this.handlers.touchLine);
line.addEventListener("touchdown", _this.handlers.touchLine);
});
}
/**
* Updates (redraws) component based on state.
* @override
* @private
*/
_update() {
const _this = this;
// update vertices to have min and max values if specified
_this.state.vertices.forEach(vtx => {
vtx.x = (vtx.xAnchor === "max") ? _this.o.maxXVal :
(vtx.xAnchor === "min") ? _this.o.minXVal :
vtx.x;
vtx.y = (vtx.yAnchor === "max") ? _this.o.maxYVal :
(vtx.yAnchor === "min") ? _this.o.minYVal :
vtx.y;
});
// sort svg vertexes using a sort map
let idxSortMap = _this.state.vertices.map((vtx, idx) => { return { vtx: vtx, idx: idx }; });
idxSortMap.sort((a, b) => a.vtx.x - b.vtx.x);
_this.state.vertices = idxSortMap.map(el => _this.state.vertices[el.idx]);
// if there are more state vertices than svg vertices, add a corresponding number of svg vertices and lines
for (let i = _this.svgEls.vertices.length; i < _this.state.vertices.length; ++i) {
_this._addSvgVertex();
}
// if there are more svg vertices than state vertices, remove a corresponding number of svg vertices and lines
for (let i = _this.svgEls.vertices.length; i > _this.state.vertices.length; --i) {
_this._removeSvgVertex();
}
// sort the svg vertices according to the vertex sort map
_this.svgEls.vertices = idxSortMap.map(el => _this.svgEls.vertices[el.idx]);
// set the correct position coordinates for every vertex
_this.state.vertices.forEach((stateVtx, idx) => {
let svgVtx = _this.svgEls.vertices[idx];
let pos = _this._calcVertexPos(stateVtx);
svgVtx.setAttribute("cx", pos.x);
svgVtx.setAttribute("cy", pos.y);
svgVtx.setAttribute("r", _this.o.vertexRadius);
let vtxFill = (_this.state.vertices[idx].isXFixed || _this.state.vertices[idx].isYFixed) ?
_this.o.fixedVertexColor :
_this.o.vertexColor;
svgVtx.setAttribute("fill", vtxFill);
// for every vertex other than the first, draw a line to the previous vertex
if (idx > 0) {
let prevVtx = _this.state.vertices[idx - 1];
let prevPos = _this._calcVertexPos(prevVtx);
let line = _this.svgEls.lines[idx - 1];
line.setAttribute("d", "M " + pos.x + " " + pos.y + " L " + prevPos.x + " " + prevPos.y);
line.setAttribute("fill", "transparent");
line.setAttribute("stroke-width", _this.o.lineWidth);
line.setAttribute("stroke", _this.o.lineColor);
}
});
// remove and reappend all svg elements so that vertices are on top of lines
_this.svgEls.lines.forEach(svgLine => {
_this.svg.removeChild(svgLine);
_this.svg.appendChild(svgLine);
});
_this.svgEls.vertices.forEach(svgVtx => {
_this.svg.removeChild(svgVtx);
_this.svg.appendChild(svgVtx);
});
// reassign listeners
_this.svgEls.vertices.forEach(vtx => {
vtx.addEventListener("mousedown", _this.handlers.touchVertex);
vtx.addEventListener("touchdown", _this.handlers.touchVertex);
});
_this.svgEls.lines.forEach(line => {
line.addEventListener("mousedown", _this.handlers.touchLine);
line.addEventListener("touchdown", _this.handlers.touchLine);
});
}
/* ==============================================================================================
* INTERNAL FUNCTIONALITY METHODS
*/
/**
* Deletes a vertex.
* @private
* @param {SVGElement} targetVtx - Vertex to Delete
*/
_deleteVertex(targetVtx) {
const _this = this;
let vtxIdx = this.svgEls.vertices.findIndex(vtx => vtx === targetVtx);
let isRemovable = !(this.state.vertices[vtxIdx].isXFixed || this.state.vertices[vtxIdx].isYFixed)
if (vtxIdx !== -1 && isRemovable) {
let newVertices = this.getState().vertices.map(x=>x);
newVertices.splice(vtxIdx, 1);
_this.setState({
vertices: newVertices
});
}
}
/**
* Adds a new SVG vertex representation.
* @private
*/
_addSvgVertex() {
const _this = this;
let newVertex = document.createElementNS(_this.SVG_NS, "circle");
_this.svgEls.vertices.push(newVertex);
_this.svg.appendChild(newVertex);
// if there is more than 1 svg vertex, we also need to draw lines between them
if (_this.svgEls.vertices.length > 1) {
this._addSvgLine();
}
}
/**
* Adds an SVG line connecting two vertices.
* @private
*/
_addSvgLine() {
let newLine = document.createElementNS(this.SVG_NS, "path");
this.svg.appendChild(newLine);
this.svgEls.lines.push(newLine);
}
/**
* Removes an SVG vertex.
* @private
*/
_removeSvgVertex() {
let vertex = this.svgEls.vertices[this.svgEls.vertices.length - 1];
this.svg.removeChild(vertex);
vertex = null;
this.svgEls.vertices.pop();
if (this.svgEls.lines.length > 0) {
this._removeSvgLine();
}
}
/**
* Removes an SVG line connecting two vertices.
* @private
*/
_removeSvgLine() {
let line = this.svgEls.lines[this.svgEls.lines.length - 1];
this.svg.removeChild(line);
line = null;
this.svgEls.lines.pop();
}
/**
* Moves a line.
* @private
* @param {SVGElement} targetLine - The target line
* @param {object} dPos -
* @param {number} dPos.x
* @param {number} dPos.y
* @param {object} vtxPos0 - Original position (before moving)
* of the two vertices immediately to the left
* and right of the line being moved in the
* form { vtx1: {x, y}, vtx2: {x, y}, boundaryBL: {x, y}, boundaryTR: {x, y} }
* If null, will be calculated from the
* corresponding svg element.
* @param {obect} [vtxPos0.vtx1]
* @param {number} [vtxPos0.vtx1.x]
* @param {number} [vtxPos0.vtx1.y]
* @param {obect} [vtxPos0.vtx2]
* @param {number} [vtxPos0.vtx2.x]
* @param {number} [vtxPos0.vtx2.y]
* @returns {object} Original position of the two vertices affected by the line move in the form
* { vtx1: {x, y}, vtx2: {x, y}, boundaryBL: {x, y}, boundaryTR: {x, y} }.
*/
_moveLine(targetLine, dPos, vtxPos0) {
const _this = this;
let lineIdx = _this.svgEls.lines.findIndex(line => line === targetLine);
// get vertices to the left and right of the selected line
let vtx1 = _this.svgEls.vertices[lineIdx];
let vtx2 = _this.svgEls.vertices[lineIdx + 1];
let vtx1curPos = {
x: parseInt(_this.svgEls.vertices[lineIdx].getAttribute("cx")),
y: parseInt(_this.svgEls.vertices[lineIdx].getAttribute("cy"))
};
let vtx2curPos = {
x: parseInt(_this.svgEls.vertices[lineIdx + 1].getAttribute("cx")),
y: parseInt(_this.svgEls.vertices[lineIdx + 1].getAttribute("cy"))
};
// if vtxPos0 is null or undefined, this is the first time this function
// was called in the line move, and we need to get the position of affected
// vertices from the svg attributes.
// vtx1 and vtx2 are the left and right vertices bounding the line
// boundaryBL is the bottom left boundary restricting movement of the line
// boundaryTR is the top right boundary restricting movement of the line
if (vtxPos0 === null || vtxPos0 === undefined) {
let boundaryBL = {
x: (lineIdx > 0) ?
parseInt(_this.svgEls.vertices[lineIdx - 1].getAttribute("cx"))
: _this._calcVertexPos({x: _this.o.minXVal, y: _this.o.minYVal}).x,
y: _this._calcVertexPos({x: _this.o.minXVal, y: _this.o.minYVal}).y
};
let boundaryTR = {
x: (lineIdx + 2 < _this.svgEls.vertices.length) ?
parseInt(_this.svgEls.vertices[lineIdx + 2].getAttribute("cx"))
: _this._calcVertexPos({x: _this.o.maxXVal, y: _this.o.maxYVal}).x,
y: _this._calcVertexPos({x: _this.o.maxXVal, y: _this.o.maxYVal}).y
};
vtxPos0 = {
vtx1: vtx1curPos,
vtx2: vtx2curPos,
boundaryBL: boundaryBL,
boundaryTR: boundaryTR
};
}
// calculate the new positions for the two affected vertices
let vtx1newPos = {
x: vtxPos0.vtx1.x + dPos.x,
y: vtxPos0.vtx1.y + dPos.y
};
let vtx2newPos = {
x: vtxPos0.vtx2.x + dPos.x,
y: vtxPos0.vtx2.y + dPos.y
};
// if moving would take x values outside of boundaries, keep x values the same
if (vtx1newPos.x < vtxPos0.boundaryBL.x ||
vtx2newPos.x < vtxPos0.boundaryBL.x ||
vtx1newPos.x > vtxPos0.boundaryTR.x ||
vtx2newPos.x > vtxPos0.boundaryTR.x) {
vtx1newPos.x = vtx1curPos.x;
vtx2newPos.x = vtx2curPos.x;
}
// if moving would take y values outside of boundaries, keep y values the same
// remember that y-coordinates are inverted when dealing with the canvas
if (vtx1newPos.y > vtxPos0.boundaryBL.y ||
vtx2newPos.y > vtxPos0.boundaryBL.y ||
vtx1newPos.y < vtxPos0.boundaryTR.y ||
vtx2newPos.y < vtxPos0.boundaryTR.y) {
vtx1newPos.y = vtx1curPos.y;
vtx2newPos.y = vtx2curPos.y;
}
this._moveVertex(vtx1, vtx1newPos);
this._moveVertex(vtx2, vtx2newPos);
// return the original position so that it may be used again if the line move is not finished
return vtxPos0;
}
/**
* Moves a vertex.
* @private
* @param {SVGElement} targetVtx - The target vertex
* @param {Object} newPos - The new position
* @param {number} newPos.x
* @param {number} newPos.y
*/
_moveVertex(targetVtx, newPos) {
const _this = this;
let vtxState = _this._calcVertexState(newPos);
let vtxIdx = _this.svgEls.vertices.findIndex(vtx => vtx === targetVtx);
let vertices = _this.getState().vertices.map(x=>x);
// move the vertex if it's not fixed in x or y direction
vertices[vtxIdx].x = (vertices[vtxIdx].isXFixed) ? vertices[vtxIdx].x : vtxState.x;
vertices[vtxIdx].y = (vertices[vtxIdx].isYFixed) ? vertices[vtxIdx].y : vtxState.y;
_this.setState({
vertices: vertices
});
}
/* ===========================================================================
* HELPER METHODS
*/
/**
* Calculates the x and y for a vertex in the graph according to its state value.
* @private
*/
_calcVertexPos(vertexState) {
let normalizedWidth = this._getWidth() - (2 * this.o.vertexRadius);
let normalizedHeight = this._getHeight() - (2 * this.o.vertexRadius);
return {
x: (normalizedWidth * (vertexState.x / this.o.maxXVal)) + this.o.vertexRadius,
y: (normalizedHeight - (normalizedHeight * (vertexState.y / this.o.maxYVal))) + this.o.vertexRadius
};
}
/**
* Calculates the x and y for a vertex state based on position on the graph.
* (inverse of _calcVertexPos).
* @private
*/
_calcVertexState(vertexPos) {
let normalizedWidth = this._getWidth() - (2 * this.o.vertexRadius);
let normalizedHeight = this._getHeight() - (2 * this.o.vertexRadius);
return {
x: this.o.maxXVal * ((vertexPos.x - this.o.vertexRadius) / normalizedWidth),
y: this.o.maxYVal - (this.o.maxYVal * (vertexPos.y / normalizedHeight))
};
}
/**
* Converts on-screen x distance to scaled x state-value.
* @private
*/
_xPxToVal(x) {
return ((x - this.o.vertexRadius) / (this._getWidth() + (2 * this.o.vertexRadius)))
* (this.o.maxXVal - this.o.minXVal);
}
/**
* Converts on-screen y distance to scaled y state-value.
* @private
*/
_yPxToVal(y) {
return (y / (this._getHeight() + (2 * this.o.vertexRadius))) * (this.o.maxYVal - this.o.minYVal);
}
}
export default Graph;