I was exploring the zoomable circle packing library from d3:
https://observablehq.com/#d3/zoomable-circle-packing
But I cannot for the love of god understand how it works. So I figured I would tinker with it by copying the code into an html file and then experimenting. Unfortunately copy the javascript here into a script tag (while referencing d3) was not sufficient to see a working chart.
To that end I did some more research and found that the package creator had a sample index.html in this post:
https://gist.github.com/mbostock/7607535
I replicated this index.html locally and ran it, which surprisingly worked, but then got the following error: my page doesn't render correctly.
See below image:
Compared to what was expected:
Code replicated here:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
cursor: pointer;
}
.node:hover {
stroke: #000;
stroke-width: 1.5px;
}
.node--leaf {
fill: white;
}
.label {
font: 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
text-anchor: middle;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff, 0 -1px 0 #fff;
}
.label,
.node--root,
.node--leaf {
pointer-events: none;
}
</style>
<svg width="960" height="960"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = 20,
diameter = +svg.attr("width"),
g = svg.append("g").attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
var color = d3.scaleLinear()
.domain([-1, 5])
.range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"])
.interpolate(d3.interpolateHcl);
var pack = d3.pack()
.size([diameter - margin, diameter - margin])
.padding(2);
d3.json("https://raw.githubusercontent.com/d3/d3-hierarchy/v1.1.8/test/data/flare.json", function(error, root) {
if (error) throw error;
root = d3.hierarchy(root)
.sum(function(d) { return d.size; })
.sort(function(a, b) { return b.value - a.value; });
var focus = root,
nodes = pack(root).descendants(),
view;
var circle = g.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("class", function(d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; })
.style("fill", function(d) { return d.children ? color(d.depth) : null; })
.on("click", function(d) { if (focus !== d) zoom(d), d3.event.stopPropagation(); });
var text = g.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("class", "label")
.style("fill-opacity", function(d) { return d.parent === root ? 1 : 0; })
.style("display", function(d) { return d.parent === root ? "inline" : "none"; })
.text(function(d) { return d.data.name; });
var node = g.selectAll("circle,text");
svg
.style("background", color(-1))
.on("click", function() { zoom(root); });
zoomTo([root.x, root.y, root.r * 2 + margin]);
function zoom(d) {
var focus0 = focus; focus = d;
var transition = d3.transition()
.duration(d3.event.altKey ? 7500 : 750)
.tween("zoom", function(d) {
var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + margin]);
return function(t) { zoomTo(i(t)); };
});
transition.selectAll("text")
.filter(function(d) { return d.parent === focus || this.style.display === "inline"; })
.style("fill-opacity", function(d) { return d.parent === focus ? 1 : 0; })
.on("start", function(d) { if (d.parent === focus) this.style.display = "inline"; })
.on("end", function(d) { if (d.parent !== focus) this.style.display = "none"; });
}
function zoomTo(v) {
var k = diameter / v[2]; view = v;
node.attr("transform", function(d) {
console.log(d);
console.log(v);
console.log(d.x);
console.log(v[0]);
return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")";
});
circle.attr("r", function(d) { return d.r * k; });
}
});
</script>
In the following part of the code:
root = d3.hierarchy(root)
.sum(function(d) { return d.size; })
.sort(function(a, b) { return b.value - a.value; });
When calling .sum, the attribute size doesn't exists, so D3 is unable to calculate the circle radius, replace it by value and it will work.
Related
I am using d3.js version 5 and circle-pack and d3 arc generator. Circle pack and zoom code is standard and used at many places. When I try to put arc text along circle, it works fine and it loads fine but when I try to zoom in or zoom out , text/labels does not accordingly put along with circle border. Here is my code: https://codepen.io/dmd7/pen/BvMwbr
I tried 'd3-circle-text' plug-in as mentioned here in 'd3-circle-text' on zoomable circle-pack but did not work as it is using older version of d3 plus zoom in functionality is different there.
Also, wanted to try what is achieved here: http://nbremer.github.io/occupations/ but did not work as again converting from older d3 version to newer d3 version makes it harder.
Here is my code: https://codepen.io/dmd7/pen/BvMwbr
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>test</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<style>
text {
font-size: 11px;
}
text.parent {
fill: #1f77b4;
}
text {
font: 10px sans-serif;
}
/*.text {
cursor: pointer;
}*/
text:hover {
/*color: #FF0000; */
font-weight: bold;
stroke: #000;
stroke-width: 0.5px;
text-decoration: underline;
}
circle {
fill: #ccc;
stroke: #999;
pointer-events: all;
cursor: pointer;
opacity: 0.55;
}
circle.parent {
fill: #1f77b4;
fill-opacity: .1;
stroke: steelblue;
}
circle.parent:hover {
stroke: #ff7f0e;
stroke-width: .5px;
}
/*circle.child {
pointer-events: none;
}*/
/*Node hover*/
/*.node {
cursor: pointer;
}
.node:hover {
stroke: #000;
stroke-width: 1.5px;
}
.node--leaf {
fill: white;
}
*/
/*Circle text*/
.label {
font: 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
text-anchor: middle;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff, 0 -1px 0 #fff;
}
.label,
.node--root,
.node--leaf {
pointer-events: none;
}
.arc-path {
visibility: hidden;
}
</style>
</head>
<body>
<div id="container">
<svg width="700" height="700" id="cir_svg"></svg>
</div>
</body>
</html>
and here is javaScript code:
var svg, margin, diameter, g, pack, focus, view, circle, node;
var maxNumLevels = 3;
document.addEventListener("DOMContentLoaded", function (e) {
svg = d3.select("svg"),
margin = 20,
diameter = +svg.attr("width");
g = svg.append("g").attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
pack = d3.pack()
.size([diameter - margin, diameter - margin])
.padding(2);
initialSpecialCase();
});
function generateLevels(pivotLevel) {
pivNum = parseInt(pivotLevel.match(/VirusL(\d+)_s/)[1]);
var r = '';
for (var n = 0; n <= maxNumLevels; ++n) {
if(n!=0) {
r += ",";
}
r += "VirusL" + (pivNum + n) + "_s";
}
curLevelsStr = r;
return r;
}
function extractTaxIdFromName(nameTaxId) {
var idx = nameTaxId.lastIndexOf(", taxid:");
if(idx != -1) {
return nameTaxId.substring(idx + 8);
}
return '';
}
function initialSpecialCase() {
var data1 = {};
d3.json("https://api.myjson.com/bins/1h5co8").then(function(dta) {
console.info("Data from ajax: ", dta);
data1 = {
'name' : 'root',
'field' : '',
'value' : dta.response.numFound,
'pivot' : dta['facet_counts']['facet_pivot'][generateLevels('VirusL0_s')]
};
createCircles(data1);
});
}
function mapData(d) {
if (!d.hasOwnProperty('pivot'))
return;
d.hasChildren = true;
var maxPivName = "VirusL" + (pivNum + maxNumLevels - 1).toString() + "_s";
if (d.field === maxPivName)
return;
var cnodes = d.pivot;
for (c in cnodes) {
if (!cnodes[c].hasOwnProperty('name')) {
cnodes[c].name = cnodes[c].value;
cnodes[c].value = cnodes[c].count;
}
}
return cnodes;
}
function arcSVG(mx0, my0, r, larc, sweep, mx1, my1) {
return 'M'+mx0+','+my0+' A'+r+','+r+' 0 '+larc+','+sweep+' '+mx1+','+my1;
}
function createCircles(data) {
var root = d3.hierarchy(data, mapData)
.sort(function(a, b) { return b.value - a.value; });
console.info(root);
focus = root;
var nodes = pack(root).descendants();
var nodeg = g.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", function(d) {
return d.children ? "node" : "leaf node";
})
;
nodeg.append("circle")
.attr("class", function(d) { return d.children ? "parent" : "child"; })
.attr("id", function(d) { return 'c' + extractTaxIdFromName(d.data.name); })
.on("click", function(d) {
selectedVirus = d.data.name;
if (!d.children && d.parent) {
zoom(d); d3.event.stopPropagation();
}
else if (focus !== d) {
zoom(d);
d3.event.stopPropagation();
}
});
nodeg.append("title")
.text(function(d) {
return d.data.name;
});
nodeg.each(function(d, i) {
var gg = d3.select(this);
if(d.depth === 3) {
// gg.append('text')
// .style('font-size', d3.min([3 * d.r / d.data.name.length, 16]))
// .attr('dy', '0.3em')
// .text(d.data.name);
}
else if(d.depth > 0) {
var rr = d.r - 5;
gg.append('path')
.attr('d', arcSVG(-rr, 0, rr, 1, 1, rr, 0))
.attr('id', 'label-path-' + i)
.style('fill', 'none')
.style('stroke', 'none');
gg.append('text')
.append('textPath')
.attr('xlink:href', '#label-path-' + i)
.attr('startOffset', '50%')
.style("text-anchor","middle")
.style('font-size', '10px')
.style('fill', 'black')
.text(d.data.name);
}
});
node = nodeg.selectAll("circle,text");
circle = nodeg.selectAll("circle");
svg
.on("click", function() {
zoom(root);
});
zoomTo([root.x, root.y, root.r * 2 + margin]);
}
function zoom(d) {
var focus0 = focus; focus = d;
var transition = d3.transition()
.duration(d3.event.altKey ? 7500 : 750)
.tween("zoom", function(d) {
var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + margin]);
return function(t) { zoomTo(i(t)); };
});
transition.selectAll("text")
.filter(function(d) { return d.parent === focus || this.style.display === "inline"; })
.style("fill-opacity", function(d) { return d.parent === focus ? 1 : 0; })
.on("start", function(d) { if (d.parent === focus) this.style.display = "inline"; })
.on("end", function(d) { if (d.parent !== focus) this.style.display = "none"; });
}
function zoomTo(v) {
var k = diameter / v[2]; view = v;
node.attr("transform", function(d) { return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")"; });
circle.attr("r", function(d) { return d.r * k; });
}
Appreciate your help.
Thank You.
You're not selecting the paths for the <textPath>s in the zoom function. It can be done as this:
svg.selectAll("path")
.attr('d', function(d){
return arcSVG(-(d.r - 5) * k, 0, (d.r - 5) * k, 1, 1, (d.r - 5) * k, 0)
});
Of course, a better idea is naming your selections, and also improving the new d attribute. Also, mind creating globals (like circle and node), just for using them in the zoom function. Finally, your logic for setting the display property of the textpaths is not working correctly.
Here is the forked Codepen: https://codepen.io/anon/pen/QzYawj?editors=0010
I am trying to get the node details (id attribute) when it is right clicked and the contextmenu function is called. I am able to get the node object using var self = d3.select(this); but I am not able to work out the
id attribute of the node (i can see it in the console log though)
I am planning to pass the id to the menu function once I'll get the node.id
JSFiddle
var circle = svg.append("g").selectAll("circle") .data(force.nodes())
.enter().append("circle").attr("r", 6) .call(force.drag)
.on('contextmenu', function(){
d3.event.preventDefault();
var self = d3.select(this);
console.log(self);
var n1=(self[0])[0];
console.log(n1);
menu(d3.mouse(svg.node())[0], d3.mouse(svg.node())[1]);
});
You can pass the datum as a parameter of the function called on the contextmenu event:
.on('contextmenu', function(d) { ... }
which allows you to get the id within the function:
console.log(d.id);
.node {
fill: #000;
}
.cursor {
fill: green;
stroke: brown;
pointer-events: none;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
#licensing {
fill: green;
}
.link.licensing {
stroke: green;
}
.link.resolved {
stroke-dasharray: 0,2 1;
}
circle {
fill: green;
stroke: red;
stroke-width: 1.5px;
}
text {
font: 10px sans-serif;
pointer-events: none;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var width = 500, height = 300;
var links = [{source:"simulator",target:"monitor" ,type:"resolved"} , {source:"web",target:"monitor" ,type:"resolved"} ];
var nodes = [ {"id":"monitor", "grp":"system"}, {"id":"simulator", "grp":"system"}, {id:"web", grp:"client"}];
function reset() {
}
function contextMenu() {
var height,
width,
margin = 0.1, // fraction of width
items = [],
rescale = false,
style = {
'rect': {
'mouseout': {
'fill': 'rgb(244,244,244)',
'stroke': 'white',
'stroke-width': '1px'
},
'mouseover': {
'fill': 'rgb(200,200,200)'
}
},
'text': {
'fill': 'steelblue',
'font-size': '13'
}
};
function menu(x, y) {
d3.select('.context-menu').remove();
scaleItems();
// Draw the menu
d3.select('svg')
.append('g').attr('class', 'context-menu')
.selectAll('tmp')
.data(items).enter()
.append('g').attr('class', 'menu-entry')
.style({'cursor': 'pointer'})
.on('mouseover', function(){
d3.select(this).select('rect').style(style.rect.mouseover) })
.on('mouseout', function(){
d3.select(this).select('rect').style(style.rect.mouseout) });
d3.selectAll('.menu-entry')
.append('rect')
.attr('x', x)
.attr('y', function(d, i){ return y + (i * height); })
.attr('width', width)
.attr('height', height)
.style(style.rect.mouseout);
d3.selectAll('.menu-entry')
.append('text')
.text(function(d){ return d; })
.attr('x', x)
.attr('y', function(d, i){ return y + (i * height); })
.attr('dy', height - margin / 2)
.attr('dx', margin)
.style(style.text);
// Other interactions
d3.select('body')
.on('click', function() {
d3.select('.context-menu').remove();
});
}
menu.items = function(e) {
if (!arguments.length) return items;
for (i in arguments) items.push(arguments[i]);
rescale = true;
return menu;
}
// Automatically set width, height, and margin;
function scaleItems() {
if (rescale) {
d3.select('svg').selectAll('tmp')
.data(items).enter()
.append('text')
.text(function(d){ return d; })
.style(style.text)
.attr('x', -1000)
.attr('y', -1000)
.attr('class', 'tmp');
var z = d3.selectAll('.tmp')[0]
.map(function(x){ return x.getBBox(); });
width = d3.max(z.map(function(x){ return x.width; }));
margin = margin * width;
width = width + 2 * margin;
height = d3.max(z.map(function(x){ return x.height + margin / 2; }));
// cleanup
d3.selectAll('.tmp').remove();
rescale = false;
}
}
return menu;
}
var width = 400,
height = 200,
radius = 8;
var map = {}
nodes.forEach(function(d,i){
map[d.id] = i;
})
links.forEach(function(d) {
d.source = map[d.source];
d.target = map[d.target];
})
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(50)
.charge(-200)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// Per-type markers, as they don't inherit styles.
svg.append("defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var menu = contextMenu().items('first item', 'second option', 'whatever, man');
var circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("r", 6)
.call(force.drag)
.on('contextmenu', function(d){
d3.event.preventDefault();
var self = d3.select(this);
var n1=(self[0])[0];
console.log(d.id);
menu(d3.mouse(svg.node())[0], d3.mouse(svg.node())[1]);
});
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.id; });
var node = svg.selectAll(".node"),
link = svg.selectAll(".link");
function mousedownNode(d, i) {
nodes.splice(i, 1);
links = links.filter(function(l) {
return l.source !== d && l.target !== d;
});
d3.event.stopPropagation();
refresh();
}
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform);
}
function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
</script>
In addition: For all the ones that want to know how to be able to show node details (for example the node name) in the context menu itself - this is my solution.
The node details can be taken from the "data" element
The placeholder can be taken from the "d" element
The desired information to show in the context menu has to be written into the "text" attribute
if(d.title == 'ConfigMenuPlaceholder'){
text = 'Config: '+data.name;
}
These lines should be written at the following position:
function createNestedMenu(parent, root, depth = 0) {
var resolve = function (value) {
return utils.toFactory(value).call(root, data, index);
};
parent.selectAll('li')
.data(function (d) {
var baseData = depth === 0 ? menuItems : d.children;
return resolve(baseData);
})
.enter()
.append('li')
.each(function (d) {
var elm = this;
// get value of each data
var isDivider = !!resolve(d.divider);
var isDisabled = !!resolve(d.disabled);
var hasChildren = !!resolve(d.children);
var hasAction = !!d.action;
var text = isDivider ? '<hr>' : resolve(d.title);
if(d.title == 'ConfigMenuPlaceholder'){
text = 'Config: '+data.name;
}
var listItem = d3.select(this)
.classed('is-divider', isDivider)
.classed('is-disabled', isDisabled)
.classed('is-header', !hasChildren && !hasAction)
.classed('is-parent', hasChildren)
.html(text)
.on('click', function () {
// do nothing if disabled or no action
if (isDisabled || !hasAction) return;
d.action(elm, data, index);
//d.action.call(root, data, index);
closeMenu();
});
if (hasChildren) {
// create children(`next parent`) and call recursive
var children = listItem.append('ul').classed('is-children', true);
createNestedMenu(children, root, ++depth)
}
});
}
Here is a parallel coordinate with some sample data
http://plnkr.co/edit/dCNuBsaDNBwr7CrAJUBe?p=preview
Here the numerical axis like column1 , column3 have small number of data so it looks ok. But in practice, I have a big data (something like 600 entries), so numerical ticks in axis become clustered , overlapped.
Is there a way so that it looks good. Like Setting the number of ticks in parallel coordinate. For instance, if I want to change the number of ticks in column1 of this example from 10 to 5.
Any help would be appreciated.
Thanks
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.background path {
fill: none;
stroke: #ddd;
stroke-opacity: .4;
shape-rendering: crispEdges;
}
.foreground path {
fill: none;
stroke: steelblue;
stroke-opacity: .7;
}
.brush .extent {
fill-opacity: .3;
stroke: #fff;
shape-rendering: crispEdges;
}
.axis line,
.axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.axis text {
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
cursor: move;
}
</style>
<body>
<script src="http://d3js.org/d3.v4.min.js"></script>
<script>
var margin = {top: 30, right: 10, bottom: 10, left: 10},
width = 600 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var x = d3.scalePoint().rangeRound([0, width]).padding(1),
y = {},
dragging = {};
var line = d3.line(),
//axis = d3.axisLeft(x),
background,
foreground,
extents;
var container = d3.select("body").append("div")
.attr("class", "parcoords")
.style("width", width + margin.left + margin.right + "px")
.style("height", height + margin.top + margin.bottom + "px");
var svg = container.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var quant_p = function(v){return (parseFloat(v) == v) || (v == "")};
d3.json("convertcsv.json", function(error, cars) {
dimensions = d3.keys(cars[0]);
x.domain(dimensions);
dimensions.forEach(function(d) {
var vals = cars.map(function(p) {return p[d];});
if (vals.every(quant_p)){
y[d] = d3.scaleLinear()
.domain(d3.extent(cars, function(p) {
return +p[d]; }))
.range([height, 0])
console.log(y[d]);
}
else{
vals.sort();
y[d] = d3.scalePoint()
.domain(vals.filter(function(v, i) {return vals.indexOf(v) == i;}))
.range([height, 0],1);
}
})
extents = dimensions.map(function(p) { return [0,0]; });
// Add grey background lines for context.
background = svg.append("g")
.attr("class", "background")
.selectAll("path")
.data(cars)
.enter().append("path")
.attr("d", path);
// Add blue foreground lines for focus.
foreground = svg.append("g")
.attr("class", "foreground")
.selectAll("path")
.data(cars)
.enter().append("path")
.attr("d", path);
// Add a group element for each dimension.
var g = svg.selectAll(".dimension")
.data(dimensions)
.enter().append("g")
.attr("class", "dimension")
.attr("transform", function(d) { return "translate(" + x(d) + ")"; })
.call(d3.drag()
.subject(function(d) { return {x: x(d)}; })
.on("start", function(d) {
dragging[d] = x(d);
background.attr("visibility", "hidden");
})
.on("drag", function(d) {
dragging[d] = Math.min(width, Math.max(0, d3.event.x));
foreground.attr("d", path);
dimensions.sort(function(a, b) { return position(a) - position(b); });
x.domain(dimensions);
g.attr("transform", function(d) { return "translate(" + position(d) + ")"; })
})
.on("end", function(d) {
delete dragging[d];
transition(d3.select(this)).attr("transform", "translate(" + x(d) + ")");
transition(foreground).attr("d", path);
background
.attr("d", path)
.transition()
.delay(500)
.duration(0)
.attr("visibility", null);
}));
// Add an axis and title.
var g = svg.selectAll(".dimension");
g.append("g")
.attr("class", "axis")
.each(function(d) { d3.select(this).call(d3.axisLeft(y[d]));})
//text does not show up because previous line breaks somehow
.append("text")
.attr("fill", "black")
.style("text-anchor", "middle")
.attr("y", -9)
.text(function(d) { return d; });
// Add and store a brush for each axis.
g.append("g")
.attr("class", "brush")
.each(function(d) {
if(y[d].name == 'r'){
// console.log(this);
d3.select(this).call(y[d].brush = d3.brushY().extent([[-8, 0], [8,height]]).on("brush start", brushstart).on("brush", go_brush).on("brush", brush_parallel_chart).on("end", brush_end));
}
else if(y[d].name == 'n')
d3.select(this).call(y[d].brush = d3.brushY().extent([[-8, 0], [15,height]]).on("brush start", brushstart).on("brush", go_brush).on("brush", brush_parallel).on("end", brush_end_ordinal));
})
.selectAll("rect")
.attr("x", -8)
.attr("width", 16);
}); // closing
function position(d) {
var v = dragging[d];
return v == null ? x(d) : v;
}
function transition(g) {
return g.transition().duration(500);
}
// Returns the path for a given data point.
function path(d) {
return line(dimensions.map(function(p) { return [position(p), y[p](d[p])]; }));
}
function go_brush() {
d3.event.sourceEvent.stopPropagation();
}
invertExtent = function(y) {
return domain.filter(function(d, i) { return y === range[i]; });
};
function brushstart(selectionName) {
foreground.style("display", "none")
//console.log(selectionName);
var dimensionsIndex = dimensions.indexOf(selectionName);
//console.log(dimensionsIndex);
extents[dimensionsIndex] = [0, 0];
foreground.style("display", function(d) {
return dimensions.every(function(p, i) {
if(extents[i][0]==0 && extents[i][0]==0) {
return true;
}
return extents[i][1] <= d[p] && d[p] <= extents[i][0];
}) ? null : "none";
});
}
// Handles a brush event, toggling the display of foreground lines.
function brush_parallel_chart() {
for(var i=0;i<dimensions.length;++i){
if(d3.event.target==y[dimensions[i]].brush) {
//if (d3.event.sourceEvent.type === "brush") return;
extents[i]=d3.event.selection.map(y[dimensions[i]].invert,y[dimensions[i]]);
}
}
foreground.style("display", function(d) {
return dimensions.every(function(p, i) {
if(extents[i][0]==0 && extents[i][0]==0) {
return true;
}
return extents[i][1] <= d[p] && d[p] <= extents[i][0];
}) ? null : "none";
});
}
function brush_end(){
if (!d3.event.sourceEvent) return; // Only transition after input.
if (!d3.event.selection) return; // Ignore empty selections.
for(var i=0;i<dimensions.length;++i){
if(d3.event.target==y[dimensions[i]].brush) {
extents[i]=d3.event.selection.map(y[dimensions[i]].invert,y[dimensions[i]]);
extents[i][0] = Math.round( extents[i][0] * 10 ) / 10;
extents[i][1] = Math.round( extents[i][1] * 10 ) / 10;
d3.select(this).transition().call(d3.event.target.move, extents[i].map(y[dimensions[i]]));
}
}
}
// brush for ordinal cases
function brush_parallel() {
for(var i=0;i<dimensions.length;++i){
if(d3.event.target==y[dimensions[i]].brush) {
var yScale = y[dimensions[i]];
var selected = yScale.domain().filter(function(d){
// var s = d3.event.target.extent();
var s = d3.event.selection;
return (s[0] <= yScale(d)) && (yScale(d) <= s[1])
});
var temp = selected.sort();
extents[i] = [temp[temp.length-1], temp[0]];
}
}
foreground.style("display", function(d) {
return dimensions.every(function(p, i) {
if(extents[i][0]==0 && extents[i][0]==0) {
return true;
}
//var p_new = (y[p].ticks)?d[p]:y[p](d[p]);
//return extents[i][1] <= p_new && p_new <= extents[i][0];
return extents[i][1] <= d[p] && d[p] <= extents[i][0];
}) ? null : "none";
});
}
function brush_end_ordinal(){
console.log("hhhhh");
if (!d3.event.sourceEvent) return; // Only transition after input.
if (!d3.event.selection) return; // Ignore empty selections.
for(var i=0;i<dimensions.length;++i){
if(d3.event.target==y[dimensions[i]].brush) {
var yScale = y[dimensions[i]];
var selected = yScale.domain().filter(function(d){
// var s = d3.event.target.extent();
var s = d3.event.selection;
return (s[0] <= yScale(d)) && (yScale(d) <= s[1])
});
var temp = selected.sort();
extents[i] = [temp[temp.length-1], temp[0]];
if(selected.length >1)
d3.select(this).transition().call(d3.event.target.move, extents[i].map(y[dimensions[i]]));
}
}
}
</script>
You can either use d3-axis.ticks() or d3-axis.tickValues([]) to specify custom number of ticks/tick values.
For example, for column1 using tickValues and column3 using ticks:
var g = svg.selectAll(".dimension");
g.append("g")
.attr("class", "axis")
.each(function(d) {
if(d === 'column1') {
d3.select(this).call(d3.axisLeft(y[d]).tickValues([.1, .2, .3, .5,.8]));
} else if(d === 'column3') {
d3.select(this).call(d3.axisLeft(y[d]).ticks(4));
} else {
d3.select(this).call(d3.axisLeft(y[d]));
}
})
A fork of your plunkr: TickValues and ticks DEMO
Hope this helps. Read the docs for more options.
I have done basic things in D3 so I don't have deep knowledge about it.
I have created the zoomable circle packing chart with bar chart.
Here is the code:
var xr, yr, xaxis, yaxis, bar, bg;
var svg = d3.select("svg"),
margin = 20,
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
diameter = +svg.attr("width"),
g = svg.append("g").attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
var color = d3.scaleLinear()
.domain([-1, 5])
.range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"])
.interpolate(d3.interpolateHcl);
var pack = d3.pack()
.size([diameter - margin, diameter - margin])
.padding(2);
d3.json("occupation.json", function(error, root) {
if (error) throw error;
root = d3.hierarchy(root)
.sum(function(d) {
return d.size;
})
.sort(function(a, b) {
return b.value - a.value;
});
var focus = root,
nodes = pack(root).descendants(),
view;
var circle = g.selectAll("circle")
.data(nodes.filter(function(d) {
return d.height > 0
}))
.enter().append("circle")
.attr("class", function(d) {
return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root";
})
.style("fill", function(d) {
return d.children ? color(d.depth) : null;
})
.on("click", function(d) {
if (focus !== d) {
if (d.children) {
zoom(d), d3.event.stopPropagation();
} else {
var nextMonthVal = prompt("Please enter target value you want to set");
if (nextMonthVal) {
alert("You have set the target of Rs. " + nextMonthVal + " for next Month");
}
}
}
});
var leaf = g.selectAll(".bars")
.data(nodes.filter(function(d) {
return d.height == 1
}))
.enter()
.append("g")
.attr("x", 0)
.attr("y", 0)
.attr("height", function(d) {
return d.x + d.r
})
.attr("width", function(d) {
return d.y + d.r
})
.attr("class", "bars")
.each(function(d) {
drawBarData(this, this.__data__, d);
})
var text = g.selectAll(".label")
.data(nodes.filter(function(d) {
return d.height > 0
}))
.enter().append("text")
.attr("class", "label")
.style("fill-opacity", function(d) {
return d.parent === root ? 1 : 0;
})
.style("display", function(d) {
return d.parent === root ? "inline" : "none";
})
.text(function(d) {
return d.data.name + " " + d.data.size + " Rs.";
});
var node = g.selectAll("circle,.bars,.label");
svg
.style("background", color(-1))
.on("click", function() {
zoom(root);
});
zoomTo([root.x, root.y, root.r * 2 + margin]);
function zoom(d) {
var focus0 = focus;
focus = d;
var transition = d3.transition()
.duration(d3.event.altKey ? 7500 : 750)
.tween("zoom", function(d) {
var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + margin]);
return function(t) {
zoomTo(i(t), focus);
};
});
transition.selectAll(".label")
.filter(function(d) {
return d.parent === focus || this.style.display === "inline";
})
.style("fill-opacity", function(d) {
return d.parent === focus ? 1 : 0;
})
.on("start", function(d) {
if (d.parent === focus) this.style.display = "inline";
})
.on("end", function(d) {
if (d.parent !== focus) this.style.display = "none";
});
}
function zoomTo(v, focus) {
var k = diameter / v[2];
view = v;
node.attr("transform", function(d) {
return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")";
});
circle.attr("r", function(d) {
return d.r * k;
});
if (focus && focus.height == 1) {
var data2 = focus.children.map(function(d) {
return d.data
})
var data1 = []
rectwidth = focus.r,
rectheight = focus.r;
barsize = data2.length;
maxDataPoint = d3.max(data2, function(d) {
return d.size
});
var linearScale = d3.scaleLinear()
.domain([0, maxDataPoint])
.range([0, rectheight]);
for (var i = 0; i < data2.length; i++) {
data1.push({
name: data2[i].name,
size: linearScale(data2[i].size)
})
}
bg.attr("transform", function(d) {
console.log(d);
return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")";
})
bar.attr("x", function(d, i) {
console.log("D ::: >", d);
return i * (rectwidth / data1.length);
})
.attr("y", function(d) {
return rectheight - d.size;
})
.attr("width", focus.r / data1.length - 2)
.attr("height", function(d) {
return d.size;
});
}
}
function drawBarData(ele, data, d) {
if (!data && !data.parent)
return;
var data2 = data.children.map(function(e) {
return e.data
})
var data1 = []
rectwidth = d.r,
rectheight = d.r;
barsize = data2.length;
maxDataPoint = d3.max(data2, function(d) {
return d.size
});
var linearScale = d3.scaleLinear()
.domain([0, maxDataPoint])
.range([0, rectheight]);
for (var i = 0; i < data2.length; i++) {
data1.push({
name: data2[i].name,
size: linearScale(data2[i].size)
})
}
bg = d3.select(ele).attr("transform", "translate(" + 0 + "," + 0 + ")").append("g")
.attr("class", "chart-wrapper")
.attr("transform", function(d) {
console.log('BG ::: >>>', d);
return "translate(" + -d.r / 2 + "," + -d.r / 2 + ")";
});
bar = bg.selectAll(".bar")
.data(data1)
.enter()
.append('rect')
.attr("class", "bar")
.attr("x", function(d, i) {
return i * (rectwidth / data1.length);
})
.attr("y", function(d) {
return rectheight - d.size;
})
.attr("width", d.r / data1.length - 2)
.attr("height", function(d) {
return d.size;
});
}
});
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
a {
color: #00B7FF;
}
.container {
width: 400px;
}
.node {
cursor: pointer;
}
.node:hover {
border: #000;
border-width: 1.5px;
}
.node--leaf {
color: white;
}
.label {
font: 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
text-align: center;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff, 0 -1px 0 #fff;
}
.label,
.node--root {
pointer-events: none;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="960" height="960"></svg>
(not enough room for json - refer to plunkr)
I've placed the bar chart as per my understanding. But I also want barchart to be zoomed on the focus of each node. I am not getting any idea how to do it.
Please help me if anyone having idea or solution.
I'm very new to D3, and I don't understand all the logic of it.
I try to use examples, but it seems to be so many way to do things that it confuses me...
Anyway, I try to make this example dynamic.
Here is the code I have right now :
<!DOCTYPE html>
<meta charset="utf-8">
<style>
text {
font: 12px sans-serif;
text-anchor: middle;
}
.titre {
font-size: 18px;
}
.node--hover circle {
stroke: #000;
stroke-width: 1.2px;
}
#csv {
float: left;
}
</style>
<form>
<textarea name="csv" id="csv" cols="50" rows="30">id,taille,titre
D3,
D3.one,2000
D3.two,2000</textarea>
</form>
<svg width="400" height="400"><g transform="translate(1,1)"></g></svg>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var format = d3.format(",d");
var color = d3.scaleSequential(d3.interpolateMagma)
.domain([-4, 4]);
var stratify = d3.stratify()
.parentId(function(d) { return d.id.substring(0, d.id.lastIndexOf(".")); });
var pack = d3.pack()
.size([width - 2, height - 2])
.padding(3);
var csv = $("#csv").val();
data = d3.csvParse(csv);
var root = stratify(data)
.sum(function(d) { return d.taille; })
.sort(function(a, b) { return b.taille - a.taille; });
pack(root);
var node = svg.select("g")
.selectAll("g")
.data(root.descendants())
.enter().append("g")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.attr("class", function(d) { return "node" + (!d.children ? " node--leaf" : d.depth ? "" : " node--root"); })
.each(function(d) { d.node = this; });
node.append("circle")
.attr("id", function(d) { return "node-" + d.id; })
.attr("r", function(d) { return d.r; })
.style("fill", function(d) {
return color(d.depth);
});
var leaf = node.filter(function(d) { return !d.children; });
leaf.append("clipPath")
.attr("id", function(d) { return "clip-" + d.id; })
.append("use")
.attr("xlink:href", function(d) { return "#node-" + d.id + ""; });
leaf.append("text")
.attr("clip-path", function(d) { return "url(#clip-" + d.id + ")"; }).attr("class", function(d) { return d.data.titre=="1" ? "titre" : ""})
.selectAll("tspan")
.data(function(d) { return d.id.substring(d.id.lastIndexOf(".") + 1).split(/(?=[A-Z][^A-Z])/g); })
.enter().append("tspan")
.attr("x", 0)
.attr("y", function(d, i, nodes) { return 13 + (i - nodes.length / 2 - 0.5) * 20; })
.text(function(d) { return d; });
$(function() {
$("#csv").blur(function()
{
update();
});
});
function update()
{
var csv = $("#csv").val();
data = d3.csvParse(csv);
var root = stratify(data)
.sum(function(d) { return d.taille; })
.sort(function(a, b) { return b.taille - a.taille; });
pack(root);
//console.log(root.descendants());
node = node.data(root.descendants(), function(d) {return d});
console.log(node);
//node.exit().remove();
node.enter().append("g")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.attr("class", function(d) { return "node" + (!d.children ? " node--leaf" : d.depth ? "" : " node--root"); })
.each(function(d) { d.node = this; });
node.append("circle")
.attr("id", function(d) { return "node-" + d.id; })
.attr("r", function(d) { return d.r; })
.style("fill", function(d) {
return color(d.depth);
});
var leaf = node.filter(function(d) { return !d.children; });
leaf.append("clipPath")
.attr("id", function(d) { return "clip-" + d.id; })
.append("use")
.attr("xlink:href", function(d) { return "#node-" + d.id + ""; });
leaf.append("text")
.attr("clip-path", function(d) { return "url(#clip-" + d.id + ")"; }).attr("class", function(d) { return d.data.titre=="1" ? "titre" : ""})
.selectAll("tspan")
.data(function(d) { return d.id.substring(d.id.lastIndexOf(".") + 1).split(/(?=[A-Z][^A-Z])/g); })
.enter().append("tspan")
.attr("x", 0)
.attr("y", function(d, i, nodes) { return 13 + (i - nodes.length / 2 - 0.5) * 20; })
.text(function(d) { return d; });
}
</script>
You can test it on bl.ocks :
https://bl.ocks.org/matthieubrunet/79ef9968e2eaaba7f0718a373d240025
The update is supposed to happen on blur.
I think my problem is around the enter function (in the update function), which returns all elements instead of only new ones.
As ou can see, I commented out the exit, because it removes all the childs circles.
Thanks a lot
One of the most confusing things with d3 is how to handle the enter, update and exit pattern. I won't provide you a tutorial on it as there are a number of great resources already - out there. But I've taken the time to re-factor your update function to handle the situations properly:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
text {
font: 12px sans-serif;
text-anchor: middle;
}
.titre {
font-size: 18px;
}
.node--hover circle {
stroke: #000;
stroke-width: 1.2px;
}
#csv {
float: left;
}
</style>
<form>
<textarea name="csv" id="csv" cols="50" rows="30">id,taille,titre
D3,
D3.one,2000
D3.two,2000</textarea>
</form>
<svg width="400" height="400">
<g transform="translate(1,1)"></g>
</svg>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var format = d3.format(",d");
var color = d3.scaleSequential(d3.interpolateMagma)
.domain([-4, 4]);
var stratify = d3.stratify()
.parentId(function(d) {
return d.id.substring(0, d.id.lastIndexOf("."));
});
var pack = d3.pack()
.size([width - 2, height - 2])
.padding(3);
update();
$(function() {
$("#csv").blur(function() {
update();
});
});
function update() {
var csv = $("#csv").val();
data = d3.csvParse(csv);
var root = stratify(data)
.sum(function(d) {
return d.taille;
})
.sort(function(a, b) {
return b.taille - a.taille;
});
pack(root);
// data-binding
var node = svg.selectAll(".node").data(root.descendants(), function(d){
return d;
});
// exiting nodes
node.exit().remove();
// entering nodes
var nodeEnter = node.enter().append("g")
.attr("class", function(d) {
return "node" + (!d.children ? " node--leaf" : d.depth ? "" : " node--root");
});
// add circles
nodeEnter.append("circle")
.attr("id", function(d) {
return "node-" + d.id;
});
// update + enter
node = nodeEnter.merge(node);
// position everyone
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
// update circles
node.select("circle")
.attr("r", function(d) {
return d.r;
})
.style("fill", function(d) {
return color(d.depth);
});
// handle enter of leafs
var leafEnter = nodeEnter.filter(function(d) {
return !d.children;
});
leafEnter.append("clipPath")
.attr("id", function(d) {
return "clip-" + d.id;
})
.append("use")
.attr("xlink:href", function(d) {
return "#node-" + d.id + "";
});
leafEnter.append("text")
.attr("clip-path", function(d) {
return "url(#clip-" + d.id + ")";
}).attr("class", function(d) {
return d.data.titre == "1" ? "titre" : "";
});
node.select("text")
.selectAll("tspan")
.data(function(d) {
return d.id.substring(d.id.lastIndexOf(".") + 1).split(/(?=[A-Z][^A-Z])/g);
})
.enter().append("tspan")
.attr("x", 0)
.attr("y", function(d, i, nodes) {
return 13 + (i - nodes.length / 2 - 0.5) * 20;
})
.text(function(d) {
return d;
});
}
</script>