Create element "in memory", like jQuery does, in D3.js - d3.js

Instead of doing
d3.select("body").append("svg")
, which most d3.js examples do, I'd like to create an element, and NOT attach it to body or anything right away.
Kind of like $('<div/>') in jQuery.
How can I do this?

Create the element using document.createElement() and pass it to d3 as usual.
In console:
> a = document.createElement("div")
<div>​</div>​
> d3.select(a).append("svg")
[Array[1]]
> a
<div>​
<svg>​</svg>​
</div>​

// create selection containing unattached div node
var sel = d3.create('svg');
and if you want just the node...
// retrieve unattached node
var node = d3.create('svg').node();

To create the svg element "in memory" use document.createElementNS.
Use of document.createElementNS:
// Create the svg elem
var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
// Create a g elem
var g = document.createElementNS("http://www.w3.org/2000/svg", "g");
// Create a d3 Selection with the elem
var d3Svg = d3.select(svg);
var d3g = d3.select(g);
Append a d3 selection to another d3 selection:
d3Svg.append(function(){ return d3g.node(); });

You should try something like:
var $svg = $('<svg/>');
var d3svg = d3.select($svg[0]);
// ... manipulate d3svg ...
$('body').append($svg)
Hope it helps.

I had to do this in order to support percentage widths on my custom SVG element:
var svgDom = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgDom.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
svg = d3.select(svgDom)
.attr("class", "chart")
.attr("width", "100%")
.attr("height", "100%");

Related

add radio buttons and lables to d3.js

I am trying to add radio buttons to an area with axis and shapes like this:
var triangleFunctions = ['Perpendicular Bisector', 'Median', 'Altitude'],
selected = 0;
var form = svg.append('form');
var labelEnter = form.selectAll('span')
.data(triangleFunctions)
.enter().append('span');
labelEnter.append('input').attr({
type: 'radio',
class: 'shape',
name: 'mode',
value: function(d, i) {return i;}
});
labelEnter.append("label").text(function(d) {return d;});
You can see a jsbin of the code here.
The labels and inputs are not appearing.
You can use foreignObject (but be aware that it does not work in IE):
var form = svg.append("foreignObject").attr("width", "400")
.attr("height", "50").append("xhtml:body").append('form');
Note that body tag is required, and it also has to contain xhtml: part.
Demo: http://jsbin.com/mafikorezo/edit?js,output

geoJSON projection with d3.js and dc.js for South Africa and provinces

I have been struggling for a few days trying to use dc.js with d3.js projection to draw a map of South Africa and the provinces. I have exhausted my search as most results incorporate the path used for SVG when not using dc.js and I can't seem to find a suitable example for correcting a projection in dc.js.
I can't seem to find the map thats being drawing and I don't know how to correct my projection.
I really really don't know what i'm missing, and anyone that can assist will be appreciated.
UPDATE: I have geoJson that ive tested in mapshaper and it works so the geojson is fine. I am just struggling with the projection.
zaMap = zaMapString
//new array
var zaMapData = [];
for(var p in zaMap["features"])
{
console.log("ndx2 province data " + zaMap["features"][p]["properties"]["name"]);
zaMapData.push({
province: zaMap["features"][p]["properties"]["name"],
donation: 1000
})
};
//crossfilter instance
var ndx2 = crossfilter(zaMapData);
//dimensions and group for dc/d3
var provinceDim = ndx2.dimension(function(d) {console.log("province d " + d["province"]); return d["province"];});
var donationsByProvince = provinceDim.group().reduceSum(function(d) {
return d["donation"];
});
//geoChoroplethChart
var zaChart = dc.geoChoroplethChart("#map");
//start of chart code using d3 and dc
zaChart.dimension(provinceDim)
.group(donationsByProvince)
.width(1000)
.height(330)
.colors(["#E2F2FF", "#C4E4FF", "#9ED2FF", "#81C5FF", "#6BBAFF", "#51AEFF", "#36A2FF", "#1E96FF", "#0089FF", "#0061B5"])
.projection(d3.geo.mercator()
.scale(26778)
.translate([8227, 3207]))
.overlayGeoJson(zaMap["features"], "name", function (d) {
return d.properties.name;
});
dc.renderAll();
$("#progress").css({"display": "none"});
})
UPDATE 2: I switched from fiddle to codepen so I could upload the geoJson file as a asset. The geoJson takes a while to load but using code from an existing stackoverflow question, I have gotten the map to draw and projection to correct itself automatically. The d3.js function is not wrapping the dc.js to tie in with crossfilter.js as yet but I am working on that. But this is progress :)
In http://jsfiddle.net/Jimmypoo/f67xo5ry/1/, you are trying to use JSON.parse to parse an zaMapString, which is already a JS object. You don't need to parse it, it's not a string.
Secondly, d3.json is meant for passing in a remote URL, which d3 grabs for you. You are trying to pass in a JS object, which already exists. So you can remove that function, and simply use .overlayGeoJson(zaMap["features"], "name", function (d) { inside.
You also forgot to include jQuery, yet you use it in $("#progress").css({"display": "none"});. You'll need to wrap the entire JS section in a $(document).ready as well.
Also, you are including the scripts multiple times, in both minified and unminified forms.You only need one instance of each library.
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/dc/1.7.0/dc.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/dc/1.7.0/dc.min.js"></script>
You are also trying to include dc's CSS as JavaScript.
<script src="http://cdnjs.cloudflare.com/ajax/libs/dc/1.7.0/dc.css"></script>
It should be added in JsFiddle's left-hand side resource panel instead.
I also don't think assigning #map directly to the body of your document is going to make things easier for you either..would recommend including something interior of that like <div id="map" style="width:100%;height:300px"></div>
These suggestions don't solve all your problems but get you most of the way along.You still have projection issues. Here is an forked fiddle to move from - http://jsfiddle.net/uggtjem6/
I have gotten the geoJson to work with d3.js, dc.js and crossfiler.
var width = 300;
var height = 400;
var zaMapData = [];
//geoChoroplethChart
var zaChart = dc.geoChoroplethChart("#map");
d3.json("https://s3-us-west-2.amazonaws.com/s.cdpn.io/384835/layer1.json", function(json) {
var zaMap = JSON.stringify(json);
console.log(zaMap);
for (var i = 0; i < json.features.length; i++) {
console.log("ndx2 province data " + json["features"][i]["properties"]["PROVINCE"]);
zaMapData.push({
province: json["features"][i]["properties"]["PROVINCE"],
donation: i*1000
})
};
//crossfilter instance
var ndx2 = crossfilter(zaMapData);
//dimensions and group for dc/d3
var provinceDim = ndx2.dimension(function(d) {console.log("province d " + d["province"]); return d["province"];});
var donationsByProvince = provinceDim.group().reduceSum(function(d) {
return d["donation"];
});
var max_province = donationsByProvince.top(1)[0].value;
// create a first guess for the projection
var center = d3.geo.centroid(json)
var scale = 150;
var offset = [width/2, height/2];
var projection = d3.geo.mercator().scale(scale).center(center)
.translate(offset);
// create the path
var path = d3.geo.path().projection(projection);
// using the path determine the bounds of the current map and use
// these to determine better values for the scale and translation
var bounds = path.bounds(json);
var hscale = scale*width / (bounds[1][0] - bounds[0][0]);
var vscale = scale*height / (bounds[1][1] - bounds[0][1]);
var scale = (hscale < vscale) ? hscale : vscale;
var offset = [width - (bounds[0][0] + bounds[1][0])/2,
height - (bounds[0][1] + bounds[1][1])/2];
// new projection
projection = d3.geo.mercator().center(center)
.scale(scale).translate(offset);
path = path.projection(projection);
//create dc.js chart
zaChart.dimension(provinceDim)
.group(donationsByProvince)
.width(width)
.height(height)
.colors(["#E2F2FF", "#C4E4FF", "#9ED2FF", "#81C5FF", "#6BBAFF", "#51AEFF", "#36A2FF", "#1E96FF", "#0089FF", "#0061B5"])
.colorDomain([0, max_province])
.projection(d3.geo.mercator()
.center(center)
.scale(scale)
.translate(offset))
.overlayGeoJson(json["features"], "PROVINCE", function (d) {
return d.properties.PROVINCE;
})
.title(function (p) {
return "Province: " + p["key"]
+ "\n"
+ "Total Donations: R " + Math.round(p["value"])
});
dc.renderAll();
});
My codepen here.

Create Unique Path Within Each SVG Group Element Using D3

I'm trying to create 50 SVG groups with each group containing a path that draws a particular US state. However, I can't figure out how to access the state data from the SVG group when creating the state path for that group. Can someone put me on the right track? Thanks!
var coords = [
{ 'id':'HI', 'class':'state hi', 'd': 'm 233.08751,519.30948 1.93993, ...' },
{ 'id':'AK', 'class':'state ak', 'd': 'm 158.07671,453.67502 -0.32332, ...'}
...
];
// Select the SVG
var svgContainer = d3.select('svg');
// Create groups with state data
var groups = svgContainer.selectAll('g')
.data(coords)
.enter()
.append('g');
// Create state path for each group
groups.each(function(g){
g.select('path')
.data(___NEED TO RETURN THE PATH DATA HERE___)
.enter()
.append('path');
});
Uncaught TypeError: g.select is not a function
Assuming your data is valid and you only want to handle new elements (no updates), this should work:
// Select the SVG
var svgContainer = d3.select('svg');
// Create groups with state data
var groups = svgContainer.selectAll('g')
.data(coords)
.enter()
.append('g')
.append('path')
.attr('d', function (d) { return d.d; });
I have created a pretty simplified fiddle with an example.
If you still want to use the .each function, you can proceed as follows:
groups.each(function (d, i){
// `this` (in this context) is bound to the current DOM element in the enter selection
d3.select(this).append('path').attr('d', d.d);
});
For further details, you can check the API documentation.
Hope it helps.

When a d3.behavior.zoom event is triggered programatically, how do you set inital values for translate and scale?

The squares in the example below are part of an SVG group that has an initial translate and scale set.
Clicking on a square initiates a zoom transition. But the intial values set by the transition are different from my defaults, as made obvious by the jarring start to this transition.
How can I set initial values for translate and scale on a zoom transition that I initiate programatically?
var svg = d3.select("#main");
svg.append("rect").attr({"x":0,"y":0,"height":100,"width":100,"fill":"red"})
svg.append("rect").attr({"x":100,"y":100,"height":100,"width":100,"fill":"blue"})
svg.append("rect").attr({"x":0,"y":100,"height":100,"width":100,"fill":"green"})
svg.append("rect").attr({"x":100,"y":0,"height":100,"width":100,"fill":"yellow"})
var zoom = d3.behavior.zoom().on("zoom",function(){
var t = d3.event.translate;
var s = d3.event.scale;
console.log(s)
svg.attr("transform","translate("+t[0]+","+t[1]+") scale("+s+")")
}).scaleExtent([1,10]).scale(1).translate([0,0])
d3.select("svg").call(zoom)
d3.selectAll("rect").on("mousedown",function(){
var scale = Math.random()*3;
var translate = [Math.random()*200,Math.random()*200]
zoom.scale(scale);
zoom.translate(translate);
//new transition
var T = svg.transition().duration(5000)
zoom.event(T);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<p style="font-weight:bold">When a zoom is triggered programatically, how do you set inital values for translate and scale?</p>
<p>Click on one of the squares</p>
<svg height="600px" width="600px">
<g id="main" transform="translate(25,25) scale(0.25)"></g>
</svg>
That is a problem with the zoom function itself. I would suggest zooming the children as opposed to the parent if that would work
var zoom = d3.behavior.zoom().on("zoom",function(){
var t = d3.event.translate;
var s = d3.event.scale;
svg.selectAll("rect").attr("transform","translate("+t[0]+","+t[1]+") scale("+s+")")
}).scaleExtent([1,10]);
EDIT
The problem with the above code is that d3.js does not register the transformation or initial state of the SVG. This problem runs deeper. As d3 does not keep track of the SVG transformations and just executes them. It only keeps track of transformations you've run on the library in a variable called __chart__.
So when the zoom function is run it just interpolates the variables and gives the output. As no functions have been run on this yet the __chart__ variable has not been set and causing the jerky start from (x=0, y=0, k=1).
Solution:
Run this code before the zoom transformation to set the initial chart manually
svg.transition().each(function(){
this.__chart__={x:25,y:25,k:0.25}; //or you can pick those values using attr
});
Zoom the svg programmatically to 25,25,0.25 first before any other function. (this is why your workaround works as the __chart__ variable gets set)
To set the initial value of the zoom, try something like this:
// Init zoom
var zoom = d3.behavior.zoom().on("zoom", function () {
svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
});
// Get SVG element
var svg = d3.select("svg")
.call(zoom)
.append("g");
// Create circle
svg.append("circle")
.attr("cx",0)
.attr("cy",0)
.attr("r", 5);
// Create init value
var scale = 5;
var translate = [50, 50];
// Set init value
zoom.scale(scale);
zoom.translate(translate);
// Call zoom event
svg.call(zoom.event);
// or svg.transition().call(zoom.event);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg height="100px" width="100px"></svg>
I was looking for the answer to this, but it seems D3 has already evolved a couple of versions.
Although Majkl and cjds's answers helped me solve my problem, I thought it would help to leave more up to date information, since it was hard finding v5.4 examples out there, until I found Observable at least.
// Applies event transformation to the Group element's attribute
const zoom_action = () => g.attr("transform", d3.event.transform)
// Create the zoom handler
const zoom = d3
.zoom()
.on("zoom", zoom_action)
// Get SVG element and apply zoom behaviour
var svg = d3
.select("svg")
.call(zoom)
// Create Group that will be zoomed
var g = svg.append("g")
// Create circle
g.append("circle")
.attr("cx",0)
.attr("cy",0)
.attr("r", 5)
// Set initial scale and translation
zoom.scaleBy(svg, 5)
zoom.translateBy(svg, 50, 50)
<script src="https://d3js.org/d3.v5.js"></script>
<svg height="100px" width="100px"></svg>

D3: Is there a simple way to enlarge a map statically?

I am attempting to create a map of my city but the default map is too small. I don't want to do anything like zooming (yet) but I would like to double the size displayed.
Here is an extract of the script
var projection;
var w=1200;
var h=800;
var x=-14100;
var y=7300;
var scale=66700;
projection=d3.geo.albers()
.translate([x,y])
.scale([scale]);
var path=d3.geo.path()
.projection(projection);
var svg=d3.select("#data-div")
.append("svg")
.attr("width",w)
.attr("height",h);
var category;
// GROUPS
var paths = svg.append("g"),
circles = svg.append("g");
// TORONTO MAP JSON
d3.json("d3_files/json/new-toronto.json",function(error, data){
paths.selectAll("path")
.data(data.features)
.enter()
.append("path")
.attr("d",path)
.attr("name",function(data){
return"<strong>name</strong>"
}) // end name attr
.style("fill",function(data){
return"lightgrey";
}) // end fill style
.style("stroke","#000")
}); // end toronto json
Instead of:
var scale=66700;
do:
var scale= 33350;
This will 'zoom' the projection out. From the API:
The scale factor corresponds linearly to the distance between
projected points

Resources