UPDATE
I managed to get trendline support added to the RGrah Line and Scatter charts. There's a demo in the download archive called demos/line-trendline.html that shows it. The Scatter chart supports trendlines too.
Assuming that I have the following values that I'm going to plot on a Line chart (these are values and not coordinates - the coordinates are calculated by my software and are shown below):
[4,4,3,2,5,5]
How would I turn those values into a set of trendline values/coordinates? (BTW I don't really have any Maths expertise beyond school level - so no fancy Maths terminology please!).
To add further details: These are a set of values that are spaced equally across a 500 pixel space (an HTML5 canvas tag). So the X coordinates are calculated for you and will come out like this (35 pixel margin on both sides of the chart): [35,121,207,293,379,465].
These are just the X coordinates, the Y coordinates are calculated automatically based on the scale, the height of the chart and the value. Here's an example Line chart that my software creates using this code:
<canvas id="cvs" width="500" height="250">
[No canvas support]
</canvas>
<script>
line = new RGraph.Line({
id: 'cvs',
data: [4,4,3,2,5,5],
options: {
xaxisLabels: ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
shadow: false,
backgroundGridBorder: false,
backgroundGridVlines: false,
xaxis: false,
yaxis: false
}
}).draw()
</script>
You can see the chart online here:
https://www.rgraph.net/demos/line-csv-reader.html
And the X/Y coordinates (that are then plotted on the canvas tag) that are generated end up as this:
[[35,71],[121,71],[207,107],[293,143],[379,35],[465,35]]
So you already know:
the X coordinates are calculated for you ... (35 pixel margin): 35, 121, 207, 293, 379, 465.
the generated result:
[[35,71], [121,71], [207,107], [293,143], [379,35], [465,35]] that's just a list of [x,y] points
From that we can remove the X we know (calculated for us) and we will get:
71, 71, 107, 143, 35, 35
we can see a pattern with the original input
4, 4, 3, 2, 5, 5
piece of cake to get the formula with that sequence:
35 + (5 - y)*36
All that remains is to put that formula into code:
<canvas id="canvas"></canvas>
<script>
canvas = document.getElementById('canvas');
canvas.width = canvas.height = 500;
ctx = canvas.getContext('2d');
x = 35
trendline = []
plot = [4, 4, 3, 2, 5, 5]
plot.forEach(function(value) {
y = 35 + (5 - value) * 36
ctx.lineTo(x, y);
trendline.push([x, y])
x += 86
});
ctx.stroke();
console.log(JSON.stringify(trendline))
</script>
Now from what you mentioned on the comments:
it just plots the values that you give it ... It doesn't generate trend lines from your data
looking at the code of rgraph on the drawLine function:
https://www.rgraph.net/libraries/src/RGraph.line.js
// Loop thru each value given, plotting the line
// (FORMERLY FIRST)
for (i=0,len=lineData.length; i<len; i+=1) {
var data_point = lineData[i];
//
// Get the yPos for the given data point
//
var yPos = this.getYCoord(data_point);
...
//
// Add the coords to an array
//
this.coords.push([xPos, yPos]);
lineCoords.push([xPos, yPos]);
That lineCoords looks like a trendline to me...
Related
I have an array of coordinates [x1,y1, x2, y2 ....] which represents a polygon on a html canvas. I am drawing the polygon using KonvaJS. I am trying to retrieve the scaled coordinates for the polygon where [x1, y1, x2, y2 ...] is the scaled coordinates.
I have tried the following:
Using JSTS https://github.com/bjornharrtell/jsts to add buffer to the coordinates.
Followed this snippet http://jsfiddle.net/qdv1n4yL/7/ and tried in integrate into my react/typescript app and created the snippet as shown below
function vectorCoordinates2JTS (polygon) {
var coordinates: any = [];
for (var i = 0; i < polygon.length; i++) {
coordinates.push(new Coordinate(polygon[i].x, polygon[i].y));
}
return coordinates;
}
function inflatePolygon(poly, spacing) {
var geoInput = vectorCoordinates2JTS(poly);
geoInput.push(geoInput[0]);
var geometryFactory = new GeometryFactory();
var shell = geometryFactory.createPolygon(geoInput);
var polygon = new BufferOp(poly);
console.log(polygon.getResultGeometry())
var inflatedCoordinates: any = [];
var oCoordinates;
oCoordinates = polygon.shell.points.coordinates;
console.log(oCoordinates.length)
for (let i = 0; i < oCoordinates.length; i++) {
var oItem;
oItem = oCoordinates[i];
inflatedCoordinates.push(Math.ceil(oItem.x), Math.ceil(oItem.y));
}
return inflatedCoordinates;
}
Here I am passing poly as [{x: 1, y:1}, {x:2, y:2}] and spacing as 1.5. It breaks on this line here polygon.getResultGeometry() with an error.
Also tried using this library to get the transformed coordinates http://turfjs.org/docs/#transformScale but it returns me a bunch of coordinates which i cant seem to use and get my end result as just a simple list of scaled coordinates.
My overall requirements for solving this problem is the polygon should have a cushioning like buffer around the original polygon which can be used to detect collision. I was able to get the collision detection between the polygon and a point working but been stuck on adding the scale/buffer for the past few days. Any help or follow up questions to this problem is highly appreciated. Thanks!
If you need mouse or touch events, for point hit detection, so to extend the edge of the polygon so that you get a larger area for collision detection, Konva has the hitStrokeWidth parameter. See point 2 on this docs page.
The gist is that Konva provides its own hit detection feature by drawing the shape in an off-screen canvas but inflating the stroke width. For example:
const line = new Konva.Line({
x: 50,
y: 100,
points: [0, 0, 50, 0, 50, 100, 0, 100],
tension: 1,
strokeWidth: 1,
hitStrokeWidth: 20,
stroke: 'black',
});
layer.add(line);
What this does is to draw the stroke with the given hitStrokeWidth size - in this case 20px. But only on the hit detection canvas offscreen. You can then avoid the need for the calculation you are attempting.
Using highcharts, what would be the best way to implement target lines over a column or bar chart? (Also known as target vs goal)
Found this picture from a similar d3.js thread as an example:
I'm thinking another series, but don't see any options in the documentation about a target line. Here's a basic column chart: jsfiddle.net/eksh8a8p/
I've thought about using a columnrange series, but you are limited to having a start/end value which can be problematic due to scaling of number values.
Are there other ideas/options that could create a similar result to the picture above?
You can use a columnrange series but there is a simpler option - a scatter series with a rectangle marker
//custom marker
Highcharts.SVGRenderer.prototype.symbols['c-rect'] = function (x, y, w, h) {
return ['M', x, y + h / 2, 'L', x + w, y + h / 2];
};
//series options
{
marker: {
symbol: 'c-rect',
lineWidth:3,
lineColor: Highcharts.getOptions().colors[1],
radius: 10
},
type: 'scatter'
example: http://jsfiddle.net/eksh8a8p/1/
I am using html5 canvas element to draw line chart. The chart works fine with positive values. But when provided negative values, the chart is not drawn correctly.
This is what I have tried. Any help will be appreciated.
http://jsfiddle.net/nshX6/142/
function getMinY () {
var min = 0;
for(var i = 0; i < data.values.length; i ++) {
if(data.values[i].Y < min) {
min = data.values[i].Y;
}
}
return Math.ceil(min);
}
Here are some tool functions you can use to build a flexible graph.
Your flexible graph will be able to show any range of data and it will always fit on the available canvas size.
calcSourceMinMax: Calculates the minimum and maximum value from a data array.
mapRange: Takes any data value and maps it into a proportional value that is guaranteed to be inside the minimum and maximum of the graphs displayable width & height. This allows your data array to contain any range of values and still never fall outside the graphing display area.
getDisplayXY: Takes a specified x,y data value and finds its display X,Y coordinate on the graph.
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var xPadding = 40;
var yPadding = 30;
// Notice I changed The X values
var data = { values:[
{ X: 0, Y: -120 },
{ X: 2, Y: 28 },
{ X: 3, Y: 18 },
{ X: 4, Y: 34 },
{ X: 5, Y: 40 },
{ X: 6, Y: 80 },
{ X: 7, Y: 80 }
]};
// calc the drawable graph boundaries
var graphLeft=xPadding;
var graphRight=canvas.width-xPadding;
var graphTop=yPadding;
var graphBottom=canvas.height-yPadding;
// graph styling
var dotRadius=3;
// calc the min & max values of data.values (calc both X & Y ranges)
var rangeX=calcSourceMinMax(data.values,'X');
var rangeY=calcSourceMinMax(data.values,'Y');
// draw the graph content
var starting=getDisplayXY(data.values[0].X,data.values[0].Y);
dot(starting,dotRadius);
for(var i=1;i<data.values.length;i++){
var ending=getDisplayXY(data.values[i].X,data.values[i].Y);
connector(starting,ending);
dot(ending,dotRadius);
starting=ending;
}
// draw the graph axes
var y0=getDisplayXY(graphLeft,0).displayY;
ctx.beginPath();
ctx.moveTo(graphLeft,graphTop);
ctx.lineTo(graphLeft,graphBottom);
ctx.moveTo(graphLeft,y0);
ctx.lineTo(graphRight,y0);
ctx.strokeStyle='#D3E';
ctx.stroke();
// draw the graph legends
ctx.textAlign='right';
ctx.textBaseline='middle';
var y0=getDisplayXY(graphLeft,0).displayY;
var yMin=getDisplayXY(graphLeft,rangeY.min).displayY;
var yMax=getDisplayXY(graphLeft,rangeY.max).displayY;
var xMax=getDisplayXY(graphRight,rangeX.max).displayX;
ctx.fillText(rangeY.min,graphLeft-10,yMin);
ctx.fillText(0,graphLeft-10,y0);
ctx.fillText(rangeY.max,graphLeft-10,yMax);
ctx.fillText(rangeX.max,graphRight+10,y0);
///////////////////////////////////
// HELPER FUNCTIONS
///////////////////////////////////
//
function getDisplayXY(valueX,valueY){
// calc the display X & Y from data.values[i]
x=mapRange(valueX,rangeX.min,rangeX.max,graphLeft,graphRight);
// Note: canvas y values increase going downward
// so swap graphTop & graphBottom
y=mapRange(valueY,rangeY.min,rangeY.max,graphBottom,graphTop);
return({displayX:x,displayY:y})
}
//
function connector(starting,ending){
ctx.beginPath();
ctx.moveTo(starting.displayX,starting.displayY);
ctx.lineTo(ending.displayX,ending.displayY);
ctx.stroke();
}
//
function dot(position,radius){
ctx.beginPath();
ctx.moveTo(position.displayX,position.displayY);
ctx.arc(position.displayX,position.displayY,radius,0,Math.PI*2);
ctx.closePath();
ctx.fill();
}
// map source values into a designated range
function mapRange(value, sourceLow, sourceHigh, mappedLow, mappedHigh) {
return mappedLow + (mappedHigh - mappedLow) * (value - sourceLow) / (sourceHigh - sourceLow);
}
// mapping helper function
function calcSourceMinMax(a,prop){
var min=1000000;
var max=-1000000;
for(var i=0;i<a.length;i++){
var value=a[i][prop];
if(value<min){min=value;}
if(value>max){max=value;}
}
return({min:min,max:max});
}
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=350 height=300></canvas>
You will want to style the graph according to your design needs. This minimal example shows a legend containing the x,y axes and the min,max ranges of values.
Also, the x-axis is put at the y=0 value. You will also want to check that there is indeed a y=0 in the range of your y values. If not, you might move the x-axis at the bottom of your graph.
Good luck with your project!
Try modifaying this parameters :
var xPadding = 30;
var yPadding = 30;
To :
var xPadding = 30;
var yPadding = 100;
After that you need to change Y step to be bigger.
EDIT
In case that you have dynamic data you need to normalize values.
step 1 : find min value for Y
step 2 : if min value is less than zero (0) you need to normalize all values to be positive. Add that value to every element of array
step 3 : shift Y axes to it's new position.
I use jqPlot to plot a line chart out of a .csv-file.
I need to get the xmax and ymax values of the plot and use them for further processings.
How do I get this or any other values and write them inside my own variables?
EDIT
Let's say this is my plot:
What I need is not the maximum x-value from the array (here 1380). I need to get the maximum value from the plot (here 2000). For further processing I would like to add rectangles inside the plot, see second picture: and calculate their height as a x-value and not as their pixel-value.
Therefore I need to access the jqplot variables, not the array variables I give over to jqplot.
So, at some point you have an array of values that you passed to jqPlot to draw the graph, for example:
var myData = [[1, 2],[3,5.12],[5,13.1],[7,33.6],[9,85.9],[11,219.9]];
If you want to find the maximum x and y values, you just need to loop through the array keeping track of the largest value you've found so far.
var maxX, maxY;
for (var i=0; i < myData.length; i++) {
if (myData[i][0] > maxX || !maxX) {
maxX = myData[i][0];
}
if (myData[i][1] > maxY || !maxY) {
maxY = myData[i][1];
}
}
Here's a simple demo: http://jsfiddle.net/LAbvj/
EDIT: Ok, so I think what you are now asking for is the maximum for each axis. In that case, this is simple:
var plot1 = $.jqplot('chart1', [
[3, 7, 19, 1, 4, 6, 8, 2, 5]
]);
console.log(plot1.axes.xaxis.max);
console.log(plot1.axes.yaxis.max);
See demo: http://jsfiddle.net/KJTRF/
This I guess is more of a maths question or maybe an SVG question. I was looking at modifying some example code I found on the raphael.js site. I already modified it to have a custom centre point. Now I want to modify it so that I can specify at which angle the arc is started at. (similar to d3.js so I can use it to have something like a bar chart with the middle missing).
However I have no idea where or how to begin. My maths is terrible, I have no idea what alpha is and the a variable does. Or why x and y are calculated that way. I have been reading the SVG specification over and over but I am missing some crucial basic knowledge and I don't know.
Can someone point me in the right direction so I can begin to understand this stuff?
window.onload = function () {
var r = Raphael("holder", 600, 600),
R = 200,
init = true,
param = {stroke: "#fff", "stroke-width": 30};
// Custom Attribute
r.customAttributes.arc = function (xPos, yPos, value, total, R) {
var alpha = 360 / total * value,
a = (90 - alpha) * Math.PI / 180,
x = xPos + R * Math.cos(a),
y = yPos - R * Math.sin(a),
var path = [["M", xPos, yPos - R], ["A", R, R, 0, +(alpha > 180), 1, x, y]];
return {path: path};
};
var sec = r.path().attr(param).attr({arc: [300, 300, 3, 60, R]});
};
Running the code produces:
<svg height="600" version="1.1" width="600" xmlns="http://www.w3.org/2000/svg" style="overflow: hidden; position: relative;">
<path style="" fill="none" stroke="#bfb5b5" d="M300,100A200,200,0,0,1,361.8033988749895,109.7886967409693" stroke-width="30">
</svg>
Also I have no idea how the arc parameters work together to draw what they are drawing.
Apologies for the lack of focus on the question.
EDIT:
It's based on the polar clock example. http://raphaeljs.com/polar-clock.html
I think the author of the example is trying to create a custom attribute in order to make it easy to create arcs based on clock rotation.
Basically the total paramter of the custom attribute represents the total movement of the clock (60 seconds) while value (3 in your case) represents the length (in seconds) of the arc you are trying to draw. So basically you have an arc of 3 seconds.
Now for the math:
alpha : the angle (in degrees) of the arc. You notice the conversion from seconds to degrees: 3 seconds -> 18 degrees
a : the angle in radians. Trigonometric formulas use radians not degrees, so you need this conversion. For some reason that I don't understand, this is the complementary angle (90 - alpha)
Edit: the complementary angle is (probably) used to compensate for the fact that in trigonometry the y-axis points upwards while on the svg canvas it points downwards.
x, y : ending points of the path (arc) you are drawing. These are caculated using elementary trigonometry (sorry..you're not getting any help here).
The parameters for the svg arc are described here: http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands