Synchronize an image with the rest of the svg with D3js - d3.js

I am trying to plot some points on a map. My map is a svg generated separately (in the PATH_PARAM file). It is supposed to act as a background, with details that must be at specific coordinates in the final plot. Even if it's not exactly that you can imagine the map is a picture of a face I traced myself (so I know its size and where are each elements in the map's referential) and I want to add points precisely in the middle of the eyes after importing it on my svg with D3.js.
For now I simply append it to the svg, like so: (this javascript is called during the loading of the page)
var SIZE_PARAM = 200;
var DATA_PARAM = [{position: [0,1]}, {position: [4,5]}] //actually it's a far more bigger set of points.
var graph = d3.select("#graph");
svg = graph.append("svg")
.attr("width",window.innerWidth)
.attr("height",window.innerHeight)
.call(d3.behavior.zoom().on("zoom",
function () {svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")")}
))
.append("g");
background = svg.selectAll("image")
.data([0])
.enter()
.append("svg:image");
background.attr("xlink:href", PATH_PARAM)
.attr("width",SIZE_PARAM)
.attr("height",SIZE_PARAM);
scale = d3.scale.linear()
.domain([dom_x_min, dom_x_max])
.rangeRound([ 0, SIZE_PARAM ])
.clamp(true);
circles = svg.selectAll("circle")
.data(DATA_PARAM)
.enter()
.append("circle");
circles.attr("class", "point")
.attr("cx", function(d,i) {return scale(d.position[0]);})
.attr("cy", function(d,i) {return scale(d.position[1]);})
.attr("r", function(d,i) {return 1;})
The problem I got is that the map and the points are not synchronized with the map. The relative position between points is good, and I managed to have the same scale for the map and the points by setting a unique SIZE_PARAM parameter, but there is a translation between the points and the map. This translation is not visible for a small number of points (like the two of the example) but becomes more and more noticeable as this number increases.
Following some advice I added this to the CSS:
#graph * {
position: absolute;
top: 0px;
left: 0px;
}
But it doesn't solve my problem.
What can I do to synchronize the map with the points ?

Related

How to center & scale map in d3js (use projection.center/translate/rotate?)

Given that I have topoJSON data of a given geographical feature and a specific projection.
How should I center and scale the map to fit its parent object?
It seems I can use either projection.rotate(), projection.translate() or projection.center() to center a map:
https://github.com/d3/d3-3.x-api-reference/blob/master/Geo-Projections.md
What are the differences and how does scale affect the different functions?
Use projection.fitExtent() in v4. Documentation. Example.
fitExtent takes two parameters:
extent is the top left and bottom right corner of the projection, represented by an array of two arrays – e.g. [[0, 0], [width, height]].
object is a GeoJSON object.
If the top left corner of the projection is [0, 0], you can use the convenience method projection.fitSize(), where you only pass the bottom right corner of the extent, represented by a single array of two items – e.g. [width, height].
Actually, it's a mix of both. According to the API, projection.center:
sets the projection’s center to the specified location, a two-element array of longitude and latitude in degrees and returns the projection.
So, it's used to set the center of the map. Regarding projection.translate:
If point is specified, sets the projection’s translation offset to the specified two-element array [x, y] and returns the projection. If point is not specified, returns the current translation offset which defaults to [480, 250]. The translation offset determines the pixel coordinates of the projection’s center. The default translation offset places ⟨0°,0°⟩ at the center of a 960×500 area.
As you can see, projection.translate depends on projection.center ("the translation offset determines the pixel coordinates of the projection’s center"). So, both values will determine how the map sits in its container
This is a demo showing the map of Japan (this code is not mine) in a smaller SVG, 500x500. In this one, we'll set the translate to the middle of the SVG:
.translate([width/2, height/2]);
Check the demo:
var topoJsonUrl = "https://dl.dropboxusercontent.com/u/1662536/topojson/japan.topo.json";
var width = 500,
height = 500,
scale = 1;
d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g").attr("id", "all-g");
var projection = d3.geo.mercator()
.center([138, 38])
.scale(1000)
.translate([width / 2, height / 2]);
d3.json(topoJsonUrl, onLoadMap);
function onLoadMap (error, jpn) {
var path = d3.geo.path()
.projection(projection);
var features = topojson.object(jpn, jpn.objects.japan);
var mapJapan = features;
d3.select("#all-g")
.append("g").attr("id", "path-g").selectAll("path")
.data(features.geometries)
.enter()
.append("path")
.attr("fill", "#f0f0f0")
.attr("id", function(d,i){ return "path" + i})
.attr("stroke", "#999")
.attr("stroke-width", 0.5/scale)
.attr("d", path);
}
path {
stroke: black;
stroke-width: 1.5;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://d3js.org/topojson.v0.min.js"></script>
And, in this one, to the left:
.translate([width/4, height/2]);
Check the demo:
var topoJsonUrl = "https://dl.dropboxusercontent.com/u/1662536/topojson/japan.topo.json";
var width = 500,
height = 500,
scale = 1;
d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g").attr("id", "all-g");
var projection = d3.geo.mercator()
.center([138, 38])
.scale(1000)
.translate([width / 4, height / 2]);
d3.json(topoJsonUrl, onLoadMap);
function onLoadMap (error, jpn) {
var path = d3.geo.path()
.projection(projection);
var features = topojson.object(jpn, jpn.objects.japan);
var mapJapan = features;
d3.select("#all-g")
.append("g").attr("id", "path-g").selectAll("path")
.data(features.geometries)
.enter()
.append("path")
.attr("fill", "#f0f0f0")
.attr("id", function(d,i){ return "path" + i})
.attr("stroke", "#999")
.attr("stroke-width", 0.5/scale)
.attr("d", path);
}
path {
stroke: black;
stroke-width: 1.5;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://d3js.org/topojson.v0.min.js"></script>
In both cases, however, changing projection.center will move the map in its container.

Modifiying dimple layout grouped chart

I am using the same chart as below. I want to push the x-axis headers i.e. Regular, Premium, Budget little bit below i.e. top padding or margin. Give some styling to it like give background color and change text color. I tried using fill and it does not work as desired. I would like to hide Price Tier/Channel also
http://dimplejs.org/examples_viewer.html?id=bars_vertical_grouped
These are SVG text elements so there is no top-padding or margin. You can move them down a bit by increasing the y property though, running the following after you call the chart.draw method will move the labels down 5 pixels:
d3.selectAll(".dimple-axis-x .dimple-custom-axis-label")
.attr("y", function (d) {
// Get the y property of the current shape and add 5 pixels
return parseFloat(d3.select(this).attr("y")) + 5;
});
To change the text colour you need to use the fill property (again that's an svg text thing):
d3.selectAll(".dimple-axis-x .dimple-custom-axis-label")
.style("fill", "red");
To colour the background of the text is a little less trivial, there actually isn't a thing for that in SVG, however you can insert a rectangle behind the text and do what you like with it:
d3.selectAll(".dimple-axis-x .dimple-custom-axis-label")
// Iterate each shape matching the selector above (all the x axis labels)
.each(function () {
// Select the shape in the current iteration
var shape = d3.select(this);
// Get the bounds of the text (accounting for font-size, alignment etc)
var bounds = shape.node().getBBox();
// Get the parent group (this the target for the rectangle to make sure all its transformations etc are applied)
var parent = d3.select(this.parentNode);
// This is just the number of extra pixels to add around each edge as the bounding box is tight fitting.
var padding = 2;
// Insert a rectangle before the text element in the DOM (SVG z-position is entirely determined by DOM position)
parent.insert("rect", ".dimple-custom-axis-label")
// Set the bounds using the bounding box +- padding
.attr("x", bounds.x - padding)
.attr("y", bounds.y - padding)
.attr("width", bounds.width + 2 * padding)
.attr("height", bounds.height + 2 * padding)
// Do whatever styling you want - or set a class and use CSS.
.style("fill", "pink");
});
These three statements can all be chained together so the final code will look a bit like this:
d3.selectAll(".dimple-axis-x .dimple-custom-axis-label")
.attr("y", function (d) { return parseFloat(d3.select(this).attr("y")) + 5; })
.style("fill", "red")
.each(function () {
var shape = d3.select(this);
var bounds = shape.node().getBBox();
var parent = d3.select(this.parentNode);
var padding = 2;
parent.insert("rect", ".dimple-custom-axis-label")
.attr("x", bounds.x - padding)
.attr("y", bounds.y - padding)
.attr("width", bounds.width + 2 * padding)
.attr("height", bounds.height + 2 * padding)
.style("fill", "pink");
});
FYI the dimple-custom-axis-label class was added in a recent release of dimple so please make sure you are using the latest version. Otherwise you'll have to find an alternative selector

d3js scatterplot custom icons and animation

I'm just starting with d3js and I wanted to know if it's possible to create a scatterplot with custom icons for the data points similar to this method for Forced Layout?
I don't want to use d3.svg.symbol() as I want to use a custom icon with my company logo to mark each data point.
My final goal is to translate a point (with a custom icon) along a horizontal axis depending on the x value. I've searched high and low on how to do this with d3js but have had no luck.
To use an icon instead of a symbol, just swap out the path element (that the symbol generator is called on) with an image element.
Given a data set, D, with elements like {src: http.myImageURL, x: 10, y : 20} it would look something like this:
var svg = d3.select('body').append('svg');
svg.append('g').selectAll('.myPoint')
.data(D)
.enter()
.append('image')
.attr("xlink:href", function(d){ return d.src })
.attr("x", function(d){ return d.x })
.attr("y", function(d){ return d.y })
.attr("width", 16)
.attr("height", 16);

How to limit the text of polygons in Voronoi diagram with D3.js?

I've see the Example of D3.js-Voronoi Tessellation.But I want to put some text in each of polygons instead of a circle,Here is my js code:
var width = 600, height = 400;
var vertices = d3.range(20).map(function(d){
return [Math.random() * width, Math.random() * height]
});
var voronoi = d3.geom.voronoi();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
path = svg.append("g").selectAll("path");
svg.selectAll("info")
.data(vertices.slice(1))
.enter().append("text")
.attr("transform", function(d) {
return "translate(" + d + ")";
})
.text("someText")
.attr("shape-rendering","crispEdges")
.style("text-anchor","middle");
redraw();
function redraw(){
path = path
.data(voronoi(vertices), polygon);
path.exit().remove();
path.enter().append("path")
.attr("class", function(d, i) {return "q" + (i % 9) + "-9";})
.attr("d", polygon);
path.order();
}
function polygon(d){
return "M" + d.join("L") + "Z";
}
I have a JSFiddle for that basic example here:
my voronoi code
now, I want each of the polygons' text in the center of the polygon, and don't cross with the polygon's border. If the polygon have not enough space to contain the all text, just contain the first part of it!
Let me know if there is anything I can do to solve this issue, thank you!
PS:I'm so sorry to my English, yes, it's so poor! :)
Have a look at this example http://bl.ocks.org/mbostock/6909318 , you probably want to place the text at the polygon centroid and not the seed (point) used to determine the voronoi tessellation.
That should fix the majority of your layout issues.
Automatically scaling the text to fit is a little bit harder, if you are willing to scale and rotate the text you can use a technique similar to the following to determine the length of the line at that point:
https://mathoverflow.net/questions/116418/find-longest-segment-through-centroid-of-2d-convex-polygon
Then you need to determine the angle of the line. I have a plugin that should help with that:
http://bl.ocks.org/stephen101/7640188/3ffe0c5dbb040f785b91687640a893bae07e36c3
Lastly you need to scale and rotate the text to fit. To determine the width of the text use getBBox() on the text element:
var text = svg.append("svg:text")
.attr("x", 480)
.attr("y", 250)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.style("font", "300 128px Helvetica Neue")
.text("Hello, getBBox!");
var bbox = text.node().getBBox();
Then you use the angle you calculated earlier to scale and rotate your text:
text.attr("transform", "rotate(40) scale(7)")
I would love to give a complete example but this is quite a bit of work to get it right.
There are other options to achieve the same effect but none of them are simple (ie you could anneal the layout similar to the way d3 does the Sankey layout)

Can d3 build shapes within a shape?

I have a newby question. Can D3 draw this:
http://www.nytimes.com/interactive/2008/05/03/business/20080403_SPENDING_GRAPHIC.html?_r=0
using the voronoi function within d3? What i am thinking is a svg that behaves like a and binds the voronoi found here http://bl.ocks.org/mbostock/4060366 to a circle. NY Times has accomplished the above visualization using flash.
Any ideas?
I have tried creating a large circle and embeding the smaller circles, but the voronoi does not show up and the points are not confined to the outer circle.
Code generated:
<svg class="PiYG" width="560" height="570">
<circle cx="270" cy="300" r="260" style="stroke: rgb(0, 0, 0);">
<g>
My js code looks something like this:
var width = 560, height = 570;
var svg = d3.select("#VD1").append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "PiYG");
var path = svg.append("circle")
.attr("cx", 270)
.attr("cy", 300)
.attr("r", 260)
.style("stroke", "#000")
.append("g")
.selectAll("path");
var vertices = d3.range(count).map(function(d) {
return [Math.random() * width, Math.random() * height];
});
var voronoi = d3.geom.voronoi()
.clipExtent([[0, 0], [width, height]]);
svg.selectAll("circle")
.data(vertices.slice(2))
.enter().append("circle")
.attr("transform", function(d) { return "translate(" + d + ")"; })
.attr("r", 2);
Thanks so much!
Not really, but not due to any shortcomings of d3, but rather because that's not what a Voronoi function does. A Voronoi function builds lines based on which regions of a graph are closest to a given point. It is not a way of proportionately dividing a circle into smaller segments, with size corresponding to data.
That being said, it is definitely possible to create a circular Voronoi diagram. To do so, you'll have to make a few changes to the example Voronoi diagram code.
First you'll have to make sure that all of the points fit in a circle. In your example, point location is given by d, here:
.attr("transform", function(d) { return "translate(" + d + ")"; })
Either d (your dataset) needs to fit in the circle, or you need to make some transformation of it. For data normalized to (-1,1) in both dimensions, the function
.attr("transform", function(d) { return "translate([" +
d[0]*Math.sqrt(1 - Math.pow(d[1],2)/2)
+ "," +
d[1]*Math.sqrt(1 - Math.pow(d[0],2)/2)
+ "])"; })
will do so. Here, we've created a new array that will be bounded by a circle from the array originally in d.
Next, you would need to clip your Voronoi diagram to be contained within a circle. The fun part is, there isn't a built in 'circle' geometric object to clip with, so you'll need to get creative! Either build a custom way to do this, or let the Voronoi extend beyond your circle and build an SVG to cover it up. Either should work.

Resources