I have adjusted dygraphs a bit to help with page load times for dygraphs with a large amount of points to draw. To do this I have added a few lines of code to the drawPointsOnLine function around line 1378 of dygraph-combined.js. Instead of just increasing the counter by one (idx++):
if (pointsOnLine.length > 10000){
window.scalingfactor = Math.round(pointsOnLine.length/10000);
idx=idx+window.scalingfactor;
window.scalingfactorset=true;
} else{
idx++;
window.scalingfactor="";
window.scalingfactorset=true;
}
This code seems to be working perfectly for skipping a certain number of points to draw and updates as the dygraph is zoomed, panned and reverted.
The problem I have is with trying to display on the canvas how many points are being skipped. I decided to let the user know how many points are being skipped.
To do this I added in the underlayCallback where the canvas is drawn
if (scalingfactorset) {
scalingfactormsg= "Every "+scalingfactor+" point(s) plotted";
wid=canvas.measureText(scalingfactormsg);
canvas.fillText(scalingfactormsg,area.x+area.w-wid.width-5,area.y+area.h-70);
} else {
if ((<?php echo $lines; ?> -1) >10000) {
firstscalingfactor=Math.round((<?php echo $lines; ?> -1)/10000);
firstscalingfactormsg= "Every "+firstscalingfactor+" point(s) plotted";
wid=canvas.measureText(firstscalingfactormsg);
canvas.fillText(firstscalingfactormsg,area.x+area.w-wid.width-5,area.y+area.h-70);
} else {
firstscalingfactor="";
firstscalingfactormsg= "Every "+firstscalingfactor+" point(s) plotted";
wid=canvas.measureText(firstscalingfactormsg);
canvas.fillText(firstscalingfactormsg,area.x+area.w-wid.width-5,area.y+area.h-70);
}
However since I believe that underlaycallback function is called before points are drawn the scalingfactor will never be drawn as the correct value but the data will be scaled correctly.
At least that is what I would expect, but for some odd reason this will work perfectly as planned for some datsets when zooming panning and reverting to original view the canvas message will update with the correct scaling factor.
For other datasets on the first load it will be correct (I tried to hard code in a first scaling factor to fix this before I found out the supposed order of callbacks) but when you zoom, pan, or return to original view the scalingfator will be stuck to the empty string and display "Every point(s) plotted" even though the data is being scaled by a certain amount.
It seems to work more often for larger datasets than small datasets but there are a few small datasets that still work. I think this might have to do with the way dygraphs handles callbacks. I think if I could force dygraphs to draw the points first everything would be fixed but im not sure if it is possible.
If anyone knows anyway to fix this strange behavior please let me know. In case it matters I set scalingfactorset to false right before I call the dygraph, and the data are in csv format. I have also made sure there are no formatting errors between datasets when the code works and when it does not. Let me know if there is any other helpful info I can provide.
Related
I have been using D3 for about a year, and I have made dashboards in the past without issue. However, this time I have hit a rather strange bump in the road. I have a geographic dashboard with topo JSON and a horizontal bar chart under it that can be found at my gist/block here:
http://bl.ocks.org/diggetybo/77469aa2acec38f1870197724ea671d6
Note you may have to click to view the full view window, the blocks website seems to cap iframe height at 500. (you may disregard all files aside from index.html, the others are just source codes, and my tsv data file)
In the past, my typical dashboard technique is assigning a separate div for each graph of my dashboard. Whereas, the issue at hand here is that there SHOULD be horizontal bars visible under the map of the USA. However, despite much tinkering, the horizontal bars are not displayed. I have still appended them in the same way -- it has always worked for me in the past. So basically, I'm not sure why the horizontal bar chart is not being displayed.
Developer tools report that there are no errors at all -- which is strange. I can clearly tell that there is something wrong, because half of my dashboard is missing.
I have added comments in index.html to indicate where the bar chart portion of the code is. Specifically, lines:159-228.
It might also be helpful to articulate conceptually what the horizontal bar chart is 'doing', just in case. It is supposed to append rects from a variable that has sorted my parsed data from largest to smallest in value (d.fxb in my tsv), and given a fill as per a linear color scale.
UPDATE
I have closed off the style call, and now I'm trouble shooting the other errors. Sadly, I'm getting stuck at the first line of the bar chart portion of the code. Line 160. I have updated the gist, and I will include the relevant lines here for easy reference:
var values = data.fxb.sort(function(a, b) {
return -(a - b);
});
var name_value_array = json.features.name;
var name_values = name_value_array.sort(function(a, b) {
return -(a.value - b.value);
});
I'm getting an error that says: "cannot read property "sort" of undefined. To my knowledge, the array should be defined. This time around I was very careful about making sure everything was enclosed within the proper )} tokens. I triple checked to make sure these lines were still enclosed by the data parsing function. So I'm not sure why that's happening. I do have faith in the bottom portion of the bar chart code, so if someone can spot what is holding me up I would be very grateful.
Thank you
This won't solve all of your problems, but you have failed to close this style call.
.style("fill", function(d) {
// Get data value
var value = d.properties.fxb;
if (value=='None') {
return "#999999"
}
if (value) {
//If value exists…
return color(value);
} else {
//If value is undefined…
return "#999999";
}
You also then end up with an extra }); at the bottom of your script (where the style call finally gets closed)
There are more errors, but that is swallowing them and the reason that they are not showing up.
I'm working on a modification to vis.js's Graph3d to do a filled line graph, like this:
The hard part - unsurprisingly - is working out the rendering order for the polygons. I think I can do this by checking whether a ray from the viewer to a given line B crosses line A:
In this example, since line A is "in the way" of line B, we should draw line A first. I'll use a snippet of code from How do you detect where two line segments intersect? to check whether the lines cross.
However, I haven't figured how to find the position of the user's view. I kind of assumed this would be the camera object, so wrote a little bit of debug code to draw the camera on the graph:
var camera = this._convert3Dto2D(this.camera.getCameraLocation());
ctx.strokeStyle = Math.random()>0.5 ? 'ff0000' : '00ff00';
ctx.beginPath();
ctx.moveTo(camera.x, camera.y);
ctx.lineTo(camera.x, camera.y+5);
ctx.stroke();
In fact, the camera co-ordinates as measured by this are always at 0,0,0 on the graph (which would be the far top right on the above screengrab). What I need, I think, is effectively the bottom of the screen.
How can I find this? Or is there a better way to achieve what I'm trying to do?
I don't know if this is still an active issue, but FWIW, Graph3D has internal handling of the sort ordering.
All graph points are sorted with respect to the viewpoint, using a representative coordinate called point.bottom. The rendering is then done using this ordering, with the most distant elements drawn first. This works fine as long as none of the elements intersect; in that case, you can expect artefacts.
Basically, all you need to do, is define point.bottom per graph polygon, and Graph3D will then pick it up from there.
If you are still interested in working on this:
This happens in Graph3d.js, method Graph3d.prototype._calcTranslations(). For an example, have a look at how the Grid and Surface graph elements are initialized in Graph3d.prototype._getDataPoints(). The relevant code is:
obj = {};
obj.point = point3d;
obj.trans = undefined;
obj.screen = undefined;
obj.bottom = new Point3d(x, y, this.zRange.min);
My attempts to make my y values scale to logarithmic are turning my data upside-down.
I have vanilla js code and every implementation I read about are tied up in huge libraries of production code, I am not sure how/what to extract, I need some guidance as I could not put my finger on the problem weather it be sum or miss-use of the Math functions.
I am testing this by drawing the y 'data' to canvas (no libraries used) my x axis is constant 2px difference
Math.log uses e (2.718) as default base which is what I have read.. So I should be seeing my price data on a natural log scale but it wont work.
function logScale(data){
var log=data.slice(0);
var i=log.length;
while(i--){
log[i]=Math.log(data[i]); // should be natural but I don't see a change
// log[i]=Math.pow(Math.log(data[i]),10); //upside-down
// log[i]=Math.log(data[i])/Math.LN10; //no visible change when drawn to canvas
}
console.dir(log);
return log;
}
Another attempt from a couple of weeks ago where I am using the data min, max and difference. then removing all the infinity.
function ᐥlogarithm(data){
var Lmax,Lmin,Ldif,Logarithm,infinity;
Lmax=Math.max.apply(this,data);
Lmin=Math.min.apply(this,data);
Ldif=(Lmax-Lmin);
Logarithm=[];
infinity=[];
for(var i=data.length-1;i>=0;i--){
Logarithm[i]=Math.log((data[i]-Lmin)/Ldif);
if(Logarithm[i]===-Infinity){infinity.push(i);}
}
for(var i=0;i<=infinity.length-1;i++){Logarithm.splice(infinity[i],1);}
return Logarithm;
}
The data looks different but still not quite like log scale. It is vertically warped to best describe it.
Please note jsfiddle-ing this won't work as The data is bitcoin prices (real time) so as there is no working code for a log scale there is no good way to show a comparison. Bitcoin or any other exchange data gets served as is so these functions would (if they worked) transfom any data array to log scale.
How do D3 do it? What is wrong with my code?
Every example I have found shows all of the scatter plot points to be of random radii. Is it possible to have them all the same size? If I try to statically set the radius all of the circles will be very small (I'm assuming the default radius). However, if I use Math.random() as in most examples there are circles large and small. I want all the circles to be large. Is there a way to do that? Here's the code snippet forming the graph data using Math.random() (this works fine for some reason):
function scatterData(xData, yData)
{
var data = [];
for (var i = 0; i < seismoNames.length; i++)
{
data.push({
key: seismoNames[i],
values: []
});
var xVals=""+xData[i];
xVals=xVals.split(",");
var yVals=""+yData[i];
yVals=yVals.split(",");
for (var j = 0; j < xVals.length; j++)
{
data[i].values.push({
x: xVals[j],
y: yVals[j],
size: Math.random()
});
}
}
return data;
}
Math.random() spits out values between 0 and 1 such as 0.164259538891095 and 0.9842195005008699. I have tried putting these as static values in the 'size' attribute, but no matter what the circles are always really small. Is there something I'm missing?
Update: The NVD3 API has changed, and now uses pointSize, pointSizeDomain, etc. instead of just size. The rest of the logic for exploring the current API without complete documentation still applies.
For NVD3 charts, the idea is that all adjustments you make can be done by calling methods on the chart function itself (or its public components) before calling that function to draw the chart in a specific container element.
For example, in the example you linked too, the chart function was initialized like this:
var chart = nv.models.scatterChart()
.showDistX(true)
.showDistY(true)
.color(d3.scale.category10().range());
chart.xAxis.tickFormat(d3.format('.02f'));
chart.yAxis.tickFormat(d3.format('.02f'));
The .showDistX() and .showDistY() turn on the tick-mark distribution in the axes; .color() sets the series of colours you want to use for the different categories. The next too lines access the default axis objects within the chart and set the number format to be a two-digit decimal. You can play around with these options by clicking on the scatterplot option from the "Live Code" page.
Unfortunately, the makers of the NVD3 charts don't have a complete documentation available yet describing all the other options you can set for each chart. However, you can use the javascript itself to let you find out what methods are available.
Inspecting a NVD3.js chart object to determine options
Open up a web page that loads the d3 and nvd3 library. The live code page on their website works fine. Then open up your developer's console command line (this will depend on your browser, search your help pages if you don't know how yet). Now, create a new nvd3 scatter chart function in memory:
var testChart = nv.models.scatterChart();
On my (Chrome) console, the console will then print out the entire contents of the function you just created. It is interesting, but very long and difficult to interpret at a glance. And most of the code is encapsulated so you can't change it easily. You want to know which properties you can change. So run this code in the next line of your console:
for (keyname in testChart){console.log(keyname + " (" + typeof(testChart[keyname]) + ")");}
The console should now print out neatly the names of all the methods and objects that you can access from that chart function. Some of these will have their own methods and objects you can access; discover what they are by running the same routine, but replacing the testChart with testChart.propertyName, like this:
for (keyname in testChart.xAxis){console.log(keyname + " (" + typeof(testChart.xAxis[keyname]) + ")");}
Back to your problem. The little routine I suggested above doesn't sort the property names in any order, but skimming through the list you should see three options that relate to size (which was the data variable that the examples were using to set radius)
size (function)
sizeDomain (function)
sizeRange (function)
Domain and range are terms used by D3 scales, so that gives me a hint about what they do. Since you don't want to scale the dots, let's start by looking at just the size property. If you type the following in the console:
testChart.size
It should print back the code for that function. It's not terribly informative for what we're interested in, but it does show me that NVD3 follows D3's getter/setter format: if you call .property(value) you set the property to that value, but if you call .property() without any parameters, it will return back the current value of that property.
So to find out what the size property is by default, call the size() method with no parameters:
testChart.size()
It should print out function (d) { return d.size || 1}, which tells us that the default value is a function that looks for a size property in the data, and if it doesn't exist returns the constant 1. More generally, it tells us that the value set by the size method determines how the chart gets the size value from the data. The default should give a constant size if your data has no d.size property, but for good measure you should call chart.size(1); in your initialization code to tell the chart function not to bother trying to determine size from the data and just use a constant value.
Going back to the live code scatterplot can test that out. Edit the code to add in the size call, like this:
var chart = nv.models.scatterChart()
.showDistX(true)
.showDistY(true)
.color(d3.scale.category10().range())
.size(1);
chart.xAxis.tickFormat(d3.format('.02f'));
chart.yAxis.tickFormat(d3.format('.02f'));
Adding that extra call successfully sets all the dots to the same size -- but that size is definitely not 1 pixel, so clearly there is some scaling going on.
First guess for getting bigger dots would be to change chart.size(1) to chart.size(100). Nothing changes, however. The default scale is clearly calculating it's domain based on the data and then outputting to a standard range of sizes. This is why you couldn't get big circles by setting the size value of every data element to 0.99, even if that would create a big circle when some of the data was 0.01 and some was 0.99. Clearly, if you want to change the output size, you're going to have to set the .sizeRange() property on the chart, too.
Calling testChart.sizeRange() in the console to find out the default isn't very informative: the default value is null (nonexistent). So I just made a guess that, same as the D3 linear scale .range() function, the expected input is a two-element array consisting of the max and min values. Since we want a constant, the max and min will be the same. So in the live code I change:
.size(1);
to
.size(1).sizeRange([50,50]);
Now something's happening! But the dots are still pretty small: definitely not 50 pixels in radius, it looks closer to 50 square pixels in area. Having size computed based on the area makes sense when sizing from the data, but that means that to set a constant size you'll need to figure out the approximate area you want: values up to 200 look alright on the example, but the value you choose will depend on the size of your graph and how close your data points are to each other.
--ABR
P.S. I added the NVD3.js tag to your question; be sure to use it as your main tag in the future when asking questions about the NVD3 chart functions.
The radius is measured in pixels. If you set it to a value less than one, yes, you will have a very small circle. Most of the examples that use random numbers also use a scaling factor.
If you want all the circles to have a constant radius you don't need to set the value in the data, just set it when you add the radius attribute.
Not sure which tutorials you were looking at, but start here: https://github.com/mbostock/d3/wiki/Tutorials
The example "Three little circles" does a good step-by-step of the different things you can do with circles:
http://mbostock.github.io/d3/tutorial/circle.html
I am drawing a Polyline with a fairly large number of lat/lng points (~ 1000). I have two sliders (start and end) that allow the user to adjust the time bounds which then updates the Polyline to show the data between those two times.
My update algorithm goes something like this:
var mvcPath = new google.maps.MVCArray();
for (var i = 0; i < gpsData.length; i++) {
if (gpsData[i]['timestamp'] <= endDate &&
gpsData[i]['timestamp'] >= startDate) {
mvcPath.push(gpsData[i]['location']);
}
}
this.path.setPath(mvcPath);
Now the weird thing is, when I drag the end slider the line redraws as expected, however when I drag the start slider, it redraws the line correctly except at high zoom levels parts of the line seem to move slightly (it doesn't do this when zoomed in close). I thought it might be something to do with the anti alias algorithm Google applies to the Polyline but it doesn't do it when I move the end slider.
Anyone know what is causing this flickering?
I've hacked a solution which seems to work for now. I tried to keeping the number of points consistent as the API didn't seem to like me adding points to the front.
Lets say I have a set of 1000 points and I only want to show from 200 - 900. If I draw the location of point 200, 200 times then draw the rest of the data to point 900, it stops the flickering. To display 10 - 330 I would draw point 10, 10 times then draw the rest of the data to point 330.
I guess this has something to the way Google internally stores the lines of the map, if you add to the front of the line it may have to re-index a whole array and redraw the line from scratch.
I won't accept the answer for now incase someone comes up with a better answer.