Is it possible to create a d3/svg circular canvas/container? - d3.js

I'm trying to implement a circular canvas in d3. I've tried editing this example of a rectangular canvas...
http://jsfiddle.net/k92end80/1/
But whenever I add an svg:circle it simply blocks any nodes behind it - does anyone know if this is even possible in d3/svg?
I was naively thinking I could just replace the below attributes with a cx, cy, r attribute...
var container = d3.select("body")
.append("svg")
.attr("id", "svgcontainer")
.attr("width", 300) //replace with a cx
.attr("height", 300) //replace with a cy
//add a r attr
.style("background-color", "#aaaaee")
.call(zoom)
.append("g");
Thanks for any help!

The SVG 1.1 spec tells us:
The canvas is infinite for each dimension of the space, but rendering occurs relative to a finite rectangular region of the canvas. This finite rectangular region is called the SVG viewport.
Taking that for granted neither d3 nor any other framework will be able to establish a canvas or viewport of any other shape.

Related

How does Mike Bostock's (D3js's creator) pan & zoom example work?

I'm having a hard time understanding this example:
https://bl.ocks.org/mbostock/4e3925cdc804db257a86fdef3a032a45
I understand the general concept, all the points in the graph are put into a svg g-element, which is useful because now you can apply a transform on the group and have all the points scale accordingly. This transforms the whole coordinate system that these points are in. But this does not matter for the mouse-coordinate that is passed along for panning because those coordinates come from an invisible rect in the svg that does not get transformed.
What I don't understand is this:
svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.call(d3.zoom()
.scaleExtent([1 / 2, 4])
.on("zoom", zoomed));
function zoomed() {
g.attr("transform", d3.event.transform);
}
Is the .call a member function of the default svg-rectangle? What does it do here? And d3.zoom is according to the documentation a 'behaviour'. What does that mean and how does the coupling between these two work here? And what is the purpose of the function .on, which gets passed 'zoom'? I assume it's for passing the browser event of zooming on an element to the event handler zoomed. But then how does panning get handled here? And is it just me or would it have had more sense to have .scaleExtent and .on as parameters of d3.zoom()?
The rect receives the browser zoom events.
The zoomed function applies the transform to the g element
Note we have the rect after the g otherwise the rect would behind the g not receiving events.
svg
- g
- rect

How do I use/retrieve the C3 scale function post generate?

I am using C3 to generate a chart and have found it necessary to dig into the underlying D3 constructs to supplement its functionality. I'm at an impasse and need to draw a region that is both limited by the x values and the y values but the "regions" functionality of C3 only allows one or the other. So, I need to draw on the C3-generated chart a rect at the appropriate location but using the scale functions to determine the X/Y values of the new rect. Is there any way to do this with C3 or underlying D3 library?
You can access the scale function from the chart.internal (chart.internal.x and chart.internal.y). You'll probably need the margins too (otherwise your positions will be offset by that much)
Here's how you draw a rectangle from point (0 index, 100) to (2 index, 400)
var rect = chart.internal.svg.append("rect")
.attr('fill', 'rgba(255, 9, 0, 0.1)')
.attr("x", chart.internal.x(0) + chart.internal.margin.left)
.attr("y", chart.internal.y(400) + chart.internal.margin.top)
.attr("width", chart.internal.x(2) - chart.internal.x(0))
.attr("height", chart.internal.y(100) - chart.internal.y(400));
Fiddle - http://jsfiddle.net/hehpy91o/

why does d3 rescale all scales during zoom?

The problem is that my yScale changes upon panning.
Here's the definition of the scale:
this.yScale = d3.scale.linear()
.domain([0, this.maxY * this.yHeader])
.rangeRound([0, height * this.yHeightScalor]);
I need to keep hold of the scale (i.e. use this.yScale instead of just var yScale) because of my redraw function. The trick is, panning = zooming where d3.event.scale === 1 and zooming rescales domains as you can see if you put a breakpoint in D3's zoom.js rescale function.
I can get around it by making sure my yScale is defined correctly when used but it seems to me that somethings amiss.
UPDATE: I've included the code and removed the line causing the issues. Truth be told, I only needed the xScale to zoom for user feedback. I redraw everything after the zoom anyway (in zoomEndFunction).
zoom = d3.behavior.zoom()
.x(this.xScale)
.y(this.yScale) // <--------------------- I removed this
.scaleExtent([0.5, 2])
.on("zoom", zoomFunction(this))
.on("zoomend", zoomEndFunction(this));
svg = histogramContainer.append("svg")
.attr('class', 'chart')
.attr('width', width)
.attr('height', height)
.call(zoom)
.append('g')
.attr('transform', 'translate(' + this.margin.left + ' , ' +
(height - this.margin.bottom) + ')');
// everything else is in the svg
The d3 zoom behaviour primarily acts as a wrapper for handling multiple events (mousewheel, drag, and various touch gestures), and converting them into translate and scale values, which are passed to your zoom event handlers as properties of the d3.event object.
However, you can also register a quantitative (non-ordinal) scale on the zoom behaviour using zoom.x(xScale) and zoom.y(yScale). The zoom behaviour will adjust each scale's domain to reflect the translation and scale prior to triggering the zoom event, so that all you have to do is redraw your visualization with those scales.
You do not have to register your scales on the zoom behaviour, or you can register one scale but not the other. For example,
if you are using transformations to zoom the visualization (a "geometric zoom", in d3 parlance), you don't need to change your scales at all;
if you are using a polar coordinates system, instead of x/y coordinates, you'll need to calculate the adjustments directly;
if you have a graph with a meaningful baseline, but very dense data, you may want to zoom/pan the horizontal axis but not the vertical.
From the comments, it sounds like the last situation reflects your case.

Adding a drag icon to Focus+Context via Brushing Chart

I am trying to adapt Mike Bostock's Focus+Context via Brushing chart at: bl.ocks.org/mbostock/1667367 ‎ to include a drag icon on both vertical lines of the brush rectangle. These should appear once a selection is made and act as a visual cue to shrink or expand the selected/brushed area. I see the placement of the images being dynamic i.e. moving fluidly with the brushed area as opposed to an update after the brushed area is reset. What seems most reasonable to me would be to add an svg image to the context rectangle like so:
//original code
context.append("g")
.attr("class", "x brush")
.call(brush)
.selectAll("rect")
.attr("y", -6)
.attr("height", height2 + 7)
//additional code
.append("svg:image")
.attr("xlink:href", "icon.png")
.attr("width", 10)
.attr("height", 10)
.style("opacity",1)
I've tried playing around with the x and y positioning of both images with no luck getting them to appear, but i conceptually see it working as
y axis: height of context chart divided by 2
x axis: each image respectively corresponding to the min and max x values of the brushed area
Any help would be appreciated.
Lars, thanks for the pointer which generally led me in the right direction although I ended up directly adapting from an example at https://engineering.emcien.com/2013/05/7-d3-advanced-brush-styling. That example is a bit more complex so i borrowed from a subset relevant to my purposes.
Steps i followed include
1.) Creating two images appended to the context g element and initializing their position somewhere that doesn't give the impression that the chart is brushed on loading {i put them halfway (vertically) and close together around the end of the context(horizontally)}.
var leftHandle = context.append("image")
.attr("xlink:href", "icon.gif")
.attr("width", 11)
.attr("height", 27)
.attr("x",x2(data[data.length-6].date))
.attr("y", (height2/2)-15);
var rightHandle = context.append("image")
.attr("xlink:href", "icon.gif")
.attr("width", 11)
.attr("height", 27)
.attr("x",x2(data[data.length-1].date))
.attr("y", (height2/2)-15);
2.) Within the brush function, i created a variable to hold brush.extent(), then tied the x attributes of the images to its min and max x values.
var ext = brush.extent();
leftHandle.attr("x",x2(ext[0]));
rightHandle.attr("x",x2(ext[1]));
One things i'm not completely satisfied with is that when i initially created the images after the brush rectangle, they sat on top of it, preventing me from being able to brush if i hovered over the images (which is the intuitive reaction desired). I ended up placing the images behind the rectangle which has a low opacity. Not the 100% accurate visual representation sought, but it works just fine.

How do you get selected datums in brush "brush" event handler?

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.)

Resources