gRaphael linechart draws values beyond axis - javascript

When drawing a linechart with gRaphael using milliseconds along the x-axis I commonly get inconsistencies in the placement of the data points. Most commonly the initial data points are to the left of the y-axis (as seen in the fiddle below), sometimes the last data-point will be beyond the right side of the view-box/past the termination of the x-axis.
Does anyone know:
1) Why this occurs,
2) How to prevent it, &/or
3) How to check for it (I can use transform to move the lines/points if I know when it has happened/by how much).
my code:
var r = Raphael("holder"),
txtattr = { font: "12px sans-serif" };
var r2 = Raphael("holder2"),
txtattr2 = { font: "12px sans-serif" };
var x = [], y = [], y2 = [], y3 = [];
for (var i = 0; i < 1e6; i++) {
x[i] = i * 10;
y[i] = (y[i - 1] || 0) + (Math.random() * 7) - 3;
}
var demoX = [[1, 2, 3, 4, 5, 6, 7],[3.5, 4.5, 5.5, 6.5, 7, 8]];
var demoY = [[12, 32, 23, 15, 17, 27, 22], [10, 20, 30, 25, 15, 28]];
var xVals = [1288885800000, 1289929440000, 1290094500000, 1290439560000, 1300721700000, 1359499228000, 1359499308000, 1359499372000];
var yVals = [80, 76, 70, 74, 74, 78, 77, 72];
var xVals2 = [1288885800000, 1289929440000];
var yVals2 = [80, 76];
var lines = r.linechart(10, 10, 300, 220, xVals, yVals, { nostroke: false, axis: "0 0 1 1", symbol: "circle", smooth: true })
.hoverColumn(function () {
this.tags = r.set();
for (var i = 0, ii = this.y.length; i < ii; i++) {
this.tags.push(r.tag(this.x, this.y[i], this.values[i], 160, 10).insertBefore(this).attr([{ fill: "#fff" }, { fill: this.symbols[i].attr("fill") }]));
}
}, function () {
this.tags && this.tags.remove();
});
lines.symbols.attr({ r: 3 });
var lines2 = r2.linechart(10, 10, 300, 220, xVals2, yVals2, { nostroke: false, axis: "0 0 1 1", symbol: "circle", smooth: true })
.hoverColumn(function () {
this.tags = r2.set();
for (var i = 0, ii = this.y.length; i < ii; i++) {
this.tags.push(r.tag(this.x, this.y[i], this.values[i], 160, 10).insertBefore(this).attr([{ fill: "#fff" }, { fill: this.symbols[i].attr("fill") }]));
}
}, function () {
this.tags && this.tags.remove();
});
lines2.symbols.attr({ r: 3 });
I do have to use gRaphael and the x-axis has to be in milliseconds (it is labeled later w/customized date strings)
Primary example fiddle: http://jsfiddle.net/kcar/aNJxf/
Secondary example fiddle (4th example on page frequently shows both axis errors):
http://jsfiddle.net/kcar/saBnT/
root cause is the snapEnds function (line 718 g.raphael.js), the rounding it does, while fine in some cases, is adding or subtracting years from/to the date in other cases.
Haven't stepped all the way through after this point, but since the datapoints are misplaced every time the rounding gets crazy and not when it doesn't, I'm going to go ahead and assume this is causing issues with calculating the chart columns, also before being sent to snapEnds the values are spot on just to confirm its not just receiving miscalculated data.
code of that function from g.raphael.js
snapEnds: function(from, to, steps) {
var f = from,
t = to;
if (f == t) {
return {from: f, to: t, power: 0};
}
function round(a) {
return Math.abs(a - .5) < .25 ? ~~(a) + .5 : Math.round(a);
}
var d = (t - f) / steps,
r = ~~(d),
R = r,
i = 0;
if (r) {
while (R) {
i--;
R = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
}
i ++;
} else {
if(d == 0 || !isFinite(d)) {
i = 1;
} else {
while (!r) {
i = i || 1;
r = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
i++;
}
}
i && i--;
}
t = round(to * Math.pow(10, i)) / Math.pow(10, i);
if (t < to) {
t = round((to + .5) * Math.pow(10, i)) / Math.pow(10, i);
}
f = round((from - (i > 0 ? 0 : .5)) * Math.pow(10, i)) / Math.pow(10, i);
return { from: f, to: t, power: i };
},

removed the rounding nonsense from snapEnds and no more issues, not noticed any downside from either axis or any other area of the chart. If you see one I'd love to hear it though.
code of that function from g.raphael.js now:
snapEnds: function(from, to, steps) {
return {from: from, to: to, power: 0};
},

Hi if you comment this:
if (valuesy[i].length > width - 2 * gutter) {
valuesy[i] = shrink(valuesy[i], width - 2 * gutter);
len = width - 2 * gutter;
}
if (valuesx[i] && valuesx[i].length > width - 2 * gutter) {
valuesx[i] = shrink(valuesx[i], width - 2 * gutter);
}
in g.line.js, It seems to solve the problem, and it also solves a similar problem with the values in the y axis.

Upgrading from v0.50 to v0.51 fixed the issue for me.

Still not sure why it occurs, adding in a transparent set was not a desirable option.
The simplest way to check for if the datapoints were rendered outside of the graph seems to be getting a bounding box for the axis set and a bounding box for the datapoints and checking the difference between the x and x2 values.
If anyone can help me with scaling the datapoint set, or figure out how to make this not happen at all, I will still happily appreciate/up vote answers
//assuming datapoints is the Raphael Set for the datapoints, axes is the
//Raphael Set for the axis, and datalines is the Raphael Set for the
//datapoint lines
var pointsBBox = datapoints.getBBox();
var axesBBox = axes.getBBox();
var xGapLeft = Math.ceil(axesBBox.x - pointsBBox.x);
//rounding up to integer to simplify, and the extra boost from y-axis doesn't
//hurt, <1 is a negligible distance in transform
var xGapRight = Math.ceil(axesBBox.x2 - pointsBBox.x2);
var xGap = 0;
if(xGapLeft > 0){
datapoints.transform('t' +xGapLeft +',0');
datalines.transform('t' +xGapLeft +',0');
xGap = xGapLeft;
}else if (xGapRight < 0) { //using else if because if it is a scale issue it will
//be too far right & too far left, meaning both are true and using transform will
//just shift it right then left and you are worse off than before, using
//set.transform(scale) works great on dataline but when using on datapoints scales
// symbol radius not placement
if (xGapLeft < 0 && xGapRight < xGapLeft) { xGapRight = xGapLeft; }
//in this case the initial point is right of y-axis, the end point is right of
//x-axis termination, and the difference between last point/axis is greater than
//difference between first point/axis
datapoints.transform('t' +xGapRight +',0');
datalines.transform('t' +xGapRight +',0');
xGap = xGapRight;
}
rehookHoverOverEvent(xGap); //there are so many ways to do this just leaving it
//here as a call to do so, if you don't the hover tags will be over the original
//datapoints instead of the new location, at least they were in my case.

Related

how can i create a segmented colored line using lightning chart js? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
Improve this question
Can anyone draw a segment line using this library?
I tried using addsegmentseries method but it didn't work.
Found the answer myself.
A segment can be created using addSegmentSeries() method.
Segment coordinates can be passed using add() method.
/**Segment coordinates */
const config = {
startX: 0,
startY: 60,
endX: 20,
endY: 60
};
/**Line style */
const style = new SolidLine(
{ thickness: 2, fillStyle: new SolidFill({ color: ColorRGBA(0, 125, 0) }) })
let series = chart
.addSegmentSeries()
.add(config)
.setStrokeStyle(style)
enter code here
One way to draw a segmented line is to compose single sub-segments together.
Here is an example code:
/**Segment division in many sub segments
* #param segment to split. E.C: { startX: 0, startY: 60, endX: 20, endY: 60 }
* #param min minimal value to use for segmented line begin
* #param max maximum value to use for segmented line ending
* #param offsetPx sub segments lenght */
function getSubSegments(segment, min, max, offsetPx) {
const range = segment != null ? segment.endX - segment.startX : -1;
if (range === -1) { return; }
const dividedSegments = [];
min = min <= segment.startX ? min : segment.startX - 1000;
max = max >= segment.endX ? max : segment.endX + 1000;
let offset = min + offsetPx;
while (offset <= max) {
dividedSegments.push({
startX: dividedSegments.length > 0 ? dividedSegments[dividedSegments.length - 1].endX : min,
startY: segment.startY,
endX: offset,
endY: segment.endY
});
offset += offsetPx;
}
return dividedSegments;
}
/**Function to draw segments on chart
* #param chart which will draw segments
* #param subSegments to draw on chart
* #param customStrokeStyle to apply to the line
*/
function drawSegmentedLine(chart, subSegments, customStrokeStyle) {
const lineSeriesObjs = [];
let index = -1;
let series = null;
for (let i = 0; i < subSegments.length - 1; i++) {
index = i;
if (i % 2 === 0) {
let series = chart
.addSegmentSeries()
.add(subSegments[i])
.setStrokeStyle(customStrokeStyle)
}
}
}
drawSegmentedLine(
chart,
getSubSegments({ startX: 0, startY: 60, endX: 100, endY: 60 }, -1000, 1000, 5),
new SolidLine({ fillStyle: new SolidFill({ color: ColorRGBA(0, 220, 0) }), thickness: 2 })
)

Are complex functions plots possible in jsx graph?

Suppose one attempts to plot the complex valued function $f:\mathhbb{C} \to \mathhbb{C}$ as $f(z) =z$ in jsx graph. It may not be complicated as it appears. What one needs is two connected planes. The point (x, y) in domain planr gets mapped to the point (x, y) in codomain plane. As one drags point in domain plane, corresponding changes takes place in the point in co domain plane. So the only question is how to connect two planes. It is matter of 2 dimensions only. If something similar to the following can be added to jsx graph, it would be great addition to jsx graph. Many properties of complex valued function can then be studied.
Here is the link.
http://www.jimrolf.com/java/complexTool/bookComplexTool.html
Two boards board1, board2 can be connected with board1.addChild(board2). This means, every update in board1 triggers an update in board2.
Here is a basic example, see https://jsfiddle.net/zfbrsdwh/ :
const board1 = JXG.JSXGraph.initBoard('jxgbox1', {
boundingbox: [-5, 5, 5, -5], axis:true
});
var p = board1.create('point', [1,2], {name:'Drag me'});
const board2 = JXG.JSXGraph.initBoard('jxgbox2', {
boundingbox: [-5, 5, 5, -5], axis:true
});
var q = board2.create('point', [function() { return [-p.Y(), p.X()]; }],
{name:'image'});
board1.addChild(board2);
Update in reply to the first comment: Visualizing conformal maps in the complex plane can be done by applying the map to a quadrangle. It is necessary to define the edges of the quadrangle by a curve:
var p0 = board1.create('point', [2, -2]);
var p1 = board1.create('point', [2, 2]);
var p2 = board1.create('point', [-2, 2]);
var p3 = board1.create('point', [-2, -2]);
// Draw the quadrangle through p0, p1, p2, p3 as curve
// defined by [fx, fy]
var fx = function(x) {
if (x < 0 || x > 4) { return NaN; }
if (x < 1) {
return (p1.X() - p0.X()) * x + p0.X();
} else if (x < 2) {
return (p2.X() - p1.X()) * (x - 1) + p1.X();
} else if (x < 3) {
return (p3.X() - p2.X()) * (x - 2) + p2.X();
} else if (x < 4) {
return (p0.X() - p3.X()) * (x - 3) + p3.X();
}
};
var fy = function(x) {
if (x < 0 || x > 4) { return NaN; }
if (x < 1) {
return (p1.Y() - p0.Y()) * x + p0.Y();
} else if (x < 2) {
return (p2.Y() - p1.Y()) * (x - 1) + p1.Y();
} else if (x < 3) {
return (p3.Y() - p2.Y()) * (x - 2) + p2.Y();
} else if (x < 4) {
return (p0.Y() - p3.Y()) * (x - 3) + p3.Y();
}
};
var graph1 = board1.create('curve', [fx, fy, 0, 4]);
Then it should be easy to define a conformal map and plot the composition of the two maps in the second board:
// Conformal complex map z -> 1/z
var map = function(x, y) {
var s = x*x+y*y;
return [x / s, -y/s];
};
// Draw the image of the quadrangle under the map
f2x = function(x) {
return map(fx(x), fy(x))[0];
};
f2y = function(x) {
return map(fx(x), fy(x))[1];
};
var graph2 = board2.create('curve', [f2x, f2y, 0, 4]);
The full mathlet is at https://jsfiddle.net/Lmy60f4g/2/

How to plot the graph based on equation using js

I need to plot a graph in a canvas. But how can I use an algebra equation as input, and based on the equation, draw the curve, using javascript?
For example:
x2+5y=250
The equation plots a graph with both positive and negative values.
<!DOCTYPE html>
<html>
<head>
<title>Interactive Line Graph</title>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.1.min.js"></script>
<script>
var graph;
var xPadding = 30;
var yPadding = 30;
var data = { values:[
{ X: "1", Y: 15 },
{ X: "2", Y: 35 },
{ X: "3", Y: 60 },
{ X: "4", Y: 14 },
{ X: "5", Y: 20 },
{ X: "6", Y: 95 },
]};
// Returns the max Y value in our data list
function getMaxY() {
var max = 0;
for(var i = 0; i < data.values.length; i ++) {
if(data.values[i].Y > max) {
max = data.values[i].Y;
}
}
max += 10 - max % 10;
return max;
}
// Return the x pixel for a graph point
function getXPixel(val) {
return ((graph.width() - xPadding) / data.values.length) * val + (xPadding * 1.5);
}
// Return the y pixel for a graph point
function getYPixel(val) {
return graph.height() - (((graph.height() - yPadding) / getMaxY()) * val) - yPadding;
}
$(document).ready(function() {
graph = $('#graph');
var c = graph[0].getContext('2d');
c.lineWidth = 2;
c.strokeStyle = '#333';
c.font = 'italic 8pt sans-serif';
c.textAlign = "center";
// Draw the axises
c.beginPath();
c.moveTo(xPadding, 0);
c.lineTo(xPadding, graph.height() - yPadding);
c.lineTo(graph.width(), graph.height() - yPadding);
c.stroke();
// Draw the X value texts
for(var i = 0; i < data.values.length; i ++) {
c.fillText(data.values[i].X, getXPixel(i), graph.height() - yPadding + 20);
}
// Draw the Y value texts
c.textAlign = "right"
c.textBaseline = "middle";
for(var i = 0; i < getMaxY(); i += 10) {
c.fillText(i, xPadding - 10, getYPixel(i));
}
c.strokeStyle = '#f00';
// Draw the line graph
c.beginPath();
c.moveTo(getXPixel(0), getYPixel(data.values[0].Y));
for(var i = 1; i < data.values.length; i ++) {
c.lineTo(getXPixel(i), getYPixel(data.values[i].Y));
}
c.stroke();
// Draw the dots
c.fillStyle = '#333';
for(var i = 0; i < data.values.length; i ++) {
c.beginPath();
c.arc(getXPixel(i), getYPixel(data.values[i].Y), 4, 0, Math.PI * 2, true);
c.fill();
}
});
</script>
</head>
<body>
<canvas id="graph" width="200" height="150">
</canvas>
</body>
</html>
[i am add one example ploter in math.js ] i want to how to full screen plot the graph and mouse are cilck in graph any point to show the details in x&y value.so how to change please help me.
Parsing linear equation.
Or maybe it is the Parsing of the equation that the question is about.
This answer shows how to parse a simple linear equation.
User inputs x2+5y=230 and you need to solve and plot for y for f(x) which would be the function function(x) { return (3 * x -230) / -5; }
Will assume the equation is always in the same form with x and y and some scalars and constants scalar * x + const + scalar * y = const
Define the rules
Rules
Only x and y will be considered variables.
A term is a scalar and a variable 2x or a constant +1.
All additional characters will be ignored including *,/,%
Numbers can have decimal places. Valid numbers 1 +1 0.2 -2 10e5
Scalars must be adjacent to variables 3y2 becomes 6y 3y-2 stays as is.
Parsing
To parse a equation we must break it down into unambiguous easy to manipulate units. In this case a unit I call a term and will have 3 properties.
scalar A number
variable the name of the variable x,y or null for constants
side which side of the equation the term is Left or right
An example equation
2x + 2 + 3y = 4x - 1y
First parsed to create
terms
// shorthand not code
{2,x,true; // true is for left
{2,null,true; // null is a constant
{3,y,true;
{4,x,false;
{-1,y,false;
Once all the terms are parsed then the equation is solved by summing all the terms for x, y and constants and moving everything to the left flipping the sign of any values on the right.
sumX = 2 + -4; //as 4x is on the right it becomes negative
sumY = 3 + 1;
const = 2;
Making the equation
-2x + 4y + 2 = 0
Then move the y out to the right and divide the left by its scalar.
-2x + 2 = 4y
(-2x + 2)/-4 = y
The result is a function that we can call from javascript will the value of x and get the value of y.
function(x){ return (-2 * x + 2) / 4; }
The Parser
The following function parses and returns a function for input equation for x. That function then use to plot the points in the demo below.
function parseEquation(input){
// Important that white spaces are removed first
input = input.replace(/\s+/g,""); // remove whitespaces
input = input.replace(/([\-\+])([xy])/g,"$11$2"); // convert -x -y or +x +y to -1x -1y or +1x +1y
// just to make the logic below a little simpler
var newTerm = () => {term = { val : null, scalar : 1, left : left, };} // create a new term
var pushTerm = () => {terms.push(term); term = null;} // push term and null current
// regExp [xy=] gets "x","y", or "="" or [\-\+]??[0-9\.]+ gets +- number with decimal
var reg =/[xy=]|[\-\+]??[0-9\.eE]+/g; // regExp to split the input string into parts
var parts = input.match(reg); // get all the parts of the equation
var terms = []; // an array of all terms parsed
var term = null; // Numbers as constants and variables with scalars are terms
var left = true; // which side of equation a term is
parts.forEach( p=> {
if (p === "x" || p === "y") {
if (term !== null && term.val !== null) { // is the variable defined
pushTerm(); // yes so push to the stack and null
}
if (term === null) { newTerm(); } // do we need a new term?
term.val = p;
} else if( p === "=") { // is it the equals sign
if (!left) { throw new SyntaxError("Unxpected `=` in equation."); }
if (term === null) { throw new SyntaxError("No left hand side of equation."); }// make sure that there is a left side
terms.push(term); // push the last left side term onto the stack
term = null;
left = false; // everything on the right from here on in
} else { // all that is left are numbers (we hope)
if (isNaN(p)){ throw new SyntaxError("Unknown value '"+p+"' in equation"); }//check that there is a number
if (term !== null && (p[0] === "+" || p[0] === "-")) { // check if number is a new term
pushTerm(); // yes so push to the stack and null
}
if (term === null) { newTerm(); } // do we need a new term?
term.scalar *= Number(p); // set the scalar to the new value
}
});
if (term !== null) { // there may or may not be a term left to push to the stack
pushTerm();
}
// now simplify the equation getting the scalar for left and right sides . x on left y on right
var scalarX = 0;
var scalarY = 0
var valC = 0; // any constants
terms.forEach(t => {
t.scalar *= !t.left ? -1 : 1; // everything on right is negative
if (t.val === "y") {
scalarY += -t.scalar; // reverse sign
} else if (t.val === "x") {
scalarX += t.scalar;
} else {
valC += t.scalar;
}
})
// now build the code string for the equation to solve for x and return y
var code = "return (" + scalarX + " * x + (" + valC + ")) / "+scalarY +";\n";
var equation = new Function("x",code); // create the function
return equation;
}
The following usage examples are all the same equation
var equation = parseEquation("x2+5y+x=230");
var y = equation(10); // get y for x = 10;
equation = parseEquation("x2+x=230-5y");
equation = parseEquation("x2+x-30=200-2y-3y");
equation = parseEquation("200- 2y-3y = x2+x-30");
equation = parseEquation("200-2y- 3y - x2-x+30=0");
equation = parseEquation("100.0 + 100-2y- 3y - x2-x+30=0");
equation = parseEquation("1e2 + 10E1-2y- 3y - x2-x+30=0");
Demo
I have added it to the code in the answer markE has already given. (hope you don't mind markE)
function plot(equation) {
var graph;
var xPadding = 30;
var yPadding = 30;
var data = {
values : [{
X : "1",
Y : 15
}, {
X : "2",
Y : 35
}, {
X : "3",
Y : 60
}, {
X : "4",
Y : 14
}, {
X : "5",
Y : 20
}, {
X : "6",
Y : -30
},
]
};
// Returns the max Y value in our data list
function getMaxY() {
var max = 0;
for (var i = 0; i < data.values.length; i++) {
if (data.values[i].Y > max) {
max = data.values[i].Y;
}
}
max += 10 - max % 10;
return max;
}
var scaleA = 1.4;
// Return the x pixel for a graph point
function getXPixel(val) {
return ((graph.width() / scaleA - xPadding) / data.values.length) * val + (xPadding * 1.5);
}
// Return the y pixel for a graph point
function getYPixel(val) {
return graph.height() / scaleA - (((graph.height() / scaleA - yPadding) / getMaxY()) * val) - yPadding;
}
graph = $('#graph');
var c = graph[0].getContext('2d');
c.clearRect(0,0,graph[0].width,graph[0].height);
c.lineWidth = 2;
c.strokeStyle = '#333';
c.font = 'italic 8pt sans-serif';
c.textAlign = "center";
// Draw the axises
c.beginPath();
c.moveTo(xPadding, 0);
c.lineTo(xPadding, graph.height() / scaleA - yPadding);
c.lineTo(graph.width(), graph.height() / scaleA - yPadding);
c.stroke();
// Draw the X value texts
for (var i = 0; i < data.values.length; i++) {
c.fillText(data.values[i].X, getXPixel(i), graph.height() / scaleA - yPadding + 20);
}
// Draw the Y value texts
c.textAlign = "right"
c.textBaseline = "middle";
for (var i = 0; i < getMaxY(); i += 10) {
c.fillText(i, xPadding - 10, getYPixel(i));
}
c.strokeStyle = '#f00';
// Draw the line graph
c.beginPath();
c.moveTo(getXPixel(0), getYPixel(equation(0)));
for (var i = 1; i < data.values.length; i++) {
c.lineTo(getXPixel(i), getYPixel(equation(i)));
}
c.stroke();
// Draw the dots
c.fillStyle = '#333';
for (var i = 0; i < data.values.length; i++) {
c.beginPath();
c.arc(getXPixel(i), getYPixel(equation(i)), 4, 0, Math.PI * 2, true);
c.fill();
}
}
var codeText = "";
function parseEquation(input){
// Important that white spaces are removed first
input = input.replace(/\s+/g,""); // remove whitespaces
input = input.replace(/([\-\+])([xy])/g,"$11$2"); // convert -x -y or +x +y to -1x -1y or +1x +1y
// just to make the logic below a little simpler
var newTerm = () => {term = { val : null, scalar : 1, left : left, };} // create a new term
var pushTerm = () => {terms.push(term); term = null;} // push term and null current
// regExp [xy=] gets "x","y", or "="" or [\-\+]??[0-9\.]+ gets +- number with decimal
var reg =/[xy=]|[\-\+]??[0-9\.eE]+/g; // regExp to split the input string into parts
var parts = input.match(reg); // get all the parts of the equation
var terms = []; // an array of all terms parsed
var term = null; // Numbers as constants and variables with scalars are terms
var left = true; // which side of equation a term is
parts.forEach(p=>{
if (p === "x" || p === "y") {
if (term !== null && term.val !== null) { // is the variable defined
pushTerm(); // yes so push to the stack and null
}
if (term === null) { newTerm(); } // do we need a new term?
term.val = p;
} else if( p === "="){ // is it the equals sign
if (!left) { throw new SyntaxError("Unxpected `=` in equation."); }
if (term === null) { throw new SyntaxError("No left hand side of equation."); }// make sure that there is a left side
terms.push(term); // push the last left side term onto the stack
term = null;
left = false; // everything on the right from here on in
} else { // all that is left are numbers (we hope)
if (isNaN(p)){ throw new SyntaxError("Unknown value '"+p+"' in equation"); }//check that there is a number
if (term !== null && (p[0] === "+" || p[0] === "-")){ // check if number is a new term
pushTerm(); // yes so push to the stack and null
}
if(term === null){ newTerm(); } // do we need a new term?
term.scalar *= Number(p); // set the scalar to the new value
}
});
if(term !== null){// there may or may not be a term left to push to the stack
pushTerm();
}
// now simplify the equation getting the scalar for left and right sides . x on left y on right
var scalarX = 0;
var scalarY = 0
var valC = 0; // any constants
terms.forEach(t => {
t.scalar *= !t.left ? -1 : 1; // everything on right is negative
if (t.val === "y") {
scalarY += -t.scalar; // reverse sign
} else if (t.val === "x") {
scalarX += t.scalar;
} else {
valC += t.scalar;
}
})
// now build the code string for the equation to solve for x and return y
var code = "return (" + scalarX + " * x + (" + valC + ")) / "+scalarY +";\n";
codeText = code;
var equation = new Function("x",code); // create the function
return equation;
}
function parseAndPlot(){
var input = eqInput.value;
try{
var equation = parseEquation(input);
plot(equation);
error.textContent ="Plot of "+input+ " as 'function(x){ "+codeText+"}'";
}catch(e){
error.textContent = "Error parsing equation. " + e.message;
}
}
var button = document.getElementById("plot");
var eqInput = document.getElementById("equation-text");
var error = document.getElementById("status");
button.addEventListener("click",parseAndPlot);
parseAndPlot();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<canvas id="graph" width="200" height="150"></canvas> <br>
Enter a linear equation : <input id="equation-text" value="x2 + 5y = 250" type="text"></input><input id="plot" value="plot" type=button></input><div id="status"></div>
I think I understand what you're asking...
Your existing code automatically puts your y-axis at the bottom of the canvas so negative y-values will be off-canvas.
Quick solution
The quickest solution is to divide graph.height()/2 so that your graph has it's y-axis near center-canvas. This leaves room for negative values.
Better solution
The better solution is to redesign your graphing system to allow for solutions in all axis directions.
Refactored code showing the quick solution:
I leave it to you to extend the y-axis labels in the negative direction (if desired)
var graph;
var xPadding = 30;
var yPadding = 30;
var data = { values:[
{ X: "1", Y: 15 },
{ X: "2", Y: 35 },
{ X: "3", Y: 60 },
{ X: "4", Y: 14 },
{ X: "5", Y: 20 },
{ X: "6", Y: -30 },
]};
// Returns the max Y value in our data list
function getMaxY() {
var max = 0;
for(var i = 0; i < data.values.length; i ++) {
if(data.values[i].Y > max) {
max = data.values[i].Y;
}
}
max += 10 - max % 10;
return max;
}
// Return the x pixel for a graph point
function getXPixel(val) {
return ((graph.width()/2 - xPadding) / data.values.length) * val + (xPadding * 1.5);
}
// Return the y pixel for a graph point
function getYPixel(val) {
return graph.height()/2 - (((graph.height()/2 - yPadding) / getMaxY()) * val) - yPadding;
}
graph = $('#graph');
var c = graph[0].getContext('2d');
c.lineWidth = 2;
c.strokeStyle = '#333';
c.font = 'italic 8pt sans-serif';
c.textAlign = "center";
// Draw the axises
c.beginPath();
c.moveTo(xPadding, 0);
c.lineTo(xPadding, graph.height()/2 - yPadding);
c.lineTo(graph.width(), graph.height()/2 - yPadding);
c.stroke();
// Draw the X value texts
for(var i = 0; i < data.values.length; i ++) {
c.fillText(data.values[i].X, getXPixel(i), graph.height()/2 - yPadding + 20);
}
// Draw the Y value texts
c.textAlign = "right"
c.textBaseline = "middle";
for(var i = 0; i < getMaxY(); i += 10) {
c.fillText(i, xPadding - 10, getYPixel(i));
}
c.strokeStyle = '#f00';
// Draw the line graph
c.beginPath();
c.moveTo(getXPixel(0), getYPixel(data.values[0].Y));
for(var i = 1; i < data.values.length; i ++) {
c.lineTo(getXPixel(i), getYPixel(data.values[i].Y));
}
c.stroke();
// Draw the dots
c.fillStyle = '#333';
for(var i = 0; i < data.values.length; i ++) {
c.beginPath();
c.arc(getXPixel(i), getYPixel(data.values[i].Y), 4, 0, Math.PI * 2, true);
c.fill();
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<canvas id="graph" width="200" height="300"></canvas>

vis graph2d y-axis ints only

Is there a way to make the y-axis only have integer-based tick marks. This would be a tremendous help. Also, if the tick marks could be only at multiples of
2 when 0 <= window height <= 10
10 when 10 < window height <= 100
100 window height < 100
... that would be nice - though not absolutely necessary. Sorry for the pseudo-code.
Right now I am doing the following and it works ok, but it is not great.
this.dataset = new vis.DataSet();
var options = {
start: vis.moment(),
end: vis.moment().add(50, 'seconds'),
showMajorLabels: false,
zoomable: true,
dataAxis: {
left: {
range: {
min: 0
}
},
}
style: 'bar',
drawPoints: false,
}
this.graph = new vis.Graph2d(container/*assume I made it :)*/, this.dataset, options);
//+~~~~~~~~~~+
//| LATER ON |
//+~~~~~~~~~~+
range = this.graph.getWindow();
var current = this.dataset.get({
filter: function(item) {
return item.x > range.start && item.x < range.end;
}
});
var maxY = 0;
for (var i = 0, currentLen = current.length; i < currentLen; i++) {
if (current[i].y > maxY) {
maxY = current[i].y;
}
}
maxY = maxY < 8 ? Math.floor((maxY + 4) / 4) * 4 : Math.floor((maxY + 6) / 6) * 6; // makes tick marks at places that don't cause decimal values #_#
this._graph.setOptions({
dataAxis: {
left: {
range: {
max: maxY,
}
}
}
});
Quick note: the line maxY = maxY < 8 ? ... only works for 1-11 from what I can test. I am not sure about higher numbers. However, hopefully this can be fixed.
Very very late answer, but assuming you only care about the y-axis labels themselves and not the horizontal grid lines, you can do this:
var options = {
dataAxis: {
left: {
format: function(value){
// don't show non-integer values on data axis
return Math.round(value) === value ? value : "";
}
}
}
}
You can also access window.height in that function and adjust accordingly for the behaviors you described.
So, it seems like the method I implemented works (e.g. I only see whole numbers regardless of the y value) if you want ints only and don't mind (or want) the minimum y value to be 4 and, additionally, don't mind what the step is.
However, note that this has only been tested with [0, inf) (positive, numbers or 0).
If you have a solution that uses vis to do this - rather than checking for all of the max y values manually - please do share in an answer.

Morris donut chart always start at 12 oclock (top middle)

I am using Morris donut charts and I want to try and ensure that the segments always start at 12 o'clock so there is some consistency when viewing multiple charts together on the same screen.
Previously I used jQuery circliful https://github.com/pguso/jquery-plugin-circliful which worked great but the quality was really fuzzy on Retina screens so I checked out Morris and it does what I want bar this.
I want something that looks like the first where the segment always starts at the top middle:
Does anyone know how to achieve this?
var donut= Morris.Donut({
element: 'donut-example',
data: [
{label: "New clients", value: 35},
{label: "In-Store Sales", value: 65}
],
colors: ['#90c070', '#eeeeee'],
select :0
});
donut.select(0);
http://jsfiddle.net/0t976ez6/
No, as far as I can tell from the source code, there's no API (public or private) in Morris to do that. See Donut's redraw() for how it renders the chart: it always starts from the lowest point and goes in counterclockwise direction.
Now, I would recommend to consider switching to other chart libraries, before proceeding. For example there's a D3.js — not the simplest library out there, but it provides so much freedom to do almost anything, it sometimes may be hard to work with. But there is a ton of examples of every thing you could think of. Like, here's a blog post about creating similar looking donut charts using D3.
But hey, it's a JavaScript, we can patch things, so nothing is impossible!
Warning: patching library code is not the best idea! You must be careful and do that only when it's really the only way.
The easiest (certainly not the simplest) way would be to monkey-patch Morris.Donut.redraw() to start from the Math.PI (middle top) and go in clockwise direction. To do that, the initial value of last local variable should be changed from 0 to Math.PI and logic that finds next starting point tweaked a bit.
Here's is a working example where I've surrounded lines that were patched with special PATCHED comments:
/**
* Careful: patched lib-function!
*
* In case of problems, see original
*/
Morris.Donut.prototype.redraw = function() {
var C, cx, cy, i, idx, last, max_value, min, next, seg, total, value, w, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _results;
this.raphael.clear();
cx = this.el.width() / 2;
cy = this.el.height() / 2;
w = (Math.min(cx, cy) - 10) / 3;
total = 0;
_ref = this.values;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
value = _ref[_i];
total += value;
}
min = 5 / (2 * w);
C = 1.9999 * Math.PI - min * this.data.length;
// ------------ PATCHED: start from the top
last = Math.PI;
// original:
// last = 0
// ------------
idx = 0;
this.segments = [];
_ref1 = this.values;
for (i = _j = 0, _len1 = _ref1.length; _j < _len1; i = ++_j) {
value = _ref1[i];
// ------------ PATCHED: change direction of rendering
next = last - min - C * (value / total);
seg = new Morris.DonutSegment(cx, cy, w * 2, w, next, last, this.data[i].color || this.options.colors[idx % this.options.colors.length], this.options.backgroundColor, idx, this.raphael);
// original:
// next = last + min + C * (value / total);
// seg = new Morris.DonutSegment(cx, cy, w * 2, w, last, next, this.data[i].color || this.options.colors[idx % this.options.colors.length], this.options.backgroundColor, idx, this.raphael);
// ------------
seg.render();
this.segments.push(seg);
seg.on('hover', this.select);
seg.on('click', this.click);
last = next;
idx += 1;
}
this.text1 = this.drawEmptyDonutLabel(cx, cy - 10, this.options.labelColor, 15, 800);
this.text2 = this.drawEmptyDonutLabel(cx, cy + 10, this.options.labelColor, 14);
max_value = Math.max.apply(Math, this.values);
idx = 0;
_ref2 = this.values;
_results = [];
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
value = _ref2[_k];
if (value === max_value) {
this.select(idx);
break;
}
_results.push(idx += 1);
}
return _results;
};
var donut = Morris.Donut({
element: 'donut-example',
data: [{
label: "In-Store Sales",
value: 55
}, {
label: "New clients",
value: 25
}, {
label: "Reseller stores",
value: 20
}],
colors: ['#90c070', '#ee9090', '#9090ee'],
select: 0
});
donut.select(0);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://rawgit.com/morrisjs/morris.js/master/morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<link href="http://cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.css" rel="stylesheet" />
<div id="donut-example" style="height: 250px;"></div>
Of course, there's may be another way to achieve the same thing: maybe I overlooked some local variable changing sign of which will result in the same rendering.

Categories