i have a function drawGraph as prototype looking as the following:
Presentation.prototype.drawGraph = function() {
var _this;
this.margin = {
top: 0,
right: 0,
bottom: 0,
left: 0
};
this.width = 1000 - this.margin.left - this.margin.right;
this.height = 1000 - this.margin.top - this.margin.bottom;
this.singleWidth = 50;
this.singleHeight = 32;
_this = this;
return d3.json("data.json", (function(_this) {
return function(err, json) {
if (err) {
console.error(err);
return;
}
d3.select("#backbutton").on("click", function(e) {
_this.removeRenderedContent();
return _this.clickOnBackgroundRect();
});
d3.select("#updatebutton").on("click", function(e) {
try{
$.get('http://localhost:3000/data');
}
catch (e) {
alert('Error: ' + e);
}
//code for updateGraph
});
_this.x = d3.scale.ordinal().rangeBands([0, _this.width], 0, 0);
_this.y = d3.scale.linear().range([_this.height, 0]);
_this.svg = d3.select("#graph").append("svg").attr("width", _this.width + _this.margin.left + _this.margin.right).attr("height", _this.height + _this.margin.top + _this.margin.bottom).append("g").attr("transform", "translate(" + 0 + "," + 50 + ")");
_this.svg.append("rect").attr("x", -200).attr("y", -200).attr("width", 1200).attr("height", 1200).attr("fill", "#FFFFFF").on("click", function(data, index) {
_this.removeRenderedContent();
return _this.clickOnBackgroundRect();
});
_this.groups = _this.svg.selectAll("g").data(json).enter().append("g").attr("class", function(data, index) {return "group-case " + data.type;});
_this.groups.on("click", function(data, index) {
_this.removeRenderedContent();
_this.clickOnGroupCaseRect(data, index, this);
return false;
});
_this.groups.attr("transform", function(data, index) {
return "translate(" + data.x + "," + data.y + ")";
});
_this.groups.append("rect").attr("width", _this.widthFilter).attr("height", _this.heightFilter).attr("x", 0).attr("y", 0).attr("rx", 10).attr("ry", 10);
_this.groups.append("g").append("text").attr("y", 7).attr("x", 7).text(function(data) {return data.title;}).call(function(data, xpadding) {return _this.wrapGroup(data, xpadding);}, 7);
_this = _this;
return _this.groups.append("g").each(function(data, index) {
return _this.drawChildren(this, data, index);
});
};
})(this));
};
I want the graph to be updated on click event of #updatebutton. I looked into many examples, but can't get it work for my code. Does anyone know how to reload my Graph?
Thanks in Advance!
Related
I am having a similar problem to the one described here, but the solution offered there doesn't seem to work for me. I am trying to create a force-directed graph with force-based label placement using my own json data. I'm running into an unspecified uncaught javascript error at the point of reading in my data. My data is in the following format:
{"nodes": [{"name": Bob, "id": 1}, {"name": Suzy, "id": 2}],
"links": [{"source": 1, "target":2}]}
My script is below. Any insight would be enormously helpful!
<script type="text/javascript" charset="utf-8">
var w = 960, h = 500;
var labelDistance = 0;
var vis = d3.select("body").append("svg:svg").attr("width", w).attr("height", h);
var nodes = [];
var labelAnchors = [];
var labelAnchorLinks = [];
var links = [];
d3.json("data.json", function(error, graph) {
if (error) throw error;
for (var i = 0; i < graph.nodes.length; i++) {
var node = graph.nodes[i];
nodes.push(node);
labelAnchors.push({
node : node
});
labelAnchors.push({
node : node
});
}
graph.links.forEach(function(link, i) {
links.push({
source: link.source,
target: link.target,
weight: Math.random()
});
labelAnchorLinks.push({
source: link.source * 2,
target: link.target * 2 + 1,
weight: 1
});
});
var force = d3.layout.force().size([w, h]).nodes(nodes).links(links).gravity(1).linkDistance(50).charge(-3000).linkStrength(function(x) {
return x.weight * 10
});
force.start();
var force2 = d3.layout.force().nodes(labelAnchors).links(labelAnchorLinks).gravity(0).linkDistance(0).linkStrength(8).charge(-100).size([w, h]);
force2.start();
var link = vis.selectAll("line.link").data(links).enter().append("svg:line").attr("class", "link").style("stroke", "#CCC");
var node = vis.selectAll("g.node").data(force.nodes()).enter().append("svg:g").attr("class", "node");
node.append("svg:circle").attr("r", 5).style("fill", "#555").style("stroke", "#FFF").style("stroke-width", 3);
node.call(force.drag);
var anchorLink = vis.selectAll("line.anchorLink").data(labelAnchorLinks)//.enter().append("svg:line").attr("class", "anchorLink").style("stroke", "#999");
var anchorNode = vis.selectAll("g.anchorNode").data(force2.nodes()).enter().append("svg:g").attr("class", "anchorNode");
anchorNode.append("svg:circle").attr("r", 0).style("fill", "#FFF");
anchorNode.append("svg:text").text(function(d, i) {
return i % 2 == 0 ? "" : d.node.label
}).style("fill", "#555").style("font-family", "Arial").style("font-size", 12);
var updateLink = function() {
this.attr("x1", function(d) {
return d.source.x;
}).attr("y1", function(d) {
return d.source.y;
}).attr("x2", function(d) {
return d.target.x;
}).attr("y2", function(d) {
return d.target.y;
});
}
var updateNode = function() {
this.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
force.on("tick", function() {
force2.start();
node.call(updateNode);
anchorNode.each(function(d, i) {
if(i % 2 == 0) {
d.x = d.node.x;
d.y = d.node.y;
} else {
var b = this.childNodes[1].getBBox();
var diffX = d.x - d.node.x;
var diffY = d.y - d.node.y;
var dist = Math.sqrt(diffX * diffX + diffY * diffY);
var shiftX = b.width * (diffX - dist) / (dist * 2);
shiftX = Math.max(-b.width, Math.min(0, shiftX));
var shiftY = 5;
this.childNodes[1].setAttribute("transform", "translate(" + shiftX + "," + shiftY + ")");
}
});
anchorNode.call(updateNode);
link.call(updateLink);
anchorLink.call(updateLink);
});
});
</script>
You can't read a file without specifying a correct path to your file system or to an external resource.
That means that you have to swap this line:
d3.json("/data/", function(error, graph) {
With something like:
d3.json("./data.json", function(error, graph) {
Or:
d3.json("http://www.somesite.com/data.json", function(error, graph) {
Also, make sure you are running a local server on your machine otherwise that call will fail.
I have a sunburst chart made in D3. Each 'petal' represents a subset of data. When a user clicks on one of the 'petals', I would like it to transition, fanning out to only show that subset (see image):
I'm having trouble getting the code to properly transition.
On click, all 'petals' (besides the selected one) should disappear and the remain paths should animate along the circle (using attrTween, arcTween, and interpolate?). The primary value that would be changing is the angleSize (var angleSize = (2 * Math.PI) / theData.length;).
I've tried using this, this, this, and this as reference without much success. What's the best way to handle the animation?
Thanks for your time!
--> See Plunker Here. <--
Code is below:
var colors = {
'Rank1' : '#3FA548',
'Rank2' : '#00B09E',
'Rank3' : '#8971B3',
'Rank4' : '#DFC423',
'Rank5' : '#E74341'
};
var $container = $('.chart'),
m = 40,
width = $container.width() - m,
height = $container.height() - m,
r = Math.min(width, height) / 2;
var study = null;
var arc = d3.svg.arc();
d3.csv('text.csv', ready);
function ready(err, data) {
if (err) console.warn('Error', err);
var svg = d3.select('.chart')
.append('svg')
.attr({
'width' : (r + m) * 2,
'height' : (r + m) * 2,
'class' : 'container'
})
.append('g')
.attr('transform', 'translate(' + (width / 4) + ', ' + (height / 2) + ' )');
var slice = svg.selectAll('.slice');
function updateChart(study) {
if (study) {
var theData = data.filter(function(d) {
return d.study_name === study;
});
} else {
var theData = data;
}
slice = slice.data(theData);
slice.enter()
.append('g')
.attr('class', 'slice');
var angleSize = (2 * Math.PI) / theData.length;
var startRadArr = [],
endRadArr = [];
for ( var i = 0; i < data.length; i++ ) {
var startRadius = (width / 20),
endRadius = startRadius;
for ( var x = 0; x < 4; x++ ) {
startRadArr.push(startRadius);
if ( x == 0 ) {
endRadius += Number(data[i].group1_score) * (width / 500);
} else if ( x == 1 ) {
endRadius += Number(data[i].group2_score) * (width / 500);
} else if ( x == 2 ) {
endRadius += Number(data[i].group3_score) * (width / 500);
} else {
endRadius += Number(data[i].group4_score) * (width / 500);
}
endRadArr.push(endRadius);
startRadius = endRadius + 0.3;
}
}
var startRadGroup = [],
endRadGroup = [];
for (i = 0; i < startRadArr.length; i += 4) {
startRadGroup.push(startRadArr.slice(i, i + 4));
}
for (i = 0; i < endRadArr.length; i += 4) {
endRadGroup.push(endRadArr.slice(i, i + 4));
}
slice.selectAll('path')
.remove();
for ( var x = 0; x < 4; x++ ) {
slice.append('path')
.attr({
'class' : function(d, i) {
if ( x == 0 ) {
return d.group1_class;
} else if ( x == 1 ) {
return d.group2_class;
} else if ( x == 2 ) {
return d.group3_class;
} else {
return d.group4_class;
}
},
'company' : function(d, i) {
return d.brand_name;
},
'cat' : function(d, i) {
if ( x == 0 ) {
return 'Group1';
} else if ( x == 1 ) {
return 'Group2';
} else if ( x == 2 ) {
return 'Group3';
} else {
return 'Group4';
}
},
'study' : function(d, i) {
return d.study_name;
},
'companyid' : function(d, i) {
return d.brand_id;
},
'startradius' : function(d, i) {
return startRadGroup[i][x];
},
'endradius' : function(d, i) {
return endRadGroup[i][x];
},
'startangle' : function(d, i) {
return angleSize * i;
},
'endangle' : function(d, i) {
return angleSize * (i + 1);
}
})
.on('click', selectStudy);
}
slice.exit()
.remove();
slice.selectAll('path')
.attr({
'd' : function(d) {
return arc({
innerRadius : +d3.select(this)[0][0].attributes.startradius.nodeValue,
outerRadius : +d3.select(this)[0][0].attributes.endradius.nodeValue,
startAngle : +d3.select(this)[0][0].attributes.startangle.nodeValue,
endAngle : +d3.select(this)[0][0].attributes.endangle.nodeValue
})
}
});
}
function selectStudy(d) {
study = $(this).attr('study');
updateChart(study);
}
updateChart();
}
EDIT
Updated the code (based on this) to include a properly working enter, update, and exit pattern. Still unsure about the transition however. Most of the examples I've linked to use something similar to d3.interpolate(this._current, a);, tweening between differing data.
In this chart, this._current and a are the same, angleSize (var angleSize = (2 * Math.PI) / theData.length;), startAngle, and endAngle are the only thing that changes.
Your problem is that you are not really binding data to the elements, and therefore the transition is not possible. I mangled your code a little bit so the data contains all the nested information about the starting and ending angles, so that it can be bound to the paths inside each slice.
Take a look at this Plunker: https://plnkr.co/edit/a7cxRplzy66Pc1arM2a9?p=preview
Here's the listing of the modified version:
var colors = {
Rank1: '#3FA548',
Rank2: '#00B09E',
Rank3: '#8971B3',
Rank4: '#DFC423',
Rank5: '#E74341'
};
// Configuration
var $container = $('.chart'),
m = 40,
width = $container.width() - m,
height = $container.height() - m,
r = Math.min(width, height) / 2;
var study = null;
var arc = d3.svg.arc();
// Load data
d3.csv('text.csv', ready);
// Data loaded callback
function ready(err, data) {
if (err) console.warn('Error', err);
var svg = d3.select('.chart')
.append('svg')
.attr({
'width': (r + m) * 2,
'height': (r + m) * 2,
'class': 'container'
})
.append('g')
.attr('transform', 'translate(' + (width / 4) + ', ' + (height / 2) + ' )');
var slices = svg.selectAll('.slice');
function updateChart(study) {
var theData = data;
if (study) {
theData = data.filter(function (d) {
return d.study_name === study;
});
}
var angleSize = (2 * Math.PI) / theData.length;
theData.forEach(function (item, i) {
var startRadius = (width / 20),
endRadius = startRadius,
groupName;
item.paths = [];
for (var g = 0; g < 4; g++) {
item.paths[g] = {};
item.paths[g].startRadius = startRadius;
groupName = 'group' + (g + 1) + '_score';
endRadius += Number(item[groupName]) * (width / 500);
item.paths[g].endRadius = endRadius;
startRadius = endRadius + 0.3;
}
});
// Set the data
slices = slices.data(theData);
// Enter
slices.enter()
.append('g')
.attr('class', 'slice');
// Exit
slices.exit()
.remove();
// Update
slices
.transition()
.duration(750)
.each(function (dSlice, iSlice) {
var slice = d3.select(this);
var paths = slice.selectAll('path');
// Set data
paths = paths.data(dSlice.paths);
// Exit
paths.exit()
.remove();
// Enter
paths.enter()
.append('path')
.attr('class', 'path');
// Update
paths
.transition()
.attr({
'class': function (d, i) {
return dSlice['group' + (i + 1) + '_class'];
},
'company': dSlice.brand_name,
'cat': function (d, i) {
return 'Group' + (i + 1);
},
'study': function (d, i) {
return dSlice.study_name;
},
'companyid': function (d, i) {
return dSlice.brand_id;
},
'startradius': function (d, i) {
return d.startRadius;
},
'endradius': function (d, i) {
return d.endRadius;
},
'startangle': function (d, i) {
return angleSize * iSlice;
},
'endangle': function (d, i) {
return angleSize * (iSlice + 1);
},
'd': function (d) {
return arc({
innerRadius: +d.startRadius,
outerRadius: +d.endRadius,
startAngle: +angleSize * iSlice,
endAngle: +angleSize * (iSlice + 1)
})
}
})
.duration(750);
paths.on('click', selectStudy);
});
function selectStudy(d, i) {
study = $(this).attr('study');
updateChart(study);
}
}
updateChart();
}
As you can see, the key is correctly preparing the data (let's say the format in your example .tsv file is not the best choice, but sometimes we can't choose our data sources...)
Then afterwards, by putting the code for the paths generation inside the .each call on the slices, the data can be accessed from the function (d, i) { ... } callbacks and every element happens to receive the corresponding data.
Another trick is using the slices data (accessed inside the .each function via the dSlice and iSlice vars) on the paths' callbacks. This way the paths can consume this data for their own purposes. In this case, the company and study_name properties.
Now in order to tweak the transition and make it more accurate, the starting attributes can change. You can try by setting up some attributes for the paths in the .enter() phase.
i am building a d3 Graph based on a json-File with an svg and groups:
Presentation.prototype.drawGraph = function() {
var _this;
this.margin = {
top: 0,
right: 0,
bottom: 0,
left: 0
};
this.width = 1000 - this.margin.left - this.margin.right;
this.height = 720 - this.margin.top - this.margin.bottom;
this.singleWidth = 50;
this.singleHeight = 32;
_this = this;
return d3.json("result.json", (function(_this) {
return function(err, json) {
if (err) {
console.error(err);
return;
}
// do something with the data
_this.x = d3.scale.ordinal().rangeBands([0, _this.width], 0, 0);
_this.y = d3.scale.linear().range([_this.height, 0]);
_this.svg = d3.select("#graph").append("svg").attr("width", _this.width + _this.margin.right + _this.margin.left).attr("height", _this.height + _this.margin.top + _this.margin.bottom).append("g").attr("transform", "translate(" + 0 + "," + 50 + ")");
_this.svg.append("rect").attr("x", -200).attr("y", -200).attr("width", 1200).attr("height", 1200).attr("fill", "#FFFFFF").on("click", function(data, index) {
_this.removeRenderedContent();
return _this.clickOnBackgroundRect();
});
_this.groups = _this.svg.selectAll("g").data(json).enter().append("g").attr("class", function(data, index) {return "group-case " + data.type;});
_this.groups.on("click", function(data, index) {
_this.removeRenderedContent();
_this.clickOnGroupCaseRect(data, index, this);
return false;
});
_this.groups.attr("transform", function(data, index) {
return "translate(" + data.x + "," + data.y + ")";
});
_this.groups.append("rect").attr("width", _this.widthFilter).attr("height", _this.heightFilter).attr("x", 0).attr("y", 0).attr("rx", 10).attr("ry", 10);
_this.groups.append("g").append("text").attr("y", 7).attr("x", 7).text(function(data) {return data.title;}).call(function(data, xpadding) {return _this.wrapGroup(data, xpadding);}, 7);
_this = _this;
return _this.groups.append("g").each(function(data, index) {
return _this.drawChildren(this, data, index);
});
};
})(this));
};
What i want to do is to make this graph responsive to the current browser-window size. Does anyone know the easiest way to do this?
Thanks in advance.
I'm building a map using D3 here: http://generationone.kera.org/map/
I'm hoping to find some way to prevent wrapping around the canvas (especially off the top) . I tried a couple different threads on here about translating the path and similar with little luck...
var origins = (function() {
var origins = null;
jQuery.ajax({
'async': false,
'global': false,
'url': 'http://generationone.kera.org/map/json/labels.json',
'dataType': "json",
'success': function(data) {
origins = data;
}
});
return origins;
})();
var color = d3.scale.quantize().range(['rgb(254,237,222)', 'rgb(253,208,162)',
'rgb(253,174,107)', 'rgb(253,141,60)', 'rgb(230,85,13)',
'rgb(166,54,3)'
]).domain([1, 6]);
var legend = d3.select('#legend').append('ul').attr('class', 'list-inline');
var keys = legend.selectAll('li.key').data(color.range());
keys.enter().append('li').attr('class', 'key').style('border-top-color', String)
.text(function(d) {
var r = color.invertExtent(d);
/* return formats.percent(r[0]); */
console.log(d);
if (d == "rgb(254,237,222)") {
return "-...";
} else if (d == "rgb(166,54,3)") {
return "...+";
} else {
return ".....";
}
});
var tooltip = d3.select("body").append("div").style("position", "absolute").style(
"z-index", "10").style("visibility", "hidden").text("EMPTY");
var currentWidth =jQuery('#map').width();
var width = 938;
var height = 620;
var projection = d3.geo.mercator() /* albers, mercator */ .scale(175).translate(
[width / 2, height / 1.41]);
var path = d3.geo.path().pointRadius(2).projection(projection);
var svg = d3.select("#map").append("svg").attr("preserveAspectRatio",
"xMidYMid").attr("viewBox", "0 0 " + width + " " + height).attr("width",
currentWidth).attr("height", currentWidth * height / width);
function loaded(error, countries, labels) {
svg.append("g").attr("class", "countries").selectAll("path").data(
topojson.feature(countries, countries.objects.countries).features
).enter().append("path").attr("class", function(d) {
return d.properties.name;
}).attr("d", path);
svg.append("g").attr("class", "labels").selectAll("path").data(topojson
.feature(labels, labels.objects.labels).features).enter().append(
"path").attr("d", path);
var lineFunction = d3.svg.line().x(function(d) {
return d.x;
}).y(function(d) {
return d.y;
}).interpolate("linear");
var i = origins.features.length - 1;
while (i > -1) {
var route = svg.append("path").datum({
type: "LineString",
coordinates: [origins.features[i].geometry.coordinates, [-
98.22359954643433, 30.627013239728797
]]
}).attr("class", "route").attr("d", path).attr("data-amount",
origins.features[i].properties.amount).attr("data-name",
origins.features[i].properties.name)
.style("stroke", function(d) {
var rank = origins.features[i].properties.rank;
if (rank) {
return color(rank);
} else {
return "#FEEBE2";
}
}).on("mouseover", function() {
d3.select(this).classed("active", true)
var sel_country = d3.select(this).attr("data-name");
var sel_amount = d3.select(this).attr("data-amount");
console.log(sel_country+sel_amount);
jQuery("." + sel_country).each(function(index) {
jQuery(this).css("fill", "#ffffff");
});
setTimeout(function() {
// create the notification
if (jQuery(".ns-box").length == 0) {
var notification = new NotificationFx({
message: sel_country + " " +
sel_amount,
layout: 'attached',
effect: 'bouncyflip',
type: 'notice', // notice, warning or error
onClose: function() {}
});
// show the notification
notification.show();
} else {
jQuery(".ns-box-inner").text(sel_country +
" " + sel_amount);
}
/* end if */
}, 100);
}).on("mouseout", function() {
d3.select(this).classed("active", false)
var sel_country = d3.select(this).attr("data-name");
jQuery("." + sel_country).css("fill", "#B0D0AB");
}).attr("stroke-dasharray", 2000).attr("stroke-dashoffset",
2000).transition().duration(6000).ease("linear").attr(
"stroke-dashoffset", 0);
i--;
}
}
queue().defer(d3.json, "http://generationone.kera.org/map/json/countries.topo.json").defer(d3.json,
"http://generationone.kera.org/map/json/labels.topo.json").defer(d3.json, "http://generationone.kera.org/map/json/labels.json").await(
loaded);
jQuery(window).resize(function() {
currentWidth =jQuery("#map").width();
svg.attr("width", currentWidth);
svg.attr("height", currentWidth * height / width);
});
Is there an equivalent implementation of a Bubble Tree in D3? In the link I provided, the Bubble Tree was implemented in RaphaelJS and jQuery.
The straight answer to your question is no.
Using the resources at https://github.com/okfn/bubbletree/tree/master/build, the information you already know, and the information provided on http://d3js.org/ and through D3's documentation on GitHub, you should be able to conjure up your own bubble tree for D3!
This is a piece of JavaScript I used a long time ago to visualize binary tree data:
var updateVisual;
updateVisual = function() {
var drawTree, out;
drawTree = function(out, node) {
var col, gray, i, line, lineElt, lines, sub, _results, _results1;
if (node.lines) {
out.appendChild(document.createElement("div")).innerHTML = "<b>leaf</b>: " + node.lines.length + " lines, " + Math.round(node.height) + " px";
lines = out.appendChild(document.createElement("div"));
lines.style.lineHeight = "6px";
lines.style.marginLeft = "10px";
i = 0;
_results = [];
while (i < node.lines.length) {
line = node.lines[i];
lineElt = lines.appendChild(document.createElement("div"));
lineElt.className = "lineblock";
gray = Math.min(line.text.length * 3, 230);
col = gray.toString(16);
if (col.length === 1) col = "0" + col;
lineElt.style.background = "#" + col + col + col;
console.log(line.height, line);
lineElt.style.width = Math.max(Math.round(line.height / 3), 1) + "px";
_results.push(i++);
}
return _results;
} else {
out.appendChild(document.createElement("div")).innerHTML = "<b>node</b>: " + node.size + " lines, " + Math.round(node.height) + " px";
sub = out.appendChild(document.createElement("div"));
sub.style.paddingLeft = "20px";
i = 0;
_results1 = [];
while (i < node.children.length) {
drawTree(sub, node.children[i]);
_results1.push(++i);
}
return _results1;
}
};
out = document.getElementById("btree-view");
out.innerHTML = "";
return drawTree(out, editor.getDoc());
};
Just insert some circular elements and manipulate it a bit to style in a circular manor and you should have a good program set!
Here you go. I didn't add the text or decorations, but it's the meat and potatoes:
function bubbleChart(config) {
var aspectRatio = 1,
margin = { top: 0, right: 0, bottom: 0, left: 0 },
radiusScale = d3.scale.sqrt(),
scan = function(f, data, a) {
a = a === undefined ? 0 : a;
var results = [a];
data.forEach(function(d, i) {
a = f(a, d);
results.push(a);
});
return results;
},
colorScale = d3.scale.category20(),
result = function(selection) {
selection.each(function(data) {
var outerWidth = $(this).width(),
outerHeight = outerWidth / aspectRatio,
width = outerWidth - margin.left - margin.right,
height = outerHeight - margin.top - margin.bottom,
smallestDimension = Math.min(width, height),
sum = data[1].reduce(function(a, d) {
return a + d[1];
}, 0),
radiusFractions = data[1].map(function(d) {
return Math.sqrt(d[1] / sum);
}),
radiusNormalSum = radiusFractions.reduce(function(a, d) {
return a + d;
}, 0),
scanned = scan(function(a, d) {
return a + d;
}, radiusFractions.map(function(d) {
return d / radiusNormalSum;
}), 0);
radiusScale.domain([0, sum]).range([0, smallestDimension / 6]);
var svg = d3.select(this).selectAll('svg').data([data]),
svgEnter = svg.enter().append('svg');
svg.attr('width', outerWidth).attr('height', outerHeight);
var gEnter = svgEnter.append('g'),
g = svg.select('g').attr('transform', 'translate(' + margin.left + ' ' + margin.top + ')'),
circleRing = g.selectAll('circle.ring').data(data[1]),
circleRingEnter = circleRing.enter().append('circle').attr('class', 'ring');
circleRing.attr('cx', function(d, i) {
return smallestDimension / 3 * Math.cos(2 * Math.PI * (scanned[i] + scanned[i + 1]) / 2) + width / 2;
}).attr('cy', function(d, i) {
return smallestDimension / 3 * Math.sin(2 * Math.PI * (scanned[i] + scanned[i + 1]) / 2) + height / 2;
}).attr('r', function(d) {
return radiusScale(d[1]);
}).style('fill', function(d) {
return colorScale(d[0]);
});
var circleMain = g.selectAll('circle#main').data([data[0]]),
circleMainEnter = circleMain.enter().append('circle').attr('id', 'main');
circleMain.attr('cx', width / 2).attr('cy', height / 2).attr('r', radiusScale(sum)).style('fill', function(d) {
return colorScale(d);
});
});
};
result.aspectRatio = function(value) {
if(value === undefined) return aspectRatio;
aspectRatio = value;
return result;
};
result.margin = function(value) {
if(value === undefined) return margin;
margin = value;
return result;
};
return result;
}
var myBubbleChart = bubbleChart().margin({
top: 1,
right: 1,
bottom : 1,
left: 1
});
var data = ['Random Names, Random Amounts', [['Immanuel', .4], ['Pascal', 42.9], ['Marisa', 3.3], ['Hadumod', 4.5], ['Folker', 3.2], ['Theo', 4.7], ['Barnabas', 1.0], ['Lysann', 11.1], ['Julia', .7], ['Burgis', 28.2]]];
d3.select('#here').datum(data).call(myBubbleChart);
<div class="container">
<div class="row">
<div class="col-xs-12">
<div id="here"></div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
You can use the pack layout , basically you can bind any data you want to the shapes in the graph and custom parameters for them to position well respect to each other. Another alternative would be the force layout.