Continent zoom topojson - d3.js
I'm trying to make the map able to zoom in on continenents on mouseclick. The code is based on Mike Bostock's tutorial on making maps and uses a CSV dataset to bind some values to the countries. https://bost.ocks.org/mike/map/.
I've tried to use various examples like this one: https://bl.ocks.org/mbostock/2206590 but nothing seems to work. The map just dissapears when I try to add a .onclick attribute. Does anyone have an idea how I can make the zoom work?
HTML
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
width: 960px;
height: 500px;
position: relative;
}
#canvas {
}
#canvas-svg {
}
.land {
fill: #222;
}
.boundary {
fill: none;
stroke: #fff;
stroke-width: 1px;
}
#tooltip-container {
position: absolute;
background-color: #fff;
color: #000;
padding: 10px;
border: 1px solid;
display: none;
}
.tooltip_key {
font-weight: bold;
}
.tooltip_value {
margin-left: 20px;
float: right;
}
</style>
<div id="tooltip-container"></div>
<div id="canvas-svg"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.20/topojson.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script>
d3.csv("import.csv", function(err, data) {
var config = {"data0":"Country","data1":"Total","label0":"label 0","label1":"label 1","color0":"#99ccff","color1":"#0050A1","width":800,"height":400}
var width = 960,
height = 960;
var COLOR_COUNTS = 9;
function Interpolate(start, end, steps, count) {
var s = start,
e = end,
final = s + (((e - s) / steps) * count);
return Math.floor(final);
}
function Color(_r, _g, _b) {
var r, g, b;
var setColors = function(_r, _g, _b) {
r = _r;
g = _g;
b = _b;
};
setColors(_r, _g, _b);
this.getColors = function() {
var colors = {
r: r,
g: g,
b: b
};
return colors;
};
}
function clicked(d) {
var x, y, k;
if (d && centered !== d) {
var centroid = path.centroid(d);
x = centroid[0];
y = centroid[1];
k = 4;
centered = d;
} else {
x = width / 2;
y = height / 2;
k = 1;
centered = null;
}
g.selectAll("path")
.classed("active", centered && function(d) { return d === centered; });
g.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
}
function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
function valueFormat(d) {
if (d > 1000000000) {
return "$" + Math.round(d / 1000000000 * 10) / 10 + "M";
} else if (d > 1000000) {
return "$" + Math.round(d / 1000000 * 10) / 10 + "M";
} else if (d > 1000) {
return "$" + Math.round(d / 1000 * 10) / 10 + "B";
} else {
return "$" + d + "M";
}
}
var COLOR_FIRST = config.color0, COLOR_LAST = config.color1;
var rgb = hexToRgb(COLOR_FIRST);
var COLOR_START = new Color(rgb.r, rgb.g, rgb.b);
rgb = hexToRgb(COLOR_LAST);
var COLOR_END = new Color(rgb.r, rgb.g, rgb.b);
var startColors = COLOR_START.getColors(),
endColors = COLOR_END.getColors();
var colors = [];
for (var i = 0; i < COLOR_COUNTS; i++) {
var r = Interpolate(startColors.r, endColors.r, COLOR_COUNTS, i);
var g = Interpolate(startColors.g, endColors.g, COLOR_COUNTS, i);
var b = Interpolate(startColors.b, endColors.b, COLOR_COUNTS, i);
colors.push(new Color(r, g, b));
}
var MAP_KEY = config.data0;
var MAP_VALUE = config.data1;
var projection = d3.geo.mercator()
.scale((width + 1) / 2 / Math.PI)
.translate([width / 2, height / 2])
.precision(.1);
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule();
var svg = d3.select("#canvas-svg").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
var valueHash = {};
function log10(val) {
return Math.log(val);
}
data.forEach(function(d) {
valueHash[d[MAP_KEY]] = +d[MAP_VALUE];
});
var quantize = d3.scale.quantize()
.domain([0, 1.0])
.range(d3.range(COLOR_COUNTS).map(function(i) { return i }));
quantize.domain([d3.min(data, function(d){
return (+d[MAP_VALUE]) }),
d3.max(data, function(d){
return (+d[MAP_VALUE]) })]);
d3.json("./world-topo-min.json", function(error, world) {
var countries = topojson.feature(world, world.objects.countries).features;
svg.append("path")
.datum(graticule)
.attr("class", "choropleth")
.attr("d", path);
var g = svg.append("g");
g.append("path")
.datum({type: "LineString", coordinates: [[-180, 0], [-90, 0], [0, 0], [90, 0], [180, 0]]})
.attr("class", "equator")
.attr("d", path);
var country = g.selectAll(".country").data(countries);
country.enter().insert("path")
.attr("class", "country")
.attr("d", path)
.attr("id", function(d,i) { return d.id; })
.attr("title", function(d) { return d.properties.name; })
.style("fill", function(d) {
if (valueHash[d.properties.name]) {
var c = quantize((valueHash[d.properties.name]));
var color = colors[c].getColors();
return "rgb(" + color.r + "," + color.g +
"," + color.b + ")";
} else {
return "#ccc";
}
})
.on("mousemove", function(d) {
var html = "";
html += "<div class=\"tooltip_kv\">";
html += "<span class=\"tooltip_key\">";
html += d.properties.name;
html += "</span>";
html += "<span class=\"tooltip_value\">";
html += (valueHash[d.properties.name] ? valueFormat(valueHash[d.properties.name]) : "");
html += "";
html += "</span>";
html += "</div>";
$("#tooltip-container").html(html);
$(this).attr("fill-opacity", "0.8");
$("#tooltip-container").show();
var coordinates = d3.mouse(this);
var map_width = $('.choropleth')[0].getBoundingClientRect().width;
if (d3.event.pageX < map_width / 2) {
d3.select("#tooltip-container")
.style("top", (d3.event.layerY + 15) + "px")
.style("left", (d3.event.layerX + 15) + "px");
} else {
var tooltip_width = $("#tooltip-container").width();
d3.select("#tooltip-container")
.style("top", (d3.event.layerY + 15) + "px")
.style("left", (d3.event.layerX - tooltip_width - 30) + "px");
}
})
.on("mouseout", function() {
$(this).attr("fill-opacity", "1.0");
$("#tooltip-container").hide();
});
g.append("path")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
});
function clicked(d) {
if (active.node() === this) return reset();
active.classed("active", false);
active = d3.select(this).classed("active", true);
var bounds = path.bounds(d),
dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = .9 / Math.max(dx / width, dy / height),
translate = [width / 2 - scale * x, height / 2 - scale * y];
g.transition()
.duration(750)
.style("stroke-width", 1.5 / scale + "px")
.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
}
function reset() {
active.classed("active", false);
active = d3.select(null);
g.transition()
.duration(750)
.style("stroke-width", "1.5px")
.attr("transform", "");
}
d3.select(self.frameElement).style("height", height + "px");
});
</script>
CSV
,Country,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,Total
,Afghanistan,,,34,,,35,3,41,150,344,377,660,521,235,288,74,2763
,African Union**,,,,,,53,9,,10,,,,,2,,2,76
,Albania,,,,2,,7,,5,,,,,15,7,7,7,50
,Algeria,412,546,249,195,242,159,340,509,1529,1075,829,1115,889,373,487,636,9585
,Angola,147,156,148,48,8,39,7,31,29,20,,,,31,2,61,726
,Argentina,213,6,16,12,168,3,9,24,23,16,14,46,36,44,14,23,668
,Armenia,,,,,118,,,1,,46,52,5,,16,,,238
,Australia,338,1236,663,801,472,459,727,675,445,775,1507,1567,876,255,932,1574,13301
,Austria,25,15,69,42,55,23,4,269,190,286,7,8,8,6,2,,1008
,Azerbaijan,3,,89,,4,53,159,211,29,142,146,557,333,398,602,285,3012
,Bahamas,33,,,,,3,,,,1,,,,,29,22,88
,Bahrain,299,30,54,1,10,66,63,26,20,,103,1,26,78,10,,786
,Bangladesh,203,266,41,8,32,10,221,88,13,,35,193,252,727,257,653,2997
,Barbados,,,,,,,,10,10,10,,,,,,,29
,Belarus,,,,,,6,140,,,3,,75,75,75,60,164,598
,Belgium,35,29,56,21,15,3,27,174,203,90,32,21,32,46,103,49,934
,Belize,1,,,,,,,,,,,,,,,,1
,Benin,,,7,,,,,3,,1,0,0,20,2,,,33
,Bhutan,,,,,0,,,,,,,,,,,1,1
,Bolivia,19,,,5,1,1,9,2,3,5,1,25,12,1,46,7,136
,Botswana,53,15,1,9,8,,,,,13,10,,,8,,,119
,Brazil,122,626,213,100,81,224,179,189,179,189,318,312,311,223,284,289,3840
,Brunei,,4,1,0,0,1,2,,,33,33,229,22,15,122,36,498
,Bulgaria,,,,2,12,132,22,48,116,119,8,29,5,,,3,495
,Burkina Faso,,,,,,12,1,4,,2,0,15,4,,5,6,48
,Burundi,,1,3,,,,,,,1,2,,5,,,1,14
,Cambodia,,,,,,,14,56,,4,34,,77,67,,,252
,Cameroon,,7,6,,,5,2,,1,,10,,,39,107,2,177
,Canada,570,538,428,152,352,124,104,456,408,106,236,351,211,194,306,395,4931
,Cape Verde,1,,,,,,,,,2,,,10,,,,12
,Central African Republic,,,,,,,9,,0,,,7,,,,,16
,Chad,,16,,,,,17,20,75,35,38,1,4,59,57,,322
,Chile,202,60,74,187,70,449,1095,662,397,335,475,316,62,51,125,114,4673
,China,2552,2833,2891,2373,3316,3558,2900,1692,1892,1407,1045,1128,1703,1452,1184,1214,33140
,Colombia,60,278,171,147,18,16,56,234,110,248,255,145,207,162,190,215,2510
,Comoros,,,,,,,,,6,,,,1,,,,7
,Democratic Republic of Congo,0,,,,,4,1,0,,1,27,,23,12,13,,81
,Costa Rica,,,,,,,,,,,,,1,,1,,2
,Cote d'Ivoire,0,,32,61,9,,,,,,,,,,6,6,113
,Croatia,,57,2,24,8,,,15,99,4,10,17,17,,63,11,326
,Cyprus,4,115,1,6,16,20,26,17,,,44,41,6,,,,295
,Czech Republic,14,65,47,97,7,593,45,10,23,6,13,71,22,15,,0,1029
,Denmark,50,153,51,54,228,120,83,146,95,129,17,,109,42,177,0,1455
,Djibouti,1,,3,,4,7,,,,,,,6,5,12,7,44
,Dominican Republic,13,5,0,8,35,2,,,,10,51,12,,,,1,137
,DR Congo,88,15,12,,,19,17,,18,41,151,,10,1,,,374
,Ecuador,,10,1,,15,48,17,2,102,77,90,68,88,11,53,1,583
,Egypt,837,847,719,630,654,778,678,651,333,159,686,630,281,675,368,1475,10401
,El Salvador,,,19,10,,,,,4,,,,2,,20,,55
,Equatorial Guinea,,7,,,7,12,,28,30,68,3,82,65,2,92,,394
,Eritrea,20,57,18,17,80,98,,4,,,,,,,,,293
,Estonia,27,,1,13,8,23,7,19,29,43,13,1,2,18,2,22,228
,Ethiopia,140,,20,174,239,,,,,,54,76,193,153,30,,1079
,Finland,474,9,12,227,77,107,136,108,135,42,50,58,76,280,156,228,2172
,France,107,66,43,57,93,2,67,74,5,76,103,33,91,107,17,34,976
,Gabon,,,,,6,,20,18,,,22,5,1,2,,3,77
,Gambia,,,,,5,,,,,16,,,,,,,21
,Georgia,6,85,,1,37,92,97,155,91,36,,2,,5,1,,608
,Germany,113,133,71,64,239,204,416,82,292,339,282,84,157,113,114,102,2804
,Ghana,1,12,1,6,35,0,0,16,,11,2,76,39,63,14,13,290
,Greece,708,787,400,2274,1368,405,731,1712,516,1229,652,80,37,52,199,762,11910
,Guatemala,,,,,,,,,0,,,,2,,,33,35
,Guinea,18,6,,2,,1,,0,,,4,1,4,,5,,40
,Guyana,,7,3,,,,,,0,,,,,,,,10
,Palestine,,,,,,,,,,,1,2,,,0,,3
,Lebanon,0,0,3,0,1,0,9,,,20,20,,,,,,54
,Honduras,,,,,,,,,0,,,0,0,20,13,6,39
,Hungary,14,14,,,72,13,253,196,5,4,18,9,,2,7,,604
,Iceland,,,,,,,,,,,,50,,,,,50
,India,995,1321,1911,2878,2180,1161,1480,2299,1867,1945,3017,3706,4545,5291,3487,3078,41160
,Indonesia,151,27,68,356,79,36,61,577,239,436,225,250,218,802,1136,683,5341
,Iran,418,524,435,282,121,57,423,331,62,62,103,103,47,31,13,13,3025
,Iraq,,,,,71,186,305,263,377,398,453,603,474,353,650,1215,5348
,Ireland,0,45,22,,19,5,13,19,22,0,5,2,1,,40,41,232
,Israel,368,131,349,189,852,1133,1122,862,676,153,67,85,120,153,510,617,7387
,Italy,241,221,243,559,446,162,433,525,220,109,115,298,219,91,145,596,4623
,Jamaica,,,,,,10,10,13,2,,1,,,1,,2,38
,Japan,491,441,477,480,400,464,511,498,680,525,426,292,270,306,480,310,7050
,Jordan,133,160,121,281,157,49,48,176,161,234,87,214,175,81,252,198,2526
,Kazakhstan,121,139,20,,47,42,44,82,25,39,58,52,84,141,93,419,1406
,Kenya,15,,,,,,,10,,,112,14,60,1,17,30,258
,Kosovo,,,,,,,,,,,,,,,1,,1
,Kuwait,245,67,23,45,2,12,5,279,5,9,85,113,31,73,692,366,2052
,Kyrgyzstan,,,,9,5,3,2,,,,,,,,,,19
,Laos,7,36,,0,,4,,,7,26,,14,19,19,,7,138
,Latvia,3,13,3,25,12,4,9,49,44,11,14,27,9,5,5,1,234
,Lebanon,4,,,,,1,,5,1,52,64,2,31,24,5,50,239
,Lesotho,,6,,,1,,1,,,,,,,,,,8
,Liberia,8,,,,,,,,,,,,,,,,8
,Libya,1,14,15,16,26,24,8,73,120,10,44,349
,Lithuania,6,16,12,1,47,9,52,4,27,27,8,1,1,73,3,21,308
,Uganda,,,0,,,,,,,,,,,,,,0
,Sri Lanka,1,,1,,,0,,,,,,,,,,,2
,Luxembourg,,,,1,,,,,,7,7,,1,,,,16
,Macedonia,14,112,,,,,0,,,,,,,1,,,128
,Madagascar,,,,,,,,,,0,,,,0,,,0
,Malawi,,,,,,,,,3,,,,1,2,2,,7
,Malaysia,26,26,154,147,65,57,398,526,508,1512,421,5,47,71,73,190,4227
,Maldives,,,,,,,15,,,,5,,,5,,,24
,Mali,7,,1,,,13,,8,3,10,,,9,6,,,57
,Malta,0,0,,,,18,,,,,,8,8,,12,,45
,Mauritania,31,,26,7,,,,,,,9,5,11,10,5,,103
,Mauritius,,,,,6,,,,,,,,,,45,,51
,Mexico,227,152,97,31,320,33,75,,22,59,112,229,261,69,97,500,2284
,Mongolia,,,,,,,,,14,,27,3,51,,,13,107
,Morocco,125,11,156,12,14,101,47,27,46,39,306,1398,826,82,572,42,3803
,Mozambique,0,,,1,,,,,,,,0,0,9,12,,22
,Myanmar,16,149,157,83,194,160,175,138,93,52,63,664,478,237,300,320,3277
,Namibia,,21,11,,16,,72,6,66,15,,1,57,9,2,,276
,NATO**,,18,,,,,116,,,420,,,22,,,,576
,Nepal,,11,9,9,34,6,,,1,,,3,,,5,14,91
,Netherlands,125,178,244,125,127,102,320,240,157,288,193,149,376,332,10,86,3052
,New Zealand,,45,17,107,49,10,5,81,2,48,58,23,25,26,79,56,631
,Nicaragua,,,,,,,,14,,14,,,,,,,28
,Niger,,,,14,,,,,8,1,0,,,13,3,9,48
,Nigeria,36,7,6,62,6,,15,52,19,65,186,65,4,35,224,188,971
,Macedonia,,0,,,,,,,,,,,,,,,0
,Afghanistan,19,207,,,,,,,,,,,,,,,226
,North Korea,18,28,9,8,8,5,15,5,5,4,1,,,,,,103
,Norway,229,99,90,14,6,14,542,567,624,588,159,614,149,62,23,143,3921
,Libya,,,,,,,,,,,,0,,,,,0
,Oman,118,33,40,36,41,173,301,16,75,93,30,21,120,490,736,148,2469
,Pakistan,175,408,541,628,455,421,338,670,1047,1214,2176,1063,1028,1144,752,735,12797
,Palestine,,,,,,,,2,,14,6,,,,,,21
,Panama,,,1,,,,,,,7,,,15,30,,,52
,Papua New Guinea,,,,,,,,,,,,,,,3,,3
,Paraguay,,6,,,4,1,,,,,3,8,7,0,,,29
,Peru,24,5,16,22,47,368,193,172,,43,46,63,10,49,153,169,1380
,Philippines,,10,5,10,34,12,23,16,10,1,3,63,16,75,12,158,449
,PIJ (Israel/Palestine)*,,,,,,,,,,,,,0,,,,0
,Poland,148,36,255,349,225,112,463,990,594,169,158,92,207,155,252,131,4336
,Portugal,2,19,,29,70,157,220,60,144,369,892,168,40,13,4,11,2196
,PRC (Israel/Palestine)*,,,,,,,,,,,,,,,0,,0
,Qatar,14,11,11,11,,,,27,,286,30,198,319,73,55,655,1690
,Romania,21,19,16,17,285,437,61,98,78,56,105,80,21,20,0,22,1335
,Russian Federation,,,,,,,4,100,,8,22,11,98,153,206,88,690
,Rwanda,14,,,,,,2,15,8,11,,,5,11,14,7,86
,Saudi Arabia,85,61,567,167,1170,167,205,214,363,796,1070,1237,1080,1672,2782,3161,14796
,Senegal,,,,,,15,9,18,6,4,4,20,,6,7,11,99
,Serbia,1,27,,,,,,,,,16,,0,,,,44
,Seychelles,,,,,,15,,,,,,8,,7,16,,45
,Sierra Leone,,,,,,,9,,,,,2,0,1,,,12
,Singapore,798,254,234,83,376,538,69,355,1113,1481,1020,935,828,780,683,98,9645
,Slovakia,0,,27,,,4,,0,,9,9,,2,6,,,57
,Slovenia,2,41,2,17,17,3,3,2,,6,40,28,,,,,162
,Somalia,,,,,,,,,,,,,,3,,0,3
,South Africa,6,18,,,,262,708,881,486,128,180,212,132,2,50,,3065
,South Korea,1396,773,528,752,1059,804,1650,1755,1683,796,1250,1553,1066,182,715,245,16207
,South Sudan,,,,,,,,37,44,1,,61,3,5,18,22,190
,Spain,334,168,289,253,315,363,315,337,346,235,290,181,238,176,127,153,4119
,Sri Lanka,297,161,45,35,49,58,97,89,71,,5,21,,,5,,934
,Sudan,,106,49,204,293,132,65,33,128,89,182,173,119,196,61,27,1858
,Suriname,2,,,,,,,,,,,,,7,,3,11
,Swaziland,1,1,,,,,,,,,,,,,,,2
,Sweden,204,165,75,64,47,78,122,41,44,54,51,191,206,52,43,43,1481
,Switzerland,23,68,63,117,203,168,83,108,18,39,47,31,9,4,0,2,983
,Syrian Arab Republic,64,28,45,69,25,35,100,20,276,193,298,368,371,361,15,,2267
,Syria rebels*,,,,,,,,,,,,,1,1,0,,2
,Taiwan (ROC),585,345,298,117,319,691,503,12,11,60,97,198,425,553,1084,681,5978
,Tajikistan,,,,,,,13,7,,32,,,,3,,,55
,Tanzania,3,,,51,,10,12,1,,25,,78,115,92,26,20,433
,Thailand,85,117,152,131,114,70,47,8,13,64,49,270,289,392,93,185,2078
,Timor-Leste,,,,,,,,,,,18,,,,,,18
,Togo,,,,,,,,,,,1,,4,,16,,21
,Tonga,,,,,,,,,,,,,,,,5,5
,Trinidad and Tobago,11,0,2,,,,,6,,,24,12,12,,,46,113
,Tunisia,,,83,1,,168,2,,3,,7,7,,38,43,6,357
,Turkey,1190,510,905,330,257,1113,531,692,710,780,484,770,1503,650,1556,448,12427
,Turkmenistan,,,,18,21,,,,7,36,14,236,115,69,102,122,739
,UAE,247,186,222,685,1210,2170,2021,919,752,561,605,1210,1088,2235,731,1289,16128
,Uganda,6,,34,,36,10,5,,5,23,37,471,219,20,,3,868
,UIC (Somalia)*,,,,,,,0,,,,,,,,,,0
,Ukraine,,,,,,,,,,,,,,,1,10,11
,Ukraine Rebels*,,,,,,,,,,,,,,,24,,24
,United Kingdom,871,1277,719,761,212,27,308,764,508,383,511,368,586,492,214,382,8381
,United Nations**,31,,,,2,1,2,1,0,,,,,5,23,4,69
,United States,330,487,499,592,560,520,641,819,951,968,1111,995,1180,802,566,565,11587
,United Wa State (Myanmar)*,,1,,,,,,,,,,,,,,,1
,Unknown country,,2,,0,,,,,12,,8,6,,30,31,51,139
,Unknown rebel group*,,0,0,,,,,,,,,,,,,,0
,Uruguay,4,,11,,,20,8,3,78,29,20,,,6,9,8,196
,Uzbekistan,,8,9,,,,,,,,,,,,,62,79
,Venezuela,108,103,50,15,9,21,380,774,737,358,208,594,680,1165,173,162,5535
,Viet Nam,7,85,66,28,304,297,41,8,204,78,184,1039,766,362,1078,870,5414
,Yemen,158,85,593,62,254,317,38,66,40,5,258,45,38,23,6,12,2000
,Zambia,33,,2,,,0,30,5,2,,,1,66,,,24,161
,Zimbabwe,3,10,,16,,25,25,,,,,,,,,,78
Related
Colored text and line-breaks for D3 node labels
I am working on a D3 chart, but can't get the node labeling right (I would like to put the "size" on a second line, and change all the text to white. For instance, the "Toyota" node would say "Toyota Motor" and on a new line just below, "61.84"). Here is my starting point. I tried to add a pre block with the CSV data so it could run on JSfiddle, but I got stuck. I know I need to change this code in order to get the data from the pre block instead of the external CSV: d3.text("./car_companies.csv", function(error, text) { After that, I need to add a new "node append", something like this: node.append("revenue") .style("text-anchor", "middle") .attr("dy", "1.5em") .text(function(d) { return d.revenue.substring(0, d.radius / 3); }); http://jsfiddle.net/nick2ny/pqo1x670/4/ Thank you for any ideas.
Not directly related to the question, but to use the <pre> element to hold your data, you have to use: var text = d3.select("pre").text(); Instead of d3.text(). Back to the question: For printing those values, you just need: node.append("text") .attr("dy", "1.3em") .style("text-anchor", "middle") .style("fill", "white") .text(function(d) { return d.size; }); Adjusting dy the way you want. However, there is an additional problem: you're not populating size in the data array. Therefore, add this in the create_nodes function: size: data[node_counter].size, Here is the code with those changes: <!DOCTYPE html> <meta charset="utf-8"> <style type="text/css"> text { font: 10px sans-serif; } pre { display: none; } circle { stroke: #565352; stroke-width: 1; } </style> <body> <pre id="data"> Toyota Motor,61.84,Asia,239 Volkswagen,44.54,Europe,124 Daimler,40.79,Europe,104 BMW,35.78,Europe,80 Ford Motor,31.75,America,63 General Motors,30.98,America,60 </pre> <script src="https://d3js.org/d3.v3.min.js"></script> <script> Array.prototype.contains = function(v) { for (var i = 0; i < this.length; i++) { if (this[i] === v) return true; } return false; }; var width = 500, height = 500, padding = 1.5, // separation between same-color nodes clusterPadding = 6, // separation between different-color nodes maxRadius = 12; var color = d3.scale.ordinal() .range(["#0033cc", "#33cc66", "#990033"]); var text = d3.select("pre").text(); var colNames = "text,size,group,revenue\n" + text; var data = d3.csv.parse(colNames); data.forEach(function(d) { d.size = +d.size; }); //unique cluster/group id's var cs = []; data.forEach(function(d) { if (!cs.contains(d.group)) { cs.push(d.group); } }); var n = data.length, // total number of nodes m = cs.length; // number of distinct clusters //create clusters and nodes var clusters = new Array(m); var nodes = []; for (var i = 0; i < n; i++) { nodes.push(create_nodes(data, i)); } var force = d3.layout.force() .nodes(nodes) .size([width, height]) .gravity(.02) .charge(0) .on("tick", tick) .start(); var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height); var node = svg.selectAll("circle") .data(nodes) .enter().append("g").call(force.drag); node.append("circle") .style("fill", function(d) { return color(d.cluster); }) .attr("r", function(d) { return d.radius }) node.append("text") .attr("dy", ".3em") .style("text-anchor", "middle") .style("fill", "white") .text(function(d) { return d.text; }); node.append("text") .attr("dy", "1.3em") .style("text-anchor", "middle") .style("fill", "white") .text(function(d) { return d.size; }); function create_nodes(data, node_counter) { var i = cs.indexOf(data[node_counter].group), r = Math.sqrt((i + 1) / m * -Math.log(Math.random())) * maxRadius, d = { cluster: i, radius: data[node_counter].size * 1.5, text: data[node_counter].text, size: data[node_counter].size, revenue: data[node_counter].revenue, x: Math.cos(i / m * 2 * Math.PI) * 200 + width / 2 + Math.random(), y: Math.sin(i / m * 2 * Math.PI) * 200 + height / 2 + Math.random() }; if (!clusters[i] || (r > clusters[i].radius)) clusters[i] = d; return d; }; function tick(e) { node.each(cluster(10 * e.alpha * e.alpha)) .each(collide(.5)) .attr("transform", function(d) { var k = "translate(" + d.x + "," + d.y + ")"; return k; }) } // Move d to be adjacent to the cluster node. function cluster(alpha) { return function(d) { var cluster = clusters[d.cluster]; if (cluster === d) return; var x = d.x - cluster.x, y = d.y - cluster.y, l = Math.sqrt(x * x + y * y), r = d.radius + cluster.radius; if (l != r) { l = (l - r) / l * alpha; d.x -= x *= l; d.y -= y *= l; cluster.x += x; cluster.y += y; } }; } // Resolves collisions between d and all other circles. function collide(alpha) { var quadtree = d3.geom.quadtree(nodes); return function(d) { var r = d.radius + maxRadius + Math.max(padding, clusterPadding), nx1 = d.x - r, nx2 = d.x + r, ny1 = d.y - r, ny2 = d.y + r; quadtree.visit(function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== d)) { var x = d.x - quad.point.x, y = d.y - quad.point.y, l = Math.sqrt(x * x + y * y), r = d.radius + quad.point.radius + (d.cluster === quad.point.cluster ? padding : clusterPadding); if (l < r) { l = (l - r) / l * alpha; d.x -= x *= l; d.y -= y *= l; quad.point.x += x; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }); }; } </script>
Org Chart - Cluster Layout V3 to V4
I'm trying to update this Org Chart visualisation to use V4 of d3 but I'm hitting problems. This JSFiddle uses V3 while this JSFiddle uses V4. I've run into a few things that have changed, for instance the CSV parsing (lines 53-54): // var data = d3.csv.parse(csvData); var data = d3.csvParse(csvData); And the calculation of diagonal (discussed here, lines 79-97): /* var diagonal = d3.svg.diagonal.radial() .projection(function(d) { return [d.y, d.x / 180 * Math.PI]; }); */ var diagonal = function n(n, i) { var u = t.call(this, n, i), o = e.call(this, n, i), a = (u.y + o.y) / 2, l = [u, { x: u.x, y: a }, { x: o.x, y: a }, o]; return l = l.map(r), "M" + l[0] + "C" + l[1] + " " + l[2] + " " + l[3] } And the geting of nodes has changed (lines 211-212): //var nodes = cluster.nodes(root); var nodes = d3.hierarchy(root); Now I seem to be hitting an issue with those nodes with this error message for line 216: Uncaught TypeError: cluster.links is not a function Any help greatly appreciated, I'm no slouch when it comes to JS but this is my first foray into d3 and I'm getting really rather lost :-(.
Live demo: var csvData = `Associate,Manager Matt Herman,John Smith Jane Doe,John Smith Adam Brown,John Smith Susan Harris,John Smith Mike Jones,John Smith John Smith,Colin Krauss Colin Krauss,Ashley Carlin Ashley Carlin,Lia McDermott Evan Park,Lia McDermott Lauren Werner,Evan Park Shane Waterson,Evan Park Emma Smith,Evan Park Mike Gregory,Evan Park Jose Biggleman,Evan Park Michelle Spektor,Evan Park Juan Branch,Evan Park John Orbase,Evan Park Matt McCloud,Evan Park Kelsey Carsen,Evan Park Kelli Krazwinski,Colin Krauss Stephanie Goldstien,Colin Krauss Ryan Woolwine,Colin Krauss Kyle Bohm,Colin Krauss Sydney Yellen,Colin Krauss Shankar Murjhree,Colin Krauss Wayne Ellington,Colin Krauss Dwight Folds,Colin Krauss Ellen McGlynn,Colin Krauss Nicolas Smith,Colin Krauss Molly Ercole,Colin Krauss Scott Hane,Colin Krauss Regina McMahon,Colin Krauss Skip Holden,Colin Krauss Kadeem McPherson,Colin Krauss Ray Ortiz,Colin Krauss Janet Barnes,Colin Krauss Holly Gold,Colin Krauss Lance Martinez,Ashley Carlin Mike Lubow,Ashley Carlin Jordan Belsin,Ashley Carlin Tom Strithers,Ashley Carlin Jamie Raleigh,Ellen McGlynn Joseph Bowman,Ellen McGlynn Kylie Branch,Ellen McGlynn Lars Randall,Ellen McGlynn Carlos Barndt,Lia McDermott Leo Hastings,Lia McDermott Jaime Kellemen,Lia McDermott Harvey Klien,Lia McDermott Lia McDermott,Lia McDermott`; var data = d3.csvParse(csvData); var height = document.getElementById("tree-container").offsetHeight; var width = document.getElementById("tree-container").offsetWidth; var avatarRadius = 20; var translateOffset = 25; var radius = d3.min([height, width]) / 2; var cluster = d3.cluster() .size([360, radius / 1.33]) // .separation(function(a,b){return (a.parent == b.parent ? 1:2)/a.depth;}); var svg = d3.select("#tree-container").append("svg") .attr("width", radius * 2) .attr("height", radius * 2) .attr("id", "tree-container-svg") .append("g") .attr("transform", "translate(" + radius + "," + height / 2 + ")"); //Clip path needed for cicrular SVG avatars var defs = svg.append('defs'); var clipPath = defs.append('clipPath') .attr('id', 'clip-circle') .append('circle') .attr('r', avatarRadius - 2.5); function project(x, y) { var angle = (x - 90) / 180 * Math.PI, radius = y; return [radius * Math.cos(angle), radius * Math.sin(angle)]; } var diagonal = function (d) { return "M" + project(d.x, d.y) + "C" + project(d.x, (d.y + d.parent.y) / 2) + " " + project(d.parent.x, (d.y + d.parent.y) / 2) + " " + project(d.parent.x, d.parent.y); } d3.selection.prototype.moveToFront = function () { return this.each(function () { this.parentNode.appendChild(this); }); }; d3.selection.prototype.moveToBack = function () { return this.each(function () { var firstChild = this.parentNode.firstChild; if (firstChild) { this.parentNode.insertBefore(this, firstChild); } }); }; //http://www.d3noob.org/2014/01/tree-diagrams-in-d3js_11.html function treeify(list, callback) { var dataMap = list.reduce(function (map, node) { map[node.Associate] = node; return map; }, {}); var treeData = []; list.forEach(function (node) { //Assuming the highest node is the last in the csv file if (node.Manager === node.Associate) { node.Manager = "Board of Directors" callback(node); } // add to parent var parent = dataMap[node.Manager]; if (parent) { // create child array if it doesn't exist (parent.children || (parent.children = [])) // add node to child array .push(node); } else { // parent is null or missing treeData.push(node); } }); }; function findItem(root, name, callback) { var stack = []; stack.push(root); while (stack.length !== 0) { var element = stack.pop(); if (element.Associate === name) { callback(element); return; } //The up, uncompressed case else if (element.children !== undefined && element.children.length > 0) { for (var i = 0; i < element.children.length; i++) { stack.push(element.children[i]); } } //The down (compressed) case else if (element._children !== undefined && element._children.length > 0) { for (var j = 0; j < element._children.length; j++) { stack.push(element._children[j]); } } } } function defaultPlot(root, elem) { findItem(root, elem, function (d) { //Showing 1 up and below findItem(root, d.Manager, function (x) { (x.children) ? x.children.forEach(collapse): x.children = x._children; drawIt(x, root); }) }) } function collapse(d) { if (d.children) { d._children = d.children; d._children.forEach(collapse); d.children = undefined; } } //For the buggy transition interruption with many nodes function showAllCurrentPathsAndNodes() { d3.selectAll(".link").style("opacity", 1); d3.selectAll(".node").style("opacity", 1); } // Toggle children on click. function clickedNode(d, root) { //Accounting for the transition bug on the delay showAllCurrentPathsAndNodes(); if (d.children) { d._children = d.children; d.children = undefined; drawIt(root) } else { d.children = d._children; d._children = undefined; drawIt(root) } } //http://bl.ocks.org/syntagmatic/4092944 function drawIt(root) { var nodes = d3.hierarchy(root); cluster(nodes); var links = nodes.descendants().slice(1); var link = svg.selectAll("path.link").data(links); var node = svg.selectAll("g.node").data(nodes.descendants(),function(d){ return d.data.Associate; }); link.transition().duration(1000).attr("d", diagonal); d3.selectAll(".node-cicle").classed("highlight", false); showAllCurrentPathsAndNodes(); link.enter().append("path") .attr("class", "link") .attr("d", diagonal) .attr("", function (d) { d3.select(this).moveToBack(); }) .style("opacity", 0) .transition() .duration(300) .delay(function (d, i) { return 28 * i; }).style("opacity", 1); node.transition().duration(800).attr("transform", function (d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; }); var g = node.enter().append("g") .attr("class", "node") .attr("transform", function (d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; }) .style("opacity", 0) .style("cursor", function (d) { d=d.data; return ((d._children || d.children) && d.Manager !== "Board of Directors") ? "pointer" : "not-allowed"; }) .on("mouseover", function () { d3.select(this).moveToFront(); }) //Cant trust the enter append here, reassign the event listener for all nodes each draw d3.selectAll(".node") .on("click", function (d) { d=d.data; return ((d._children || d.children) && d.Manager !== "Board of Directors") ? clickedNode(d, root) : ""; }); g.transition().duration(300) .delay(function (d, i) { return 28 * i; }) .style("opacity", 1); g.append("circle") .attr("r", avatarRadius) .attr("class", "circle-marker") .style("stroke", function (d) { d = d.data; return ((d._children || d.children) && d.Manager !== "Board of Directors") ? "steelblue" : "gray"; }) .style("fill", function (d) { d = d.data; return ((d._children || d.children) && d.Manager !== "Board of Directors") ? "steelblue" : "#fff"; }); g.append("svg:image") .attr("class", "node-avatar") .attr("xlink:href", "http://safariuganda.com/wp-content/uploads/2014/12/480px-Facebook-default-no-profile-pic.jpg") .attr("height", avatarRadius * 2) .attr("width", avatarRadius * 2) .attr("x", "-" + avatarRadius) .attr("y", "-" + avatarRadius) .attr('clip-path', 'url(#clip-circle)'); //Might want to tween this? d3.selectAll(".node-avatar") .attr("transform", function (d) { return "rotate(" + (-1 * (d.x - 90)) + ")"; }); g.append("text") .attr("dy", ".31em") .attr("class", "label-text") .text(function (d) { return d.data.Associate; }) //search all labels to ensure they are right side up (cant rely on the enter append here) d3.selectAll(".label-text") .attr("text-anchor", function (d) { return d.x < 180 ? "start" : "end"; }) .attr("transform", function (d) { return d.x < 180 ? "translate(" + translateOffset + ")" : "rotate(180)translate(-" + translateOffset + ")"; }) link.exit().transition().duration(0).style("opacity", 0).remove(); node.exit().transition().duration(0).style("opactiy", 0).remove(); } treeify(data, function (treeReturn) { var root = treeReturn; defaultPlot(root, root.children[0].Associate) }); html, body { font-family: 'Open Sans', sans-serif; font-size: 12px; background-color: #fff; height: 100%; width: 100%; background-color: #f1f1f1; position: relative; display: block; } #tree-container { position: relative; display: block; margin-left: 100px; height: 100%; width: 100%; } .node circle { stroke-width: 1.5px; } .node { font: 10px sans-serif; } .link { fill: none; stroke: #ccc; stroke-width: 1.5px; } .label-text { -webkit-user-select: none; /* Chrome/Safari */ -moz-user-select: none; /* Firefox */ -ms-user-select: none; /* IE10+ */ /* Rules below not implemented in browsers yet */ -o-user-select: none; user-select: none; } <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>test</title> </head> <body> <div id="tree-container"></div> <link rel="stylesheet" href="style.css"> <script src="https://d3js.org/d3.v4.min.js"></script> <script src="index.js"></script> </body> </html> How to migrate var diagonal = d3.svg.diagonal.radial() .projection(function(d) { return [d.y, d.x / 180 * Math.PI]; }); to function project(x, y) { var angle = (x - 90) / 180 * Math.PI, radius = y; return [radius * Math.cos(angle), radius * Math.sin(angle)]; } var diagonal = function (d) { return "M" + project(d.x, d.y) + "C" + project(d.x, (d.y + d.parent.y) / 2) + " " + project(d.parent.x, (d.y + d.parent.y) / 2) + " " + project(d.parent.x, d.parent.y); } And there're many changes in the drawIt method. Just refer to https://bl.ocks.org/mbostock/4739610f6d96aaad2fb1e78a72b385ab
Wrapping multi line labels
I'm building a responsive zoomable treemap based on this project. The problem is that the labels I've are longer than the original visualization and end up not showing: function text(text) { text.selectAll("tspan") .attr("x", function(d) { return x(d.x) + 6; }) text.attr("x", function(d) { return x(d.x) + 6; }) .attr("y", function(d) { return y(d.y) + 6; }) .style("opacity", function(d) { return this.getComputedTextLength() < x(d.x + d.dx) - x(d.x) ? 1 : 0; }); } The problem I see is that the text needs to be showed completely in the first line, but I'd like to show it in multiple lines (inside the rect) instead. There's a code by Mike Bostock which seems to solve this issue but I don't know how to apply it to the treemap.
Here's a quick a modification which wraps the parent text in that example: <!DOCTYPE html> <!-- Generic treemap, based on http://bost.ocks.org/mike/treemap/ --> <html> <head> <meta charset="utf-8"> <title>Zoomable treemap template</title> <style> #chart { background: #fff; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } .title { font-weight: bold; font-size: 24px; text-align: center; margin-top: 6px; margin-bottom: 6px; } text { pointer-events: none; } .grandparent text { font-weight: bold; } rect { fill: none; stroke: #fff; } rect.parent, .grandparent rect { stroke-width: 2px; } rect.parent { pointer-events: none; } .grandparent rect { fill: orange; } .grandparent:hover rect { fill: #ee9700; } .children rect.parent, .grandparent rect { cursor: pointer; } .children rect.parent { fill: #bbb; fill-opacity: .5; } .children:hover rect.child { fill: #bbb; } </style> </head> <body> <div id="chart"></div> <script src="http://code.jquery.com/jquery-1.7.2.min.js"></script> <script src="http://d3js.org/d3.v3.min.js"></script> <script> window.addEventListener('message', function(e) { var opts = e.data.opts, data = e.data.data; return main(opts, data); }); var defaults = { margin: { top: 24, right: 0, bottom: 0, left: 0 }, rootname: "TOP", format: ",d", title: "", width: 960, height: 500 }; function main(o, data) { var root, opts = $.extend(true, {}, defaults, o), formatNumber = d3.format(opts.format), rname = opts.rootname, margin = opts.margin, theight = 36 + 16; $('#chart').width(opts.width).height(opts.height); var width = opts.width - margin.left - margin.right, height = opts.height - margin.top - margin.bottom - theight, transitioning; var color = d3.scale.category20c(); var x = d3.scale.linear() .domain([0, width]) .range([0, width]); var y = d3.scale.linear() .domain([0, height]) .range([0, height]); var treemap = d3.layout.treemap() .children(function(d, depth) { return depth ? null : d._children; }) .sort(function(a, b) { return a.value - b.value; }) .ratio(height / width * 0.5 * (1 + Math.sqrt(5))) .round(false); var svg = d3.select("#chart").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.bottom + margin.top) .style("margin-left", -margin.left + "px") .style("margin.right", -margin.right + "px") .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")") .style("shape-rendering", "crispEdges"); var grandparent = svg.append("g") .attr("class", "grandparent"); grandparent.append("rect") .attr("y", -margin.top) .attr("width", width) .attr("height", margin.top); grandparent.append("text") .attr("x", 6) .attr("y", 6 - margin.top) .attr("dy", ".75em"); if (opts.title) { $("#chart").prepend("<p class='title'>" + opts.title + "</p>"); } if (data instanceof Array) { root = { key: rname, values: data }; } else { root = data; } initialize(root); accumulate(root); layout(root); console.log(root); display(root); if (window.parent !== window) { var myheight = document.documentElement.scrollHeight || document.body.scrollHeight; window.parent.postMessage({ height: myheight }, '*'); } function initialize(root) { root.x = root.y = 0; root.dx = width; root.dy = height; root.depth = 0; } // Aggregate the values for internal nodes. This is normally done by the // treemap layout, but not here because of our custom implementation. // We also take a snapshot of the original children (_children) to avoid // the children being overwritten when when layout is computed. function accumulate(d) { return (d._children = d.values) ? d.value = d.values.reduce(function(p, v) { return p + accumulate(v); }, 0) : d.value; } // Compute the treemap layout recursively such that each group of siblings // uses the same size (1×1) rather than the dimensions of the parent cell. // This optimizes the layout for the current zoom state. Note that a wrapper // object is created for the parent node for each group of siblings so that // the parent’s dimensions are not discarded as we recurse. Since each group // of sibling was laid out in 1×1, we must rescale to fit using absolute // coordinates. This lets us use a viewport to zoom. function layout(d) { if (d._children) { treemap.nodes({ _children: d._children }); d._children.forEach(function(c) { c.x = d.x + c.x * d.dx; c.y = d.y + c.y * d.dy; c.dx *= d.dx; c.dy *= d.dy; c.parent = d; layout(c); }); } } function display(d) { grandparent .datum(d.parent) .on("click", transition) .select("text") .text(name(d)); var g1 = svg.insert("g", ".grandparent") .datum(d) .attr("class", "depth"); var g = g1.selectAll("g") .data(d._children) .enter().append("g"); g.filter(function(d) { return d._children; }) .classed("children", true) .on("click", transition); var children = g.selectAll(".child") .data(function(d) { return d._children || [d]; }) .enter().append("g"); children.append("rect") .attr("class", "child") .call(rect) .append("title") .text(function(d) { return d.key + " (" + formatNumber(d.value) + ")"; }); children.append("text") .attr("class", "ctext") .text(function(d) { return d.key; }) .call(text2); g.append("rect") .attr("class", "parent") .call(rect); var t = g.append("text") .attr("class", "ptext") .attr("dy", ".75em") t.append("tspan") .text(function(d) { return d.key; }); t.append("tspan") .attr("dy", "1.0em") .text(function(d) { return formatNumber(d.value); }); t.call(text); g.selectAll("rect") .style("fill", function(d) { return color(d.key); }); function transition(d) { if (transitioning || !d) return; transitioning = true; var g2 = display(d), t1 = g1.transition().duration(750), t2 = g2.transition().duration(750); // Update the domain only after entering new elements. x.domain([d.x, d.x + d.dx]); y.domain([d.y, d.y + d.dy]); // Enable anti-aliasing during the transition. svg.style("shape-rendering", null); // Draw child nodes on top of parent nodes. svg.selectAll(".depth").sort(function(a, b) { return a.depth - b.depth; }); // Fade-in entering text. g2.selectAll("text").style("fill-opacity", 0); // Transition to the new view. t1.selectAll(".ptext").call(text).style("fill-opacity", 0); t1.selectAll(".ctext").call(text2).style("fill-opacity", 0); t2.selectAll(".ptext").call(text).style("fill-opacity", 1); t2.selectAll(".ctext").call(text2).style("fill-opacity", 1); t1.selectAll("rect").call(rect); t2.selectAll("rect").call(rect); // Remove the old node when the transition is finished. t1.remove().each("end", function() { svg.style("shape-rendering", "crispEdges"); transitioning = false; }); } return g; } function text(text) { text.selectAll("tspan") .attr("x", function(d) { return x(d.x) + 6; }) text.attr("x", function(d) { return x(d.x) + 6; }) .attr("y", function(d) { return y(d.y) + 6; }) .each(function(d) { var tspan = this.childNodes[0]; var w = x(d.x + d.dx) - x(d.x); wrap(tspan, w, x(d.x) + 6); }) } function text2(text) { text.attr("x", function(d) { return x(d.x + d.dx) - this.getComputedTextLength() - 6; }) .attr("y", function(d) { return y(d.y + d.dy) - 6; }) .style("opacity", function(d) { return this.getComputedTextLength() < x(d.x + d.dx) - x(d.x) ? 1 : 0; }); } function rect(rect) { rect.attr("x", function(d) { return x(d.x); }) .attr("y", function(d) { return y(d.y); }) .attr("width", function(d) { return x(d.x + d.dx) - x(d.x); }) .attr("height", function(d) { return y(d.y + d.dy) - y(d.y); }); } function name(d) { return d.parent ? name(d.parent) + " / " + d.key + " (" + formatNumber(d.value) + ")" : d.key + " (" + formatNumber(d.value) + ")"; } } if (window.location.hash === "") { d3.json("https://jsonblob.com/api/7c30e101-da91-11e6-90ab-11c211a4b3d5", function(err, res) { if (!err) { console.log(res); var data = d3.nest().key(function(d) { return d.region; }).key(function(d) { return d.subregion; }).entries(res); main({ title: "World Population" }, { key: "World", values: data }); } }); } function wrap(tspan, width, x) { var text = d3.select(tspan), words = text.text().split(/\s+/).reverse(), word, line = [], y = text.attr("y"), dy = parseFloat(text.attr("dy")) || 0.4, tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", "0.75em"); while (word = words.pop()) { line.push(word); tspan.text(line.join(" ")); if (tspan.node().getComputedTextLength() > width) { line.pop(); tspan.text(line.join(" ")); line = [word]; tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", "1em").text(word); } } } </script> </body> </html>
D3 Force layout DIV vs SVG
I am trying to modify the d3 force layout from SVG to DIV's. It seems the collision detection doesnt work as well with DIV's. You can see the working examples below. (Another quick question, anyone know why css transform:translate isnt used for hardware acceleration) DIV Version <!DOCTYPE html> <meta charset="utf-8"> <style> body, html { margin: 0; width: 100%; height: 100% } .divs div { border-radius: 50%; background: red; position: absolute; } </style> <body> <script src="https://d3js.org/d3.v3.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js"></script> <script> var width = $('body').width(), height = $('body').height(), padding = 10, // separation between nodes maxRadius = 30; var n = 20, // total number of nodes m = 1; // number of distinct clusters var color = d3.scale.category10() .domain(d3.range(m)); var xPos = d3.scale.ordinal() .domain(d3.range(m)) .rangePoints([width, width], 1); var x = d3.scale.linear() .domain([0, width]) .range([0, width]); var y = d3.scale.linear() .domain([0, height]) .range([0, height]); var nodes = d3.range(n).map(function() { var i = Math.floor(Math.random() * m), v = (i + 1) / m * -Math.log(Math.random()); return { radius: Math.random() * maxRadius + 20, color: color(i), cx: xPos(i), cy: height }; }); var force = d3.layout.force() .nodes(nodes) .size([width, height]) .gravity(0) .charge(0) .on("tick", tick) .start(); var $body = d3.select("body") .append("div").attr('class', 'divs') .attr('style', function(d) { return 'width: ' + width + 'px; height: ' + height + 'px;'; }); var $div = $body.selectAll("div") .data(nodes) .enter() .append("div") .attr('style', function(d) { return 'width: ' + (d.radius * 2) + 'px; height: ' + (d.radius * 2) + 'px; margin-left: -' + d.radius + 'px; margin-top: -' + d.radius + 'px;'; }) .call(force.drag); function tick(e) { $div .each(gravity(.2 * e.alpha)) .each(collide(.5)) .style('left', function(d) { return x(Math.max(d.radius, Math.min(width - d.radius, d.x))) + 'px'; }) .style('top', function(d) { return y(Math.max(d.radius, Math.min(height - d.radius, d.y))) + 'px'; }); } // Move nodes toward cluster focus. function gravity(alpha) { return function(d) { d.y += (d.cy - d.y) * alpha; d.x += (d.cx - d.x) * alpha; }; } // Resolve collisions between nodes. function collide(alpha) { var quadtree = d3.geom.quadtree(nodes); return function(d) { var r = d.radius + maxRadius + padding, nx1 = d.x - r, nx2 = d.x + r, ny1 = d.y - r, ny2 = d.y + r; quadtree.visit(function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== d)) { var x = d.x - quad.point.x, y = d.y - quad.point.y, l = Math.sqrt(x * x + y * y), r = d.radius + quad.point.radius + (d.color !== quad.point.color) * padding; if (l < r) { l = (l - r) / l * alpha; d.x -= x *= l; d.y -= y *= l; quad.point.x += x; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }); }; } </script> <!DOCTYPE html> <meta charset="utf-8"> <style> body, html { margin: 0;width: 100%; height: 100%} circle { } </style> <body> <script src="https://d3js.org/d3.v3.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js"></script> <script> var width = $('body').width(), height = $('body').height(), padding = 10, // separation between nodes maxRadius = 40; var n = 10, // total number of nodes m = 1; // number of distinct clusters var color = d3.scale.category10() .domain(d3.range(m)); var x = d3.scale.ordinal() .domain(d3.range(m)) .rangePoints([width - 200, width], 1); var nodes = d3.range(n).map(function() { var i = Math.floor(Math.random() * m), v = (i + 1) / m * -Math.log(Math.random()); return { radius: Math.random() * maxRadius + 30, color: color(i), cx: x(i), cy: height }; }); var force = d3.layout.force() .nodes(nodes) .size([width, height]) .gravity(0) .charge(0) .on("tick", tick) .start(); var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height); var circle = svg.selectAll("circle") .data(nodes) .enter().append("circle") .attr("r", function(d) { return d.radius; }) .style("fill", function(d) { return d.color; }) .call(force.drag); function tick(e) { circle .each(gravity(.2 * e.alpha)) .each(collide(.5)) //.attr("cx", function(d) { return d.x; }) //.attr("cy", function(d) { return d.y; }); .attr("cx", function(d) { return d.x = Math.max(d.radius, Math.min(width - d.radius, d.x)); }) .attr("cy", function(d) { return d.y = Math.max(d.radius, Math.min(height - d.radius, d.y)); }); } // Move nodes toward cluster focus. function gravity(alpha) { return function(d) { d.y += (d.cy - d.y) * alpha; d.x += (d.cx - d.x) * alpha; }; } // Resolve collisions between nodes. function collide(alpha) { var quadtree = d3.geom.quadtree(nodes); return function(d) { var r = d.radius + maxRadius + padding, nx1 = d.x - r, nx2 = d.x + r, ny1 = d.y - r, ny2 = d.y + r; quadtree.visit(function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== d)) { var x = d.x - quad.point.x, y = d.y - quad.point.y, l = Math.sqrt(x * x + y * y), r = d.radius + quad.point.radius + (d.color !== quad.point.color) * padding; if (l < r) { l = (l - r) / l * alpha; d.x -= x *= l; d.y -= y *= l; quad.point.x += x; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }); }; } </script>
The reason is you are not updating the d.x and d.y in the tick so the layout will never come to know if they are colliding or not. .style('left', function(d) { //update the d.x d.x = x(Math.max(d.radius, Math.min(width - d.radius, d.x))) return d.x + 'px'; }) .style('top', function(d) { //update the d.y d.y=y(Math.max(d.radius, Math.min(height - d.radius, d.y))); return d.y + "px" }); Working code: <!DOCTYPE html> <meta charset="utf-8"> <style> body, html { margin: 0; width: 100%; height: 100% } .divs div { border-radius: 50%; background: red; position: absolute; } </style> <body> <script src="https://d3js.org/d3.v3.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js"></script> <script> var width = $('body').width(), height = $('body').height(), padding = 10, // separation between nodes maxRadius = 30; var n = 20, // total number of nodes m = 1; // number of distinct clusters var color = d3.scale.category10() .domain(d3.range(m)); var xPos = d3.scale.ordinal() .domain(d3.range(m)) .rangePoints([width, width], 1); var x = d3.scale.linear() .domain([0, width]) .range([0, width]); var y = d3.scale.linear() .domain([0, height]) .range([0, height]); var nodes = d3.range(n).map(function() { var i = Math.floor(Math.random() * m), v = (i + 1) / m * -Math.log(Math.random()); return { radius: Math.random() * maxRadius + 20, color: color(i), cx: xPos(i), cy: height }; }); var force = d3.layout.force() .nodes(nodes) .size([width, height]) .gravity(0) .charge(0) .on("tick", tick) .start(); var $body = d3.select("body") .append("div").attr('class', 'divs') .attr('style', function(d) { return 'width: ' + width + 'px; height: ' + height + 'px;'; }); var $div = $body.selectAll("div") .data(nodes) .enter() .append("div") .attr('style', function(d) { return 'width: ' + (d.radius * 2) + 'px; height: ' + (d.radius * 2) + 'px; margin-left: -' + d.radius + 'px; margin-top: -' + d.radius + 'px;'; }) .call(force.drag); function tick(e) { $div .each(gravity(.2 * e.alpha)) .each(collide(.5)) .style('left', function(d) { d.x = x(Math.max(d.radius, Math.min(width - d.radius, d.x))) return d.x + 'px'; }) .style('top', function(d) { d.y=y(Math.max(d.radius, Math.min(height - d.radius, d.y))); return d.y + "px" }); } // Move nodes toward cluster focus. function gravity(alpha) { return function(d) { d.y += (d.cy - d.y) * alpha; d.x += (d.cx - d.x) * alpha; }; } // Resolve collisions between nodes. function collide(alpha) { var quadtree = d3.geom.quadtree(nodes); return function(d) { var r = d.radius + maxRadius + padding, nx1 = d.x - r, nx2 = d.x + r, ny1 = d.y - r, ny2 = d.y + r; quadtree.visit(function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== d)) { var x = d.x - quad.point.x, y = d.y - quad.point.y, l = Math.sqrt(x * x + y * y), r = d.radius + quad.point.radius + (d.color !== quad.point.color) * padding; if (l < r) { l = (l - r) / l * alpha; d.x -= x *= l; d.y -= y *= l; quad.point.x += x; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }); }; } </script> Hope this helps!
Make D3 force directed graph responsive and adhere to bounding box
I have a force directed graph generated via D3 that isn't playing well with the responsive code or the bounding box code I've found. Since the radius of all of my circles varies, I think it's throwing some things off... Any help is appreciated! I have to use a custom length on the lines because the nodes run into each other if I don't manually space them out because the radii aren't the same. (Please don't link me to the d3 page with the code, I've tried it, but maybe I'm placing it in the wrong spot if you think it would work on this. I also tried to post an image, but I don't have enough reputation.) var width = 876, height = 600; var color = d3.scale.category20(); var force = d3.layout.force() .charge(-1010) .linkDistance(function(d) { return d.distance; }) .size([width, height]) .gravity(0.7); var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height); var tip = d3.tip() .attr('class', 'd3-tip') .offset([-5, 0]) .html(function (d) { return d.name + " (" + d.instances + ")"; }) svg.call(tip); d3.json("datawords.json", function(error, graph) { force .nodes(graph.nodes) .links(graph.links) .start(); var link = svg.selectAll(".link") .data(graph.links) .enter().append("line") .attr("class", "link") .attr("width", function(d) { return d.totalLength; }) .style("stroke-width", function(d) { return Math.sqrt(d.value); }); var node = svg.selectAll(".node") .data(graph.nodes) .enter().append("circle") .attr("class", "node") .attr("r", function(d) {return d.instances;}) .style("fill", function(d) { return color(d.instances); }) .call(force.drag) .on('mouseover', tip.show) .on('mouseout', tip.hide) .on('click', connectedNodes) force.on("tick", function() { link.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; }); node.attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); node.each(collide(0.5)) }); //Toggle stores whether the highlighting is on var toggle = 0; //Create an array logging what is connected to what var linkedByIndex = {}; for (i = 0; i < graph.nodes.length; i++) { linkedByIndex[i + "," + i] = 1; }; graph.links.forEach(function (d) { linkedByIndex[d.source.index + "," + d.target.index] = 1; }); //This function looks up whether a pair are neighbours function neighboring(a, b) { return linkedByIndex[a.index + "," + b.index]; } function connectedNodes() { if (toggle == 0) { //Reduce the opacity of all but the neighbouring nodes d = d3.select(this).node().__data__; node.style("opacity", function (o) { return neighboring(d, o) | neighboring(o, d) ? 1 : 0.1; }); link.style("opacity", function (o) { return d.index==o.source.index | d.index==o.target.index ? 1 : 0.1; }); //Reduce the op toggle = 1; } else { //Put them back to opacity=1 node.style("opacity", 1); link.style("opacity", 1); toggle = 0; }; }; var padding = 10, // separation between circles radius=15; function collide(alpha) { var quadtree = d3.geom.quadtree(graph.nodes); return function(d) { var rb = 4*radius + padding, nx1 = d.x - rb, nx2 = d.x + rb, ny1 = d.y - rb, ny2 = d.y + rb; quadtree.visit(function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== d)) { var x = d.x - quad.point.x, y = d.y - quad.point.y, l = Math.sqrt(x * x + y * y); if (l < rb) { l = (l - rb) / l * alpha; d.x -= x *= l; d.y -= y *= l; quad.point.x += x; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }); }; }; window.addEventListener('resize', resize); function resize() { width = window.innerWidth, height = window.innerHeight; svg.attr("width", width).attr("height", height); force.size([width, height]).resume(); } }); .node { stroke: #fff; stroke-width: 1.5px; } .link { stroke: #999; stroke-opacity: .6; } .node-active{ stroke: #555; stroke-width: 1.5px; } .node:hover{ stroke: #555; stroke-width: 1.5px; } marker { display:none; } .d3-tip { line-height: 1; font-weight: bold; padding: 12px; background: rgba(0, 0, 0, 0.8); color: #fff; border-radius: 2px; } .d3-tip.n:after { margin: -1px 0 0 0; top: 200%; left: 0; } <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script> <!DOCTYPE html> <meta charset="utf-8"> <body> <script src="d3/d3tip.js"></script> <div class="graph"></div> </body>