According to the d3 docs:
When interpolating to or from zero, some interpolated values may be very small. JavaScript formats small numbers in exponential notation, which unfortunately is not supported by CSS. For example, when transitioning opacity to fade in or out, the number 0.0000001 is converted to the string "1e-7" and then ignored, giving the default value of 1! To avoid distracting flicker, start or end the transition at 1e-6 rather than 0; this is the smallest value not formatted in exponential notation.
This is giving me a problem with a histogram which has some very low frequencies. The rectangle height is interpolated as a scientific number which throws an error. I've tried the following:
svg.selectAll(".bar")
.data(freq)
.filter(function(d) {return d.freq>0.005})
.transition()
.duration(1000)
.attr("y", function(d) { return y(d.freq); })
.attr("height", function(d) { return height - y(d.freq) })
This avoids the end value being zero - but how do I filter out those elements where the initial value may be close to zero?
You can do the same thing when you're creating the bars, i.e.
svg.selectAll("rect").data(freq).filter(...)
.enter().append("rect");
At this point, it would actually make sense to prefilter your data before passing it to D3 at all, i.e. use something like var filteredData = data.filter(...) and use filteredData.
Related
I want to make a scale that is linear between 50 and 100, but if any outlier value is below 50 or above 100, gets coerced to a value of either 50 or 100, respectively, so that it doesn't fall off the visible range of my chart. (The plan is then to style those data points differently so that the user knows they're either more than or less than what they appear to be.)
How would I go about make a linear piecewise scale in D3 that does this? Or is it easier just to make a "gatekeeper" function that gets called directly by the SVG's y attribute, does those coercions manually, and then calls a regularly linear scale with "in-range" values, so that "out-of-range" values are never sent to the regular linear scale in the first place?
I think you are over complicating this.
I'm sure you are using a d3.line function, just cap the values in the y accessor:
var line = d3.line()
.x(function(d) { return x(d.x); })
.y(function(d) {
if (d.y > 150) return y(150);
else if (d.y < 50) return y(50);
else return y(d.y);
});
Actually, I just realized that linear.clamp(true) does exactly what I wanted.
I'm interested in graphing live-ish data in D3js. Now, when I say "live-ish" I mean that I'll be collecting data every 200ms +/- 10ms, but there may be several minute long periods of inactivity. Fortunately, the input data is time-stamped!
What I have so far: I've followed some line drawing in d3 guides (eg: this) and I have a Y axis with the value range/domain I want. I have an X axis with the range I want and a moving domain as per a standard time-series fixed-width graph. That is, if my graph's x axis domain is (0:15, 0:35) in 5 seconds it will be (0:20, 0:40). This transitions nicely as it's using linear easing.
I have mock-data being output each iteration of the graph tick. My domain is set up as such that new points are just out of the x-axis domain such as to allow the smooth effect as per 1. All in all, it looks great.
So where do I go from here? My desired result: data comes in asynchronously and is placed precisely at its x-axis time-stamped location. If data is up to date, it gets placed juuust outside the x-axis domain and has a smooth transition in. If data doesn't arrive in time, the graph continues without drawing any new points until data is received, at which time it adds each point at its appropriate time-stamp retroactively. If data for the missing period doesn't arrive at all, we just continue with a gap in the graph. I can emulate this by calling...
d3.select(window).on("click", .. )
Effectively, I can click to add random data at the current time-stamp using some anonymous function which allows me to mimic the data / event structure my code should handle.
I think my current confusion is due to how I add data and draw the path from it.
var line = d3.svg.line()
.interpolate("basis-open")
.x(function(d, i) { return x(now - (n-1-i)*duration); })
.y(function(d, i) { return y(d); });
var axis = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(x.axis = d3.svg.axis().scale(x).orient("bottom"));
svg.append("g")
.attr("class", "y axis")
.call(d3.svg.axis().scale(y).orient("left"));
var path = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.datum(data)
.attr("class", "line")
The big question: my y-values correspond to the path.datum(data) (data is just an array of values) appropriately, but when I push to the data array to draw the line, it always places each point graphically at equal distance apart. How do I break out of the mono-variable graph without destroying my time-series smooth scrolling animation? I could record a second array of timestamps alongside my data array, but how to I integrate those into the line? Ideally, I'd have them both be a part of the same array so I could sort by timestamp so when I call data.shift() truly the oldest data is gone. I tried changing the "duration" of the transition function but it made the graph accelerate weirdly and didn't actually break the equidistance of points on x.
How do I set up the y-axis graph to also take into account x-location without breaking my graph?
Alright, so I figured out a pretty straightforward way of doing exactly what I wanted while still using .datum(data) instead of .data(data) (where 'data' is my array). Instead of passing in an array of values to .datum, I pass in an array of objects. Or, what was once data = [value1, value2, ...] is now data = [{gx:timestamp1, gy:value1}, {gx:timestamp2, gy:value2}, ...].
My line x-axis / y-axis functions are now...
.x(function(d, i) { return x(d.gx); })
.y(function(d, i) { return y(d.gy); });
Which ends up being a bit neater than my initial run at it. My transition functions didn't have to change.
The final puzzle piece was what to do about data that comes in time-stamped but out of order. Fortunately, my display domain isn't very large and thus, I don't need to store many values in the array. As such, when new data comes in I simply sort it to make the line not a wobbly-bobbly mess.
data.sort(function(a,b){ return a.gx - b.gx});
And voila! If the size of the array is beyond the boundary, shift off data. This gives us the effect of a sliding timeseries window of the past n seconds where time-stamped data points may be arbitrarily dumped on and displayed properly.
I'm building my first line graph in d3:
http://jsfiddle.net/j94RZ/
I want to know how to utilize either the scale or axis allow me to draw a grid (of, presumably rectangles) where I can set a different background colour for each of the section of the grid...so I can alternate colours for each cell of the grid. I want the grid to be drawn and be constrained by the axes of my graph and then also adapt if the spacing of the axes ticks change (i.e. the axes changes like this: http://bl.ocks.org/mbostock/1667367). So if my graph has an x axis with 4 ticks and a y axis of 7 ticks then my graph will have a background grid that's 7 blocks high and 4 blocks wide.
I've been playing with the idea of using a range which starts at zero and ends at the full width of the graph but I don't know what value I can use for the step. Is there any way to sort of query the axis and return how many ticks there are?
var gridRange = d3.range(0, width, step?);
A better approach than your current solution would be to use scale.ticks() explicitly to get the tick values. The advantage of that is that it will still work if you change the number of ticks for some reason.
To get an alternating grid pattern instead of a single fill, you can use something like this code.
.attr("fill", function(d, i) {
return (i % 2) == 1 ? "green" : "blue";
})
Finally, to get the full grid pattern, you can either use an explicit loop as you've suggested, or nested selections. The idea here is to first pass in the y ticks, create a g element for each and then pass the x ticks to each one of these groups. In code, this looks something like this.
svg.selectAll("g.grid")
.data(y.ticks()).enter().append("g").attr("class", "grid")
.selectAll("rect")
.data(x.ticks()).enter().append("rect");
To set the position, you can access the indices within the top and bottom level data arrays like this.
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d, i, j) {
return yScale(j);
})
To set the x position, you need the index of the inner array (passed to the set of g elements), which can be accessed through the second argument of your callback. For the outer array, simply add another argument (j here).
And that's really all there is to it. Complete jsfiddle here. To update this grid dynamically, you would simply pass in the new tick values (gotten from scale.ticks()), match with the existing data, and handle the enter/update/exit selections in the usual manner.
If you want to do without the auxiliary scales (i.e. without .rangeBand()), you can calculate the width/height of the rectangles by taking the extent of the range of a scale and dividing it by the number of ticks minus 1. Altogether, this makes the code a bit uglier (mostly because you need one fewer rectangle than ticks and therefore need to subtract/remove), but a bit more general. A jsfiddle that takes this approach is here.
So after a few helpful comments above I've got close to a solution. Using Ordinal rangebands get me close to where I want to go.
I've created the range bands by using the number of ticks on my axis as a basis for the range of the input domain:
var xScale = d3.scale.ordinal()
.domain(d3.range(10))
.rangeRoundBands([0, width],0);
var yScale = d3.scale.ordinal()
.domain(d3.range(4))
.rangeRoundBands([0, height],0);
I've then tried drawing the rectangles out like so:
svg.selectAll("rect")
.data(p)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d,i) {
0
})
.attr("width", xScale.rangeBand())
.attr("height", yScale.rangeBand())
.attr("fill", "green").
attr('stroke','red');
This gets me the desired effect but for only one row deep:
http://jsfiddle.net/Ny2FJ/2/
I want,somehow to draw the green blocks for the whole table (and also without having to hard code the amount of ticks in the ordinal scales domain). I tried to then apply the range bands to the y axis like so (knowing that this wouldn't really work though) http://jsfiddle.net/Ny2FJ/3/
svg.selectAll("rect")
.data(p)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d,i) {
return yScale(i);
})
.attr("width", xScale.rangeBand())
.attr("height", yScale.rangeBand())
.attr("fill", "green").
attr('stroke','red');
The only way I can think to do this is to introduce a for loop to run the block of code in this fiddle http://jsfiddle.net/Ny2FJ/2/ for each tick of the y axis.
I am drawing circles by setting a fixed x position but a changing y position. The problem is the circles are overlapping since the radius of each circle is different.
Ideally in theory to solve that I would probably want to get the y position of the previous circle and add the radius of the current circle to it to get the y position of the current circle. Correct me if I am thinking it wrong.
Right now I am doing something like this now
var k = 10;
var circleAttributes = circles.attr("cx", '150')
.attr("cy", function (d) {
return (k++) * 10; //this is a very gray area
})
And I am getting an overlap. Ideally I would like to space the circles form each other. Even if the outer edges touch each other I could live with that. How should I approach it?
I am writing a range which i am using to get the radius
var rScale = d3.scale.linear()
.domain([min, max])
.range([10, 150]);
and simply passing that as the radius like this
.attr("r", function(d) { return rScale(d.consumption_gj_);})
This is my fiddle
http://jsfiddle.net/sghoush1/Vn7mf/27/
Did a solution here: http://tributary.io/inlet/6283630
The key was to keep track of the sum of the radius of all previous circles. I did that in a forEach loop:
data.forEach(function(d,i){
d.radius = rScale(d.consumption_gj_);
if (i !== 0){
d.ypos = d.radius*2 + data[i-1].ypos;
}
else {
d.ypos = d.radius*2;
}
})
then, when setting the attributes of the circles you can use your new d.radius and d.ypos
var circleAttributes = circles.attr("cx", '150')
.attr("cy", function (d,i) {
return d.ypos + 5*i;
})
.attr("r", function(d) { return d.radius;})
The Charge Property
The charge in a force layout refers to how nodes in the environment push away from one another or attract one another. Kind of like magnets, nodes have a charge that can be positive (attraction force) or negative (repelling force).
From the Documentation:
If charge is specified, sets the charge strength to the specified value. If charge is not specified, returns the current charge strength, which defaults to -30. If charge is a constant, then all nodes have the same charge. Otherwise, if charge is a function, then the function is evaluated for each node (in order), being passed the node and its index, with the this context as the force layout; the function's return value is then used to set each node's charge. The function is evaluated whenever the layout starts.
A negative value results in node repulsion, while a positive value results in node attraction. For graph layout, negative values should be used; for n-body simulation, positive values can be used. All nodes are assumed to be infinitesimal points with equal charge and mass. Charge forces are implemented efficiently via the Barnes–Hut algorithm, computing a quadtree for each tick. Setting the charge force to zero disables computation of the quadtree, which can noticeably improve performance if you do not need n-body forces.
A good tutorial that will help you see this in action:
http://vallandingham.me/bubble_charts_in_d3.html
I am attempting to create a vertical timeline using d3.js that is linked to a map so that any item(s) contained in the brush will also be displayed in the map. Kind of like http://code.google.com/p/timemap/ but with d3 instead of SIMILE and a vertical timeline rather than horizontal.
I can successfully create an svg with vertical bars representing time ranges, legend, ticks, and a brush. The function handling brush events is getting called and I can obtain the extent which contains the y-axis start and stop of the brush. So far so good...
How does one obtain the datums covered by the brush? I could iterate over my initial data set looking for items within the extent range but that feels hacky. Is there a d3 specific way of getting the datums highlighted by a brush?
var data = [
{
start: 1375840800,
stop: 1375844400,
lat: 0.0,
lon: 0.0
}
];
var min = 1375833600; //Aug 7th 00:00:00
var max = 1375919999; //Aug 7th 23:59:59
var yScale = d3.time.scale.utc().domain([min, max]).range([0, height])
var brush = d3.svg.brush().y(yScale).on("brush", brushmove);
var timeline = d3.select("#myDivId").append("svg").attr("width", width).attr("height", height);
timeline.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("x", function(datum, index) {return index * barSize})
.attr("y", function(datum, index) {return yScale(datum.start)})
.attr("height", function(datum, index) {return yScale(datum.end) - yScale(datum.start)})
.attr("width", function() {return barSize})
timeline.append("g")
.attr("class", "brush")
.call(brush)
.selectAll("rect")
.attr("width", width);
function brushmove() {
var extent = brush.extent();
//How do I get the datums contained inside the extent????
}
You'll need to do some kind of iteration to figure out what points live inside the brush extent. D3 doesn't automatically do this for you, probably because it can't know what shapes you're using to represent your data points. How detailed you get about what is considered "selected" and what isn't is quite application specific.
There are a few ways you can go about this:
As you suggest, you can iterate your data. The downside to this is that you would need to derive the shape information from the data again the same way you did when you created the <rect> elements.
Do a timeline.selectAll("rect") to grab all elements you potentially care about and use selection.filter to pare it down based on the x, y, height and width attributes.
If performance is a concern because you have an very large number of nodes, you can use the Quadtree helper to partition the surface and reduce the number of points that need to be looked at to find the selected ones.
Or try Crossfilter, there you pass the extent from the brush to a dimension filter and then you fetch filtered and sorted data by dimension.top(Infinity).
(A bit late answer, buy maybe useful for others, too.)