Weird chart maximum axis x value (Real Challenge) - javascript

Here you can see a chart created using graphael. http://jsfiddle.net/aNJxf/4/
It is shown with it's y axis correctly.
The first y value is 0.03100 and the maximum value at y axis is at 0.031
If we change the value to 0.03104 the maximum value at y axis becomes 1.03 and now all our points are in the bottom.
If we add another 0.00001, which makes that value 0.03105, the maximum at the axis y becomes 0.531 and now our points are shown at the wrong position of the chart.
It seems that something is going wrong while graphael calculates the maximum y axis value.
Why this happens? And how we can fix that?
The code that I have pasted there is
var r = Raphael("holder"),
txtattr = { 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 =[7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58];
var yVals = [0.03100,0.02259,0.02623,0.01967,0.01967,0.00788,0.02217,0.0137,0.01237,0.01764,0.0131,0.00942,0.0076,0.01463,0.02882,0.02093,0.02502,0.01961,0.01551,0.02227,0.0164,0.0191,0.00774,0.03076,0.0281,0.01338,0.02763,0.02334,0.00557,0.00023,0.01523,0.0263,0.03077,0.02404,0.02492,0.01954,0.01954,0.02337,0.01715,0.02271,0.00815,0.01343,0.00985,0.01837,0.00749,0.02967,0.01156,0.0083,0.00209,0.01538,0.01348,0.01353];
//r.text(160, 10, "Symbols, axis and hover effect").attr(txtattr);
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 });
Thanks

Sorry, I'm not real familiar with gRaphael, but I did find that converting your yVals into whole numbers (by multiplying each by 1e5) seemed to rid your chart of the awkward behavior.
This suggests that it could be related to the algorithm gRaphael uses to find the max axis value (as you ask in your related question) when your values are small decimal values (and alter at even more significant digits).
I know there are inherent issues with float precision, but I can't be sure that applies to your case, or that your values are low enough to consider this.
Not the best workaround, but if it would be feasible for you, you could display the yValues in an order of magnitude larger, and remind the viewer that they are actually smaller than presented. For example, your chart could go from 0 to 3100 and remind your viewer that the scale is scale * 1e-5.

Related

Apache Echarts: dataZoom's miniature misrepresents data in a managed plot (the plot drawn over the slider doesn't match the main plot's data)

When I create simple line plot/chart using Apache Echarts I also can add built-in data scaling mechanism: dataZoom. It reaches its main goal, but there is a question to scaled data representation, made by dataZoom. By default, dataZoom doesn't take into account the chart scale limits ticks or/and the minimum and maximum allowable values (range of a function, represented by the plot). Instead, the thumbnail of the chart is drawn on the specific value range passed to the plot in series section. In addition, everytime a small indent is added from the minimum and maximum values ​​to the borders of the graphic element.
As a result, the representation of the visualised data looks inconsistent with reality: null is not null, max is not max (because they don't match the lower and higher bounds of the coordinate area of ​​the thumbnail plot, respectively), the amplitude of the chart fluctuations does not correspond to the scale of real data fluctuations.
Screenshot
Is there a way (documented or undocumented) to remove the indents and force the plot to use the minimum and maximum values ​​allowed for the yAxis ticks?
I drawn a small example, it may be pasted to Echarts online editor.
let x = [];
let y = [];
let scaled = [];
/*y = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 300, 300,
10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0
];*/
for (let i = 1; i < 300; i++) {
x.push(i);
element = Math.random() * 40 + 50;
y.push(element);
scaled.push(element * 6 - 250);
}
option = {
xAxis: {
data: x
},
yAxis: {
min: 0,
max: 300
},
dataZoom: [
{
start: 50,
end: 58.5
}
],
series: [
{
name: 'Fake Data',
type: 'line',
symbol: 'none',
data: y
},
{
name: 'Simulated Scaling',
type: 'line',
symbol: 'none',
lineStyle: {
opacity: 0.3
},
data: scaled
}
]
};
As you can see, the magnitude of the fluctuations of the graph, drawn by dataZoom doesn't correspond rather to the main data, but to some kind of artificial transformation of them (light green graph). Then try to comment 11st line and uncomment lines from 4 to 7. At the start of the plot you'll see main graph touching y zero line, but not on the thumbnail.
I didn't find any params for dataZoom that make them to look like expected.

Plotly.js Adding Point on Click 2022

I am interested in creating an interactive plot in plotly.js, where a user can click on the plot and add a new point at that position. Essentially this means retrieving the plot coordinates of a mouse click. Most of the built-in click events deal with capturing a click on an already plotted marker, rather than an arbitrary position within the plot.
The following question deals with this issue, Plotly.js create a point on click. However, the code appears to have been made obsolete by changes to plotly.js at some point. An example of the answer to that question was demo'd on codepen. It appears that the xy mouse position detection now only updates within the toolbar region. My guess is that there was a renaming in the various components that make up the plot.
Link to non-functioning code from the comments of that answers:
https://codepen.io/circleoncircles/pen/abObLLE
var traces = [{
x: [1, 2, 3, 4],
y: [10, 15, 13, 17],
mode: 'markers',
type: 'scatter'
}];
traces.push({
x: [2, 3, 4, 5],
y: [16, 5, 11, 9],
mode: 'markers',
type: 'scatter'
});
traces.push({
x: [1, 2, 3, 4],
y: [12, 9, 15, 12],
mode: 'markers',
type: 'scatter'
});
traces.push({
x: [],
y: [],
mode: 'markers',
type: 'scatter'
});
var myPlot = document.getElementById('myPlot')
Plotly.newPlot('myPlot', traces, {hovermode: 'closest'});
Number.prototype.between = function(min, max) {
return this >= min && this <= max;
};
Plotly.d3.select(".plotly").on('click', function(d, i) {
var e = Plotly.d3.event;
var bg = document.getElementsByClassName('bg')[0];
var x = ((e.layerX - bg.attributes['x'].value + 4) / (bg.attributes['width'].value)) * (myPlot.layout.xaxis.range[1] - myPlot.layout.xaxis.range[0]) + myPlot.layout.xaxis.range[0];
var y = ((e.layerY - bg.attributes['y'].value + 4) / (bg.attributes['height'].value)) * (myPlot.layout.yaxis.range[0] - myPlot.layout.yaxis.range[1]) + myPlot.layout.yaxis.range[1]
if (x.between(myPlot.layout.xaxis.range[0], myPlot.layout.xaxis.range[1]) &&
y.between(myPlot.layout.yaxis.range[0], myPlot.layout.yaxis.range[1])) {
Plotly.extendTraces(myPlot, {
x: [
[x]
],
y: [
[y]
]
}, [3]);
}
});
Plotly.d3.select(".plotly").on('mousemove', function(d, i) {
var e = Plotly.d3.event;
var bg = document.getElementsByClassName('bg')[0];
var x = ((e.layerX - bg.attributes['x'].value + 4) / (bg.attributes['width'].value)) * (myPlot.layout.xaxis.range[1] - myPlot.layout.xaxis.range[0]) + myPlot.layout.xaxis.range[0];
var y = ((e.layerY - bg.attributes['y'].value + 4) / (bg.attributes['height'].value)) * (myPlot.layout.yaxis.range[0] - myPlot.layout.yaxis.range[1]) + myPlot.layout.yaxis.range[1]
if (x.between(myPlot.layout.xaxis.range[0], myPlot.layout.xaxis.range[1]) &&
y.between(myPlot.layout.yaxis.range[0], myPlot.layout.yaxis.range[1])) {
console.log("Location X:"+x+" Y"+y)
document.getElementById("xvalue").value = x;
document.getElementById("yvalue").value = y;
}
});
Does anybody have an updated method for this?
I think I've got it. I would still consider this relatively hacky, so if someone else has a better way to handle it, I'm all ears and would be happy to assign a better answer.
All you really need to do is get the bounding box of the element that represents the plot field. It appears that one such element is that with the class gridlayer. Then I grabbed its bounding rect.
So the computation lines in the above would become:
var bgrect = document.getElementsByClassName('gridlayer')[0].getBoundingClientRect();
var x = ((e.x - bgrect['x']) / (bgrect['width'])) * (myPlot.layout.xaxis.range[1] - myPlot.layout.xaxis.range[0]) + myPlot.layout.xaxis.range[0];
var y = ((e.y - bgrect['y']) / (bgrect['height'])) * (myPlot.layout.yaxis.range[0] - myPlot.layout.yaxis.range[1]) + myPlot.layout.yaxis.range[1];
One additional change is necessary if you want to use the latest version of plotly. d3 is no longer embedded in plotly, so it's necessary to get d3 from CDN (the most recent version I found to work for this is v5).
<script src="https://cdn.plot.ly/plotly-2.11.0.min.js"></script>
<script src="//d3js.org/d3.v5.min.js"></script>
In the script you just reference d3 at the root then:
d3.select(".plotly").on('click', function(d, i) {
var e = d3.event;
I revised the codepen example that was posted previously to this new method https://codepen.io/jranalli/pen/eYyVVgr

Ways to make bezier curves with about 8-9 control points in Javascript

I am making a game where i need to display ship traversing a flowing river. I am creating river with bezier curves but it is with only 3 control points from the bezerCurveTo. So, the river is not 'curvy enough.' How can i go about achieving this?
The tabageos.GeometricMath Class has some advanced functions for curves, paths and merging Arrays. To accomplish what you want you could join multiple paths together. For example: https://codepen.io/Contrapenner/pen/mdBVrbw
function dot(x, y) {
var d = document.createElement("span");
d.setAttribute("style", "position:absolute;left:" + x + "px;top:" + y + "px;width:2px;height:2px;background: blue");
document.getElementById("river").appendChild(d);
};
var riverPath = tabageos.GeometricMath.getRawArcCurvePath(5, 5, 10, 10, 5, 15, 10);
riverPath = tabageos.GeometricMath.mergeArrays(riverPath, tabageos.GeometricMath.getRawArcCurvePath(5, 15, 0, 20, 5, 25, 10));
riverPath = tabageos.GeometricMath.mergeArrays(riverPath, tabageos.GeometricMath.getRawArcCurvePath(5, 25, 10, 30, 5, 35, 10));
riverPath = tabageos.GeometricMath.mergeArrays(riverPath, tabageos.GeometricMath.getRawArcCurvePath(5, 35, 0, 40, 5, 45, 10));
//and you could keep going as needed.
//there is also getRawHermiteCurvePath
var i = 0;
for (i; i < riverPath.length; i += 2) {
dot(riverPath[i], riverPath[i + 1]);
}

dygraphs automatically set value range for chart with error/custom bars?

I have a chart who's data is loaded from a SQL database, the chart MAY contain duplicate values for the same x value.
i.e. X(time) value at time 55 seconds may have a temperature value of: 50, 51, 49, 52, stored on different rows.
I implemented the errorbars in order to represent those discrepancies to the user since multiple y values per x point is not possible for the same series.
The result csv data prior to plotting is [49, 50, 52]... this has been tested and works great, however once I got this working now all of my graphs y-axis begin at value 0 (instead of 49 in this case).
Is there any way to automate the minimum y value to simply be the minimum y value generated, i.e. 49? as it is done without error bars? or will this have to be hard coded?
I am currently implementing a draw point callback function, I could incorporate a way to extract the minimum y-value to set my limits there if there is no already implemented way.
//EDIT Added code and pictures....
function createDyGraph(newChart) {
"use strict";
var min = 100000000; //value way over possible range of data
newChart.dyGraph = new Dygraph(
document.getElementById(newChart.chartGraphID),
newChart.csvData,
{
drawPointCallback: function (g, seriesName, canvasContext, cx, cy,
seriesColor, pointSize, row) {
var col = g.indexFromSetName(seriesName),
val = parseInt(g.getValue(row, col).toString().replace(/,/g, ""), 10),
color = '';
if (newChart.erroneousData[row]) {
color = 'red';
} else {
color = newChart.colors[col - 1];
}
if (val < min) {
min = val;
}
if (color === 'red') {
canvasContext.beginPath();
canvasContext.strokeStyle = 'red';
canvasContext.arc(cx, cy, pointSize, 0, 2 * Math.PI, false);
canvasContext.stroke();
} else {
canvasContext.beginPath();
canvasContext.strokeStyle = seriesColor;
canvasContext.arc(cx, cy, pointSize, 0, 2 * Math.PI, false);
canvasContext.stroke();
}
},
customBars: true,
colors: newChart.colors,
animatedZooms: true,
connectSeparatedPoints: true,
showLabelsOnHighlight: true,
ylabel: 'Count / Value',
xlabel: 'Time (Seconds)',
drawPoints: true,
pointSize: 1,
labels: newChart.labels,
labelsDiv: document.getElementById('legend' + chartIndex),
legend: 'always'
}
);
alert(min);
}
//EDIT: Added JSON Data
[["2014-02-06T16:30:00.000Z",[null,2739,null],[null,1786,null],[null,3680.1204,null],[null,2390.9182,null]],
["2014-02-06T16:30:01.000Z",[null,2739,null],[null,1786,null],[null,3680.1204,null],[null,2390.9182,null]],
["2014-02-06T16:30:02.000Z",[null,2739,null],[null,1786,null],[null,3680.1204,null],[null,2390.9182,null]],
["2014-02-06T16:30:03.000Z",[null,2739,null],[null,1786,null],[null,3680.1204,null],[null,2390.9182,null]],
["2014-02-06T16:30:04.000Z",[null,2739,null],[null,1786,null],[null,3680.1204,null],[null,2390.9182,null]],
["2014-02-06T16:30:05.000Z",[null,2739,null],[null,1786,null],[null,3680.1204,null],[null,2390.9182,null]],
["2014-02-06T16:30:06.000Z",[null,2739,null],[null,1786,null],[null,3680.1204,null],[null,2390.9182,null]],
...
["2014-02-06T16:30:59.000Z",[null,2740,null],[null,1787,null],[null,3681.464,null],[null,2392.2569,null]]]
Well I just figured it out... for customBars the CSV format should not be [null, Value, null] initially, but rather [value, value, value].
Consequently should you want to add min or max to that, the CSV will be updated as
[min, value, max]

correct positioning with Highcharts renderer on a bar chart

I have a Highcharts bar chart that I'm trying to add custom shapes to based on the bar values and position. To start with, I'm just trying to use highcharts.renderer.path, to add a line for each bar, as tall as the bar, positioned on the x axis based on a hard coded value. Here's a picture of what I mean:
This should be easy, and it is when the chart.type = "column". In the highcharts callback, I would use getBBox() on each bar, and translate() to convert the x axis value to a pixel value.
However, I've run into several problems when trying to do this with chart.type = "bar". First, all x and y values are switched (I assume this is how the author created the bar chart from a column chart in the first place). This is true for all the properties of the chart as well: plotLeft is now the top, plotTop is now the left.
This should work:
function (chart) {
$.each(chart.series[0].data, function (pointIndex, point) {
var plotLine = {},
elem = point.graphic.element.getBBox(),
yStart,
xStart,
newline;
yStart = chart.plotTop+elem.x;
xStart = chart.plotLeft+elem.height;
plotLine.path = ["M", xStart, yStart+1, "L", xStart, yStart+point.pointWidth];
plotLine.attr = {
'stroke-width': 1,
stroke: point.color,
zIndex: 5
};
newline = chart.renderer.path(plotLine.path).attr(plotLine.attr).add();
});
});
Full example: http://jsfiddle.net/Bh3J4/9/
The second issue may be a bug that can't be overcome. It appears that when there is more than one data point, all of the x and y values get mixed up between the points. Notice in the fiddle that the colors don't match the positions. I've created an issue on GitHub.
When there's just one point, it's not a problem. When there are two points, I could easily switch the values to get the right positioning. However when there are 3 or more points, I can't seem to figure out the logic for how the values get mixed up.
The third issue, is that the translate function doesn't seem to work on the xAxis for a bar chart, even though it does on the yAxis.
chart.yAxis[0].translate(4); // correct for bottom axis
chart.xAxis[0].translate(1); // incorrect for side axis
Is there another way to achieve what I'm looking for? Am I missing something in that Fiddle that's not actually a bug?
I was able to achieve the result I wanted, but I don't know if it's coincidental or a workaround for an actual bug. Regardless, it seems that using the x value from the reverse sorted array helped me line everything up correctly. Here's the callback function for highcharts:
function (chart) {
var benchmarks = { A: 1.5, B: 3.6, C: 2 },
reverseData = _.clone(chart.series[0].data).reverse();
_.each(chart.series[0].data, function (point, pointIndex) {
var plotLine = {},
elem = point.graphic.element.getBBox(),
reverseElem = reverseData[pointIndex].graphic.element.getBBox(),
benchmark = benchmarks[point.category],
yStart = chart.plotTop+reverseElem.x,
xStart = chart.plotLeft+chart.yAxis[0].translate(benchmark),
yEnd = yStart+point.pointWidth-1;
plotLine.path = ["M", xStart, yStart+1, "L", xStart, yEnd];
plotLine.attr = {
'stroke-width': 1,
stroke: "red",
zIndex: 5
};
chart.renderer.path(plotLine.path).attr(plotLine.attr).add();
var margin = 5,
xPadding = 10,
yPadding = 5,
xSplit = xPadding/2,
ySplit = yPadding/2,
text,
box;
text = chart.renderer.text("Top Perf Avg " + benchmark, xStart, yEnd+margin+16).attr({
color: "#646c79",
align: "center",
"font-family": "Arial, sans-serif",
"font-size": 9,
"font-weight": "bold",
style: "text-transform: uppercase",
zIndex: 7
}).add();
box = text.getBBox();
chart.renderer.path(["M", box.x-xSplit, box.y-ySplit,
"l", (box.width/2)+xSplit-margin, 0,
margin, -margin,
margin, margin,
(box.width/2)+xSplit-margin, 0,
0, box.height+yPadding,
-(box.width+xPadding), 0,
0, -(box.height+yPadding)])
.attr({
'stroke-width': 1,
stroke: "#cccccc",
fill: "#ffffff",
zIndex: 6
}).add();
});
}
See the complete working graph here: http://jsfiddle.net/Bh3J4/18/
In the fact, Highcharts rotate everything using transform, so use the same to rotate these lines, see example: http://jsfiddle.net/Bh3J4/19/
function (chart) {
var d = chart.series[0].data,
len = d.length;
for(var i =0; i < len; i++){
var point = d[i],
plotLine = {},
elem = point.graphic.element.getBBox(),
yStart,
xStart,
newline;
console.log(point,point.color);
xStart = point.plotX - point.pointWidth / 2;
yStart = point.plotY;
plotLine.path = ["M", xStart, yStart, "L", xStart+point.pointWidth, yStart];
plotLine.attr = {
transform: 'translate(491,518) rotate(90) scale(-1,1) scale(1 1)',
'stroke-width': 1,
stroke: point.color,
zIndex: 5
};
newline = chart.renderer.path(plotLine.path).attr(plotLine.attr).add();
};
}
Slight adjustment that seems to give precise alignment:
Pls note: changes to calc of xStart/yStart and change to transform translate parameter.
My approach was to make it work for column chart and then get translate refined.
The only unsatisfactory part is that xStart needs: xStart = elem.x+chart.plotLeft; in 'column' mode vs xStart = elem.x; in 'bar' mode...
function (chart) {
var d = chart.series[0].data,
len = d.length;
for(var i =0; i < len; i++){
var point = d[i],
plotLine = {},
elem = point.graphic.element.getBBox(),
yStart,
xStart,
newline;
console.log(point,point.color);
xStart = elem.x;
yStart = chart.plotHeight - (elem.height/2) + chart.plotTop;
plotLine.path = ["M", xStart, yStart, "L", xStart+point.pointWidth, yStart];
plotLine.attr = {
transform: 'translate(542.5,518) rotate(90) scale(-1,1) scale(1 1)',
'stroke-width': 5,
stroke: 'blue',
zIndex: 5
};
newline = chart.renderer.path(plotLine.path).attr(plotLine.attr).add();
};
}

Categories