Using d3.js, how can I display data faster on my chart? - javascript

In my code, I am loading a JSON with 508 entries on a line chart. This JSON contains data emitted by some machines, and the keys are the names of the machines.
This is the structure of my JSON:
{
"AF3":3605.1496928113393,
"AF4":-6000.4375230516,
"F3":1700.3827875419374,
"F4":4822.544985821321,
"F7":4903.330735023786,
"F8":824.4048714773611,
"FC5":3259.4071092472655,
"FC6":4248.067359141752,
"O1":3714.5106599153364,
"O2":697.2904723891061,
"P7":522.7300768483767,
"P8":4050.79490288753,
"T7":2939.896657485737,
"T8":9.551935316881588
}
I am currently reading the data with the help of a counter called cont, however, the code that I'm using takes too long to draw the graph:
data.length=508
if (data.length>cont)
cont++`
for (var name in groups) {
var group = groups[name]
group.data.push(aData[cont][name])
group.path.attr('d', line)
console.log(cont)
}
As you can see in the gif above, my code is taking too long to plot all the data points. I would like to draw all the data elements of my data set (in this case 508) without delay, for example:
data=[{508 elements}];
tick(data)=> draw the points in the graph at the same time, by dataset.
data2=[{50 elements}];
tick(data)=> draw the points in the graph at the same time, by dataset.
Where tick is the name of the function that would draw the coordinates, without losing the sense of animation.
How can do it?
Here is a link to my code:
http://plnkr.co/edit/y8h9zs1CpLU1BZRoWZi4?p=preview

It seems to me that your problem is the fact that the graph is synchronous - "duration" is both used for animation and for graph shifting. Essentially, changing duration will avail nothing.
You can introduce a time multiplier. Then try dividing duration by two, and using a multiplier of 2. Your actual data duration is now duration*timeMultiplier (you might want to change the names to make it less confusing, or use a timeDivider in the animation).
// Shift domain
x.domain([now - (limit - 2) * duration * timeMultiplier, now - duration * timeMultiplier])
// Slide x-axis left
axis.transition()
.duration(duration)
.ease('linear')
.call(x.axis);
// Slide paths left
var t = paths.attr('transform', null)
.transition()
.duration(duration)
.ease('linear')
t.attr('transform', 'translate(' + x(now - (limit - 1) * duration * timeMultiplier) + ')')
.each('end', tick)
Another thing you might try is to add the points two at a time, i.e. you skip the shift on odd ticks, and shift double the amount on even ticks. This reduces the overhead at the expense of making the animation a bit jaggier (but not very much, because it also plays faster).

Related

Bubble chart c3js

Following the c3js documentation there is no option for Bubble chart. One workaround for that is to setup scatter plot and specify point radius, but all of the bubbles will be the same height.
point = {
r: function(d) {
var num = d.value;
return num
},
Adding the value of axis inside the r solve the problem, but now the problem is how to setup very high or very low values ? For e.g if there is 1 000 000 value the whole chart will be colored. Is there any simple workarounds for that ?
First of all, set r to return the square root of your chosen variable e.g. return sqrt(num), that way a circle representing a data point 100 times the size of another has 100, not 10,000, times the area (area=pi r2 and all that)
If the numbers are still too big use a linear scale to restrict them to a usable size:
rscale = d3.scale.linear().domain([1,1000]).range([0,10])
and then return rscale(sqrt(num))
If your problem is to represent large and small values on the same chart so small values don't disappear and large values don't exceed the chart size look at using a d3 log scale:
rscale = d3.scale.log().base(10).domain([1,1000]).range([0,10])
Of course on a log scale the areas aren't linearly proportionate any more so whether the sqrt step is necessary is debatable. If you don't just remember to adjust the domain to account for this - change it to domain([1,1000000])
if you don't know the size of your numbers beforehand it will be worthwhile looping through your dataset to pick out the min and max to plug into the domain value: domain([your_min, your_max]). my examples above all assume a max of one million.
Here's an example I forked on jsfiddle, numbers from a few hundred to over a hundred thousand are displayed using a log scale and all are visible but the differences are still obvious:
http://jsfiddle.net/m9gcno5n/

d3.js Force layout only applied vertically

I'm doing a data visualisation with d3. To give you some context,
the graph contains about 400 nodes (all data is loaded from multiple
json files) that are connected to each other
they are all mapped by year in a timeline (x axis)
the position in the y axis is completely randomized
the nodes have all different sizes
Now my question:
How can I distribute the nodes in the y axis so that they don't overlap?
You can checkout the full sourcecode on the GitHub Repository (work in progress - currently on the real-database branch).
This is a screenshot of how it currently looks:
Basically, in the tick() function, reset the nodes array x values to what you want them to be (presumably some scale to do with year), and the node and links will be drawn at those x values, and subsequent force calculations will start again from those values too
force.on("tick", function() {
// Adjust to what you want nodePos to be, here I'm just doing it by index
graph.nodes.forEach (function(nodePos,i) {
nodePos.x = i * 15;
//nodePos.x = xscale (data[i].year); // or whatever
})
// then normal node/link layout
I've forked this standard force-directed example by blt909 to show one way it could be done -->
http://jsfiddle.net/vztydams/
PS If you have a lot of items and very few discrete x values, best to give them a bit of wiggle room at first (i.e. a range in x they're contained to rather than a value), then slowly narrow that range down. Otherwise nodes will get 'stuck' behind each other.
Edit 02/03/16:
Hi Alvaro, essentially the graph.nodes is your linked data, as these are the objects that are attached to the displayed nodes as the data.
So if I set up a scale, and stick in a random year per datum:
var dom = [1994,2014];
var xscale = d3.scale.linear().domain(dom).range([20,400]);
graph.nodes.forEach (function(datum) {
datum.year = dom[0] + Math.floor (Math.random() * (dom[1] - dom[0]));
});
...
We can then restrict the x position of each node's datum like this:
graph.nodes.forEach (function(d,i) {
//d.x = i * 15;
d.x = xscale(d.year);
})
(As I say, if you have a lot of nodes and few years, you'd be better restricting to a range and then narrowing that range down on each subsequent tick)
http://jsfiddle.net/vztydams/2/

Highstock gapsize is causing line rendering issue

I'm using Highstock (v4.2.3) to present data in a StockChart with a number of different Y axes, all plotted against time on the X axis. The data has gaps in it, and I'd like to depict those gaps, but when I turn on gapSize (with any value other than zero), there's a weird quirk that causes line rendering issues--when using the navigator to zoom in on certain date ranges (not all), in some cases (whose pattern I've yet to discern) the chart fails to fully render the line across the entire x axis.
This annotated screenshot depicts the issue.
When I turn gapSize off (or explicitly set it to zero), this problem goes away. Note that the gaps themselves appear correctly on the chart (when navigating to a date range that doesn't present the line rendering issue).
plotOptions: {
series: {gapSize:2}
}
Any ideas?
jsFiddle with your issue:
http://jsfiddle.net/2N52H/109/
As you can read in our API:
http://api.highcharts.com/highstock#plotOptions.line.gapSize
A gap size of 5 means that if the distance between two points is
greater than five times that of the two closest points, the graph will
be broken
As far as I know data you have has random gaps so you will never know what is the distance between two closest points. For example if you will have data in every one hour, distance between two closest points will be 15 minutes and your gapSize will be set to 2, you will see only your closest points.
When you are using zoom sometimes your visible data closest distance is changing so the gaps are changing as well.
See this example:
http://jsfiddle.net/2N52H/111/
Maybe you can use xAxis.ordinal parameter to visualise your gaps:
http://api.highcharts.com/highstock#xAxis.ordinal
You can also change standard functionallity by using wrapper. Here you can read about it:
http://www.highcharts.com/docs/extending-highcharts/extending-highcharts
For example you can change gappedPath function:
(function(H) {
H.wrap(H.Series.prototype, 'gappedPath', function(proceed) {
var gapSize = this.options.gapSize,
xAxis = this.xAxis,
points = this.points.slice(),
i = points.length - 1;
if (gapSize && i > 0) { // #5008
// extension for ordinal breaks
while (i--) {
if (points[i + 1].x - points[i].x > gapSize) {
points.splice( // insert after this one
i + 1,
0, {
isNull: true
}
);
}
}
}
return this.getGraphPath(points);
})
}(Highcharts))
example:
http://jsfiddle.net/2N52H/113/
Kind regards.

Distance between clusters in d3 multi foci force layout

I'm trying to understand this example of a multi-foci force layout...
http://bl.ocks.org/mbostock/1804919
It contains a "padding" variable (which defines the distance between nodes in each cluster), but I'm really struggling to see where the definition of the distances between clusters comes from - a combination of gravity and charge maybe?!
For example, if you wanted to double the distance between the clusters how could you achieve that? I've played with the customisable values in the example to no avail so would really appreciate any help from anyone. Thanks!
Setting m to a smaller value will increase the distance between clusters. This is the domain for the orginal scale x which is used to initialise the cx values on the elements of the nodes array.
Calling them cx and cy is a little bit confusing because they are not the cx and cy attributes of the circles.
The cx value for each node is is determined by feeding a random integer into the ordinal scale x By the statement cx: x(i).
The circles are moved toward the focii by calling gravity every tick (animation frame). This is done in the following statement...
d.x += (d.cx - d.x)*alpha
alpha is fixed at 0.5 in the tick function when calling gravity, but there is another alpha which could also be used. It's a variable maintained by the force object which is initially set to 0.1 before the first tick, and reduced by 1% every tick. It's passed to the tick function as e.alpha, so you could also try passing this value into the gravity function instead of a constant 0.5. The effect will be to cause the nodes to "cool down" more and more gradually until they stop.

d3.js Throbbing marker points on a map

I'm building an application that will plot markers on a map - and have alarm rings animating smoothly from the markers.
The markers will have the following properties
size
x coordinate
y coordinate
alarm rating
if the alarm rating is low - I want the rings to throb very slowly, if its high to throb faster and maybe go out further.
This will be used in a dating app at some point, so like alarm rating will represent people urgently looking to find another person to date. Be good if the map falls on the users current location and an urgent user's rings just come into view.
Here is the latest fiddle, http://jsfiddle.net/NYEaX/367/
This is what I am aiming to build - http://demo.joostkiens.com/het-parool-4g/
function makeRings() {
var datapoints = circleGroup.selectAll("circle");
var radius = 1;
function myTransition(circleData){
var transition = d3.select(this).transition();
speedLineGroup.append("circle")
.attr({
"class": "ring",
"fill":"red",
"stroke":"red",
"cx": circleData.xcoord,
"cy": circleData.ycoord,
"r":radius,
"opacity": 0.4,
"fill-opacity":0.1
})
.transition()
.duration(function(){
return 100*circleData.alarmLevel;
})
.attr("r", radius + 100 )
.attr("opacity", 0)
.remove();
transition.each('end', myTransition);
}
datapoints.each(myTransition);
}
Here is some code/concepts that may help
window.setInterval(makeRings, 1000);
function makeRings() {
datapoints.each(function(circleData){
//datapoints is your d3 selection of circle elements
speedLineGroup.append("circle")
.attr({"class": "ring",
"fill":"red", //or use CSS to set fill and stroke styles
"stroke":"red",
"cx": circleData.xCoord,
"cy": circleData.yCoord,
//position according to this circle's position
"r":radius, //starting radius,
//set according to the radius used for data points
"opacity": 0.8, //starting opacity
"fill-opacity":0.5 //fill will always be half of the overall opacity
})
.transition()
.duration( intensityTimeScale(circleData.intensity) )
//Use an appropriate linear scale to set the time it takes for
//the circles to expand to their maximum radius.
//Note that you *don't* use function(d){}, since we're using the data
//passed to the .each function from the data point, not data
//attached to the ring
.attr("r", radius + intensityRadiusScale(circleData.intensity) )
//transition radius
//again, create an appropriate linear scale
.attr("opacity", 0) //transition opacity
.remove(); //remove when transition is complete
});
}
function myTransition(d){
var transition = d3.select(this).transition();
//Forward transition behavior goes here
//Probably create a new circle, expand all circles, fade out last circle
transition.each('end', myTransition); //This calls the backward transition
}
d3.select('myFlashingElement').each(myTransition);
Use 'setInterval' to call a function on a regular basis (e.g., once
or twice per second) that will create a new ring around each data
circle.
Create the rings using an .each() call on your data circles, but add
them to a different element, and/or with different class names
so there is no confusion between the rings and the data points.
Set the initial radius of the ring to be the same as the data point,
but then immediately start a transition on it. Make the duration of
the transition a function of the "intensity" data value for the
associated data circle, and also make the final radius a function of
that data value. Also transition the opacity to a value of 0.
Make the final line of the transition for the rings .remove() so
that each ring removes itself after it has finished expanding.
create looping transitions in d3 is to use the end callback on transitions. Create two functions, which each create a transition on your data, with one going from your start point to your end point, and the other going back, and have them call each other on completion, like so:
This seems to closely match the example. http://jsfiddle.net/NYEaX/468/
Here are the settings I've used.
function getDurationPerDot(circleData){
var totalTime = 3000;//3 seconds max
var time = totalTime-(circleData.alarmLevel*10)
return time;
}
function getOuterRadiusPerDot(circleData){
var radius = circleData.alarmLevel*.5;
return radius;
}

Categories