Recale plotly js z axis on zoom - javascript

plotly.js 2D histograms and contour plots automatically generate a z-axis range that accommodates the entire range of z values in the dataset being plotted. This is fine to start, but when I click-and-drag on the plot to zoom in, I'd like the z axis range to also zoom in to accommodate only the range of z values currently on display; instead, the z axis never changes. Here's a codepen (forked from the plotly examples, thanks plotly) to play around with: http://codepen.io/anon/pen/MKGyJP
(codepen code inline:
var x = [];
var y = [];
for (var i = 0; i < 500; i ++) {
x[i] = Math.random();
y[i] = Math.random() + 1;
}
var data = [
{
x: x,
y: y,
type: 'histogram2d'
}
];
Plotly.newPlot('myDiv', data);
)
This seems like pretty conventional behavior - am I missing an option in the docs somewhere to do this?
If there's no built-in option to do this, an acceptable alternative solution would be to manually set new z limits in a zoom callback, which is easy enough to implement per this example: http://codepen.io/plotly/pen/dogexw - in which case my question becomes, is there a convenience method to get the min and max z currently on display?
Thanks in advance,

plotly.js doesn't have a zoom-specific callback at the moment(follow this issue for updates).
One alternative would be to add a mode bar button updating the colorscale range:
Plotly.newPlot('myDiv', data, {}, {
modeBarButtonsToAdd: [{
name: 'click here to update the colorscale range',
click: function(graphData) {
var xRange = graphData.layout.xaxis.range,
yRange = graphData.layout.yaxis.range;
var zMin, zMax;
// code that would compute the colorscale range
// given xRange and yRange
// for example given these values:
zMin = 10;
zMax = 20;
Plotly.restyle('myDiv', {zmin: zMin, zmax: zMax});
}
}]
});
Complete example: http://codepen.io/etpinard/pen/JGvNjV

Related

how to plot two points on y axis for single x axis value (Maybe date) using plotly js

I have data set like the one shown below. For each date, I have one or more points to plot on the y-axis.
x axis (y axis) - 2021-07-01 (20, 30)
x axis (y axis) - 2021-07-02 (20)
x axis (y axis) - 2021-07-04 (10, 50)
x axis (y axis) - 2021-07-06 (40)
How to plot this data using Plotly js
If you have one or more y-values for each x-value (say in the form of a 2D array), you can loop through this array to flatten it, and repeat the necessary x-values from the original x-value array. The codepen for this is here.
var x = ["2021-07-01","2021-07-02","2021-07-04","2021-07-06"]
var y = [[20, 30],[20],[10,50],[40]]
var x_plot = []
var y_plot = []
for (let i=0; i<y.length; i++) {
for (let j=0; j<y[i].length; j++) {
x_plot.push(x[i])
y_plot.push(y[i][j])
}
}
var trace1 = {
x: x_plot,
y: y_plot,
type: 'scatter',
mode: 'markers'
}
var data = [trace1];
Plotly.newPlot('myDiv', data);
I got answer from code pen.
I think this may be useful for others as well.
If we have two y values for each x value we can duplicate x values to match y values like the one show below.
var data = [
{
x: ['2013-10-04', '2013-10-04', '2014-11-04'],
y: [1, 3, 6],
type: 'scatter'
}
];
Plotly.newPlot('myDiv', data);

Plotting a Latitude and Longitude Coordinate in SVG

Summary
I'm trying to read a .SCT file which is a custom file type that is created by a program named VRC. I use this file to plot lat long coordinates within my source area. The issue I am having is that most of the lat long coordinates are not being mapped correctly. It appears that most coordinates are mapping to some arbitrary point.
Background
Here is a sample of the code I'm currently using to convert and plot them into the SVG.js canvas.
CODE [index.js (at least the active part)]:
var coor = [];
let fixed = [];
var dms2dd = function(object) {
var deg = parseFloat(object.degrees),
min = parseFloat(object.minutes),
sec = parseFloat(object.seconds),
dir = object.dir == "N" || object.dir == "E" ? 1 : -1;
return dir*(deg+(min/60.0)+(sec/3600.0));
};
function llToXY(arr) {
mapWidth = 1000;
mapHeight = 1000;
// get x value
x = (arr[1]+180)*(mapWidth/360)
// convert from degrees to radians
latRad = arr[0]*Math.PI/180;
// get y value
mercN = Math.log(Math.tan((Math.PI/4)+(latRad/2)));
y = (mapHeight/2)-(mapWidth*mercN/(2*Math.PI));
return [x, y];
}
lineReader.eachLine('test.txt', function(line, last) {
let data = line.split(" ");
let coor_length = coor.length;
if (line[0] != " ") {
coor.push({
type: data[0],
coordinates: []
});
// Convert to DD
/*let direction = data[1].substr(0, 1);
let dms = data[1].split(".")
dms2dd({
})*/
data.splice(0, 1);
coor[coor.length - 1]["coordinates"].push(data.join(" "));
} else {
coor[coor_length - 1]["coordinates"].push(line.trim())
}
if (last) {
coor.forEach((data, index) => {
for (coordinate_pair in data["coordinates"]) {
let pair = data["coordinates"][coordinate_pair];
pair = pair.split(" ");
let x_data = pair[0].split("."),
y_data = pair[1].split(".");
let x = dms2dd({
degrees: x_data[0].substring(1),
minutes: parseFloat(x_data[1]),
seconds: parseFloat(`${x_data[2]}.${x_data[3]}`),
dir: x_data[0].substr(0,1)
});
let y = dms2dd({
degrees: y_data[0].substring(1),
minutes: parseFloat(y_data[1]),
seconds: parseFloat(`${y_data[2]}.${y_data[3]}`),
dir: y_data[0].substr(0,1)
});
console.log([x, y]);
coor[index]["coordinates"][coordinate_pair] = llToXY([x, y]);
}
})
return false;
}
});
Drawing Code
let draw = SVG("drawing").size(1000, 1000).panZoom();
let cp = <%- JSON.stringify(cp) %>;
//var line = draw.plot([32.737396,117.204284], [32.736862,117.204468], [32.737396,117.204284], [32.736862,117.204468]).stroke({ width: 1 })
// var line = draw.polyline().fill("none").stroke({width: 0.00005});
// line.transform({
// scale: 50000
// }).transform({rotation: 104.5});
cp.forEach((data)=> {
//draw.polyline(data.coordinates.join(" "))
//llToXY(data.coordinates);
draw.polyline(data.coordinates.join(" ")).fill("none").stroke({width: 0.00005}).transform({scale: 50000}).transform({rotation: -15.80})
});
This code is basically reading from a text file line-by-line and inserting the data into a variable named coor. Once the code reaches the last line, it will convert all coordinates i.e. coor values to decimal degrees.
Unfortunately, the library is not compatible with JSFiddle so I couldn't make a test scenario. I've attached all the necessary files, so you can run locally.
My main concern is that all coordinates are mapping to some arbitrary point.
Sources
This is what the image should look like
What it currently looks like:
VRC Sector File Documentation: http://www1.metacraft.com/VRC/docs/doc.php?page=appendix_g
StackOverflow Question Referenced: Convert latitude/longitude point to a pixels (x,y) on mercator projection
Library Used
svg.js: https://svgjs.dev/
svg.panzoom.js: https://github.com/svgdotjs/svg.panzoom.js
That is actually an issue with bad documentation for the SVG.js library. If you define a transformation as
element.transform({ scale: 30000 })
the result is not a transform attribute with the value scale(30000), which would mean an origin for the scaling at point (0, 0), but a transform matrix that is equivalent to a scaling around the center of the element bounding box.
In your code, each partial shape is drawn as a separate polyline, and is separately scaled around its individual center. The element, before the scaling, is extremely small, and all elements are as closely grouped together as to be virtually at one point. If they are scaled , the result looks like all elements have that point as one common center at their new size.
The most obvious solution is to scale the elements not around their individual center, but around one constant value:
const cx = ..., cy = ...
element.transform({ scale: 30000, cx, cy })
What that value is is not immediately clear. It would be a point that is in the center of the common bounding box of all polylines. How do you get at that? Let the library do the work for you.
If you add all polylines as childs of a <g> element, you can scale that group, and if you leave out values for the center, they will be computed for you:
let draw = SVG("drawing").size(1000, 1000).panZoom();
let g = draw.group();
let cp = <%- JSON.stringify(cp) %>;
cp.forEach((data) => {
group.polyline(data.coordinates.join(" "))
.fill("none")
.stroke({width: 0.00005});
});
group.transform({scale: 50000}).transform({rotation: -15.80});
The above solution is good if you want to get your resulting map at a defined size. If you want to find a scale value that actually lets the content fill your canvas from side to side, it is just as simple: you can get the bounding box of of the group, and set them as a viewbox on the <svg> element. The browser will then take care to scale that box to fit the canvas.
let draw = SVG("drawing").size(1000, 1000).panZoom();
let g = draw.group();
let cp = <%- JSON.stringify(cp) %>;
cp.forEach((data) => {
group.polyline(data.coordinates.join(" "))
.fill("none")
.stroke({width: 0.00005});
});
const { x, y, w, h } = group.bbox();
draw.viewbox(x, y w, h);

Handling large datasets in dimple.js to render a chart

I wish to draw charts for large datasets using dimple.js. My code works absolutely fine. But the only problem is that the chart takes more than 45 seconds to come up. I am looking for some kind of an optimization in my code to reduce the time taken for the rendering of the chart. The following is the code for my area chart:
var dataset = [];
// The arrays xpoints and ypoints are populated dynamically
// with hundreds of thousands of points
var xpoints = chartData["xdata"];
var ypoints = chartData["ydata"];
var area1;
var svg = dimple.newSvg("#" + mychart, 700, 600);
var x, y;
for (var i = 0; i < xpoints.length; i++)
dataset.push({
x : xpoints[i],
y1 : parseFloat(ypoints[i])
});
var myChart = new dimple.chart(svg, dataset);
myChart.setBounds(75, 30, 480, 330);
y = myChart.addMeasureAxis("y", "y1");
x = myChart.addCategoryAxis("x", "x");
area1 = myChart.addSeries("First", dimple.plot.area, [ x, y ]);
var l = myChart.addLegend(65, 10, 510, 20, "right");
myChart.draw(1500);
Is there some way to optimize this code in either dimple.js itself or maybe using d3.js?
I'm afraid Dimple is not very performant for hundreds of thousands of points. It's drawing logic is built for flexibility and for cases like this you need to write specific d3 code (think of Dimple as a Swiss-Army Knife but here you need a scalpel). Even with raw d3 you might run into problems with a path containing that number of points. Certainly try raw d3 but you might need to write some more complex additional logic to average every n points together and then fill in detail on zoom. Also remember that even with perfect client code you will suffer a noticeable wait simply getting that volume of data from the server.
I found a solution!!. I was adamant on using dimple.js itself and not raw d3.
What I did was I aggregated the values first and then passed them to the chart.draw() function
The time taken to render the graph now is reduced from 40 seconds to 12 seconds, which is much better.
For now, my aggregation function just sums up the values for a particular category. Maybe the implementation in the draw() function is a little more complex and is therefore taking extra time. xpoints[] and ypoints[] are my arrays with lakhs of points.
Earlier, I just did this:
dataset.push({
x : xpoints[i],
y1 : parseFloat(ypoints[i])
});
Now, I first apply an aggregation as follows:
var isPresent = false;
for (var j = 0; j < unique_x.length; j++) {
if (xpoints[i] == unique_x[j]) {
y_val = parseFloat(ypoints[i]);
if (isNaN(y_val)) {
y_val = 0;
}
y_sum[j] = y_sum[j] + y_val;
isPresent = true;
break;
}
}
if (isPresent == false) {
unique_x.push(xpoints[i]);
y_sum.push(parseFloat(ypoints[i]));
}
Then, I do this:
for (var i = 0; i < unique_x.length; i++) {
dataset.push({
x : unique_x[i],
y1 : y_sum[i]
});

Canvas HTML5 datapoints to arrays

I hope someone can guide me on how to convert long data points into arrays as i have a long list to plot and i hope of an easier way to loop instead of typing x 50 times.
Currently, i have data points where x increment of +.25 and y is calculated from a formula below.
Example:
dataPoints: [
{ x: 0, y: 1000*(0.5/(50*0.6))* (Math.exp(-((6)/(50*0.6)*0))) }
];
Link to demo: http://jsfiddle.net/QwZuf/95/
Thank you!
You just need a simple for loop:
var dataPoints = [];
for (var x = 0; x <= 12.5; x += 0.25) {
dataPoints.push({
x: x,
y: 1000*(0.5/(50*0.6))* (Math.exp(-((6)/(50*0.6)*x)))
});
}
and then pass that array as the dataPoints parameter to the plotting function.
See http://jsfiddle.net/alnitak/xQpv7/

Make circles not go outside of the chart bounds with D3

I am working on a chart looking like this now:
I use d3 scales and ranges to setup sizes and coordinates of circles, from JSON data.
All works fine but I need to make sure those circles that are close to extreme values don't overlap the sides of the chart (like orange circle on the top right and blue one on the bottom side), so I think I need to play with ranges and change coordinates in case they overlap or is there a better tried way to do this?
When drawing circles, in addition to the x and y scaling functions we also use an r scaling function:
var rScale = d3.scale.linear()
.domain([0, maxR])
.range([0, maxBubbleRadius]);
var xScale = d3.scale.linear()
.domain([minX, maxX])
.range([0, chartWidth]);
var yScale = d3.scale.linear()
.domain([minY, maxY])
.range([chartHeight, 0]);
where maxR is the largest r value in your dataset and maxBubbleRadius is however large you want the largest circle to be, when you plot it.
Using the x and y scaling functions it is easy to calculate where the centre of each circle will be plotted, we can then add on the (scaled) r value to see if the circle will spill over a chart boundary. With a scenario like the first chart below we can see that 4 of the circles spill over. The first step to remedy this is to find out how many vertical and horizontal units we spill over by and then increase the minimum and maximum x and y values to take this into account, before recalculating the xScale and yScale vars. If we were to then plot the chart again, the boundary would move out but there would probably still be some visible spillage (depending on actual values used); this is because the radius for a given circle is a fixed number of pixels and will therefore take up a different number of x and y units on the chart, from when we initially calculated how much it spilled over. We therefore need to take an iterative approach and keep applying the above logic until we get to where we want to be.
The code below shows how I iteratively achieve an acceptable scaling factor so that all the circles will plot without spilling. Note that I do this 10 times (as seen in the loop) - I've just found that this number works well for all the data that I've plotted so far. Ideally though, I should calculate a delta (the amount of spillage) and iterate until it is zero (this would also require overshooting on the first iteration, else we'd never reach our solution!).
updateXYScalesBasedOnBubbleEdges = function() {
var bubbleEdgePixels = [];
// find out where the edges of each bubble will be, in terms of pixels
for (var i = 0; i < dataLength; i++) {
var rPixels = rScale(_data[i].r),
rInTermsOfX = Math.abs(minX - xScale.invert(rPixels)),
rInTermsOfY = Math.abs(maxY - yScale.invert(rPixels));
var upperPixelsY = _data[i].y + rInTermsOfY;
var lowerPixelsY = _data[i].y - rInTermsOfY;
var upperPixelsX = _data[i].x + rInTermsOfX;
var lowerPixelsX = _data[i].x - rInTermsOfX;
bubbleEdgePixels.push({
highX: upperPixelsX,
highY: upperPixelsY,
lowX: lowerPixelsX,
lowY: lowerPixelsY
});
}
var minEdgeX = d3.min(bubbleEdgePixels, function(d) {
return d.lowX;
});
var maxEdgeX = d3.max(bubbleEdgePixels, function(d) {
return d.highX;
});
var minEdgeY = d3.min(bubbleEdgePixels, function(d) {
return d.lowY;
});
var maxEdgeY = d3.max(bubbleEdgePixels, function(d) {
return d.highY;
});
maxY = maxEdgeY;
minY = minEdgeY;
maxX = maxEdgeX;
minX = minEdgeX;
// redefine the X Y scaling functions, now that we have this new information
xScale = d3.scale.linear()
.domain([minX, maxX])
.range([0, chartWidth]);
yScale = d3.scale.linear()
.domain([minY, maxY])
.range([chartHeight, 0]);
};
// TODO: break if delta is small, rather than a specific number of interations
for (var scaleCount = 0; scaleCount < 10; scaleCount++) {
updateXYScalesBasedOnBubbleEdges();
}
}

Categories