Bubble tree in d3? - javascript

Is there an equivalent implementation of a Bubble Tree in D3? In the link I provided, the Bubble Tree was implemented in RaphaelJS and jQuery.

The straight answer to your question is no.
Using the resources at https://github.com/okfn/bubbletree/tree/master/build, the information you already know, and the information provided on http://d3js.org/ and through D3's documentation on GitHub, you should be able to conjure up your own bubble tree for D3!
This is a piece of JavaScript I used a long time ago to visualize binary tree data:
var updateVisual;
updateVisual = function() {
var drawTree, out;
drawTree = function(out, node) {
var col, gray, i, line, lineElt, lines, sub, _results, _results1;
if (node.lines) {
out.appendChild(document.createElement("div")).innerHTML = "<b>leaf</b>: " + node.lines.length + " lines, " + Math.round(node.height) + " px";
lines = out.appendChild(document.createElement("div"));
lines.style.lineHeight = "6px";
lines.style.marginLeft = "10px";
i = 0;
_results = [];
while (i < node.lines.length) {
line = node.lines[i];
lineElt = lines.appendChild(document.createElement("div"));
lineElt.className = "lineblock";
gray = Math.min(line.text.length * 3, 230);
col = gray.toString(16);
if (col.length === 1) col = "0" + col;
lineElt.style.background = "#" + col + col + col;
console.log(line.height, line);
lineElt.style.width = Math.max(Math.round(line.height / 3), 1) + "px";
_results.push(i++);
}
return _results;
} else {
out.appendChild(document.createElement("div")).innerHTML = "<b>node</b>: " + node.size + " lines, " + Math.round(node.height) + " px";
sub = out.appendChild(document.createElement("div"));
sub.style.paddingLeft = "20px";
i = 0;
_results1 = [];
while (i < node.children.length) {
drawTree(sub, node.children[i]);
_results1.push(++i);
}
return _results1;
}
};
out = document.getElementById("btree-view");
out.innerHTML = "";
return drawTree(out, editor.getDoc());
};
Just insert some circular elements and manipulate it a bit to style in a circular manor and you should have a good program set!

Here you go. I didn't add the text or decorations, but it's the meat and potatoes:
function bubbleChart(config) {
var aspectRatio = 1,
margin = { top: 0, right: 0, bottom: 0, left: 0 },
radiusScale = d3.scale.sqrt(),
scan = function(f, data, a) {
a = a === undefined ? 0 : a;
var results = [a];
data.forEach(function(d, i) {
a = f(a, d);
results.push(a);
});
return results;
},
colorScale = d3.scale.category20(),
result = function(selection) {
selection.each(function(data) {
var outerWidth = $(this).width(),
outerHeight = outerWidth / aspectRatio,
width = outerWidth - margin.left - margin.right,
height = outerHeight - margin.top - margin.bottom,
smallestDimension = Math.min(width, height),
sum = data[1].reduce(function(a, d) {
return a + d[1];
}, 0),
radiusFractions = data[1].map(function(d) {
return Math.sqrt(d[1] / sum);
}),
radiusNormalSum = radiusFractions.reduce(function(a, d) {
return a + d;
}, 0),
scanned = scan(function(a, d) {
return a + d;
}, radiusFractions.map(function(d) {
return d / radiusNormalSum;
}), 0);
radiusScale.domain([0, sum]).range([0, smallestDimension / 6]);
var svg = d3.select(this).selectAll('svg').data([data]),
svgEnter = svg.enter().append('svg');
svg.attr('width', outerWidth).attr('height', outerHeight);
var gEnter = svgEnter.append('g'),
g = svg.select('g').attr('transform', 'translate(' + margin.left + ' ' + margin.top + ')'),
circleRing = g.selectAll('circle.ring').data(data[1]),
circleRingEnter = circleRing.enter().append('circle').attr('class', 'ring');
circleRing.attr('cx', function(d, i) {
return smallestDimension / 3 * Math.cos(2 * Math.PI * (scanned[i] + scanned[i + 1]) / 2) + width / 2;
}).attr('cy', function(d, i) {
return smallestDimension / 3 * Math.sin(2 * Math.PI * (scanned[i] + scanned[i + 1]) / 2) + height / 2;
}).attr('r', function(d) {
return radiusScale(d[1]);
}).style('fill', function(d) {
return colorScale(d[0]);
});
var circleMain = g.selectAll('circle#main').data([data[0]]),
circleMainEnter = circleMain.enter().append('circle').attr('id', 'main');
circleMain.attr('cx', width / 2).attr('cy', height / 2).attr('r', radiusScale(sum)).style('fill', function(d) {
return colorScale(d);
});
});
};
result.aspectRatio = function(value) {
if(value === undefined) return aspectRatio;
aspectRatio = value;
return result;
};
result.margin = function(value) {
if(value === undefined) return margin;
margin = value;
return result;
};
return result;
}
var myBubbleChart = bubbleChart().margin({
top: 1,
right: 1,
bottom : 1,
left: 1
});
var data = ['Random Names, Random Amounts', [['Immanuel', .4], ['Pascal', 42.9], ['Marisa', 3.3], ['Hadumod', 4.5], ['Folker', 3.2], ['Theo', 4.7], ['Barnabas', 1.0], ['Lysann', 11.1], ['Julia', .7], ['Burgis', 28.2]]];
d3.select('#here').datum(data).call(myBubbleChart);
<div class="container">
<div class="row">
<div class="col-xs-12">
<div id="here"></div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

You can use the pack layout , basically you can bind any data you want to the shapes in the graph and custom parameters for them to position well respect to each other. Another alternative would be the force layout.

Related

How can we reduce the circle radius of bubbles in datamaps on zooming

I am trying to display a world-map with bubbles. The map and the bubble displayed perfectly. But on zooming the map, bubbles are also zooming. Any idea on how to minimize/reduce the bubble radius on zooming. Is this can be done using CSS or do we need to apply js?
This is how Am handling the zoom functionality.
function Zoom(args) {
$.extend(this, {
$buttons: $(".zoom-button"),
scale: { max: 50, currentShift: 0 },
$container: args.$container,
datamap: args.datamap
});
this.init();
}
Zoom.prototype.init = function() {
var paths = this.datamap.svg.selectAll("path"),
subunits = this.datamap.svg.selectAll(".datamaps-subunit");
// preserve stroke thickness
paths.style("vector-effect", "non-scaling-stroke");
// disable click on drag end
subunits.call(
d3.behavior.drag().on("dragend", function() {
d3.event.sourceEvent.stopPropagation();
})
);
this.scale.set = this._getScalesArray();
this.d3Zoom = d3.behavior.zoom().scaleExtent([ 1, this.scale.max ]);
this.listen();
};
Zoom.prototype.listen = function() {
this.$buttons.off("click").on("click", this._handleClick.bind(this));
this.datamap.svg
.call(this.d3Zoom.on("zoom", this._handleScroll.bind(this)))
};
Zoom.prototype._handleScroll = function() {
var translate = d3.event.translate,
scale = d3.event.scale,
limited = this._bound(translate, scale);
this.scrolled = true;
this._update(limited.translate, limited.scale);
};
Zoom.prototype._handleClick = function(event) {
var direction = $(event.target).data("zoom");
this._shift(direction);
};
Zoom.prototype._shift = function(direction) {
var center = [ this.$container.width() / 2, this.$container.height() / 2 ],
translate = this.d3Zoom.translate(), translate0 = [], l = [],
view = {
x: translate[0],
y: translate[1],
k: this.d3Zoom.scale()
}, bounded;
translate0 = [
(center[0] - view.x) / view.k,
(center[1] - view.y) / view.k
];
view.k = this._getNextScale(direction);
l = [ translate0[0] * view.k + view.x, translate0[1] * view.k + view.y ];
view.x += center[0] - l[0];
view.y += center[1] - l[1];
bounded = this._bound([ view.x, view.y ], view.k);
this._animate(bounded.translate, bounded.scale);
};
Zoom.prototype._bound = function(translate, scale) {
var width = this.$container.width(),
height = this.$container.height();
translate[0] = Math.min(
(width / height) * (scale - 1),
Math.max( width * (1 - scale), translate[0] )
);
translate[1] = Math.min(0, Math.max(height * (1 - scale), translate[1]));
return { translate: translate, scale: scale };
};
Zoom.prototype._update = function(translate, scale) {
this.d3Zoom
.translate(translate)
.scale(scale);
this.datamap.svg.selectAll("g")
.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
};
Zoom.prototype._animate = function(translate, scale) {
var _this = this,
d3Zoom = this.d3Zoom;
d3.transition().duration(350).tween("zoom", function() {
var iTranslate = d3.interpolate(d3Zoom.translate(), translate),
iScale = d3.interpolate(d3Zoom.scale(), scale);
return function(t) {
_this._update(iTranslate(t), iScale(t));
};
});
};
Any help will be appreciated.

Draw arrow in svg path dynamically is not working

var Belay = (function () {
var settings = {
strokeColor: '#fff',
strokeWidth: 2,
opacity: 1,
fill: 'none',
animate: true,
animationDirection: 'right',
animationDuration: .75
};
var me = {};
me.init = function (initObj) {
if (initObj) {
$.each(initObj, function (index, value) {
//TODO validation on settings
settings[index] = value;
});
}
}
me.set = function (prop, val) {
//TODO validate
settings[prop] = val;
}
me.on = function (el1, el2) {
var $el1 = $(el1);
var $el2 = $(el2);
if ($el1.length && $el2.length) {
var svgheight, p, svgleft, svgtop, svgwidth
var el1pos = $(el1).offset();
var el2pos = $(el2).offset();
var el1H = $(el1).outerHeight();
var el1W = $(el1).outerWidth();
var el2H = $(el2).outerHeight();
var el2W = $(el2).outerWidth();
var node1X = Math.round(el1pos.left + el1W);
var node2X = Math.round(el2pos.left);
var overlapping = node1X >= node2X;
var svgwidth = Math.abs(node2X - node1X);
var svgleft = Math.min(node1X, node2X);
var node1Y = Math.round(el1pos.top + el1H / 2);
var node2Y = Math.round(el2pos.top + el1H / 2);
var svgheight = Math.abs(node1Y - node2Y);
var svgtop = Math.min(node1Y, node2Y);
var pt1x = node1X - svgleft;
var pt1y = node1Y - svgtop + settings.strokeWidth;
var pt2x = node2X - svgleft;
var pt2y = node2Y - svgtop + settings.strokeWidth;
// cpt is the length of the control point vector
// variew with distance netween boxes
var cpt = Math.round(svgwidth * Math.min(svgheight / 300, 1));
if (overlapping) {
// Need to widen the svg because otherwise the bezier control
// points (and hence the curve) will extend outside it.
svgleft -= cpt;
svgwidth += 2 * cpt;
pt1x += cpt;
pt2x += cpt;
}
// Build the path decription
p = "M" + pt1x + "," + pt1y +
" L" + (pt1x + pt2x) / 8 + "," + pt1y +
" L" + (pt1x + pt2x) / 8 + "," + pt2y //+
" L" + (pt2x) / 1.03 + "," + pt2y +
" L" + (pt2x) / 1.03 + "," + (pt2y - 5) +
" L" + pt2x + "," + pt2y +
" L" + (pt2x) / 1.03 + "," + (pt2y + 5) +
" L" + (pt2x) / 1.03 + "," + pt2y;
//ugly one-liner
$ropebag = $('#ropebag').length ? $('#ropebag') : $('body').append($("<div id='ropebag' />")).find('#ropebag');
var svgnode = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
var defs = document.createElementNS('http://www.w3.org/2000/svg', "defs");
var marker = document.createElementNS('http://www.w3.org/2000/svg', "marker");
var path = document.createElementNS('http://www.w3.org/2000/svg', "path");
marker.setAttributeNS(null,"id","arrow");
marker.setAttributeNS(null,"markerWidth","13");
marker.setAttributeNS(null,"markerHeight","13");
marker.setAttributeNS(null,"refX","2");
marker.setAttributeNS(null,"refY","7");
marker.setAttributeNS(null,"markerUnits","userSpaceOnUse");
path.setAttributeNS(null,"d","M2,2 L2,13 L8,7 L2,2");
marker.appendChild(path);
defs.appendChild(marker);
var newpath = document.createElementNS('http://www.w3.org/2000/svg', "path");
newpath.setAttributeNS(null, "d", p);
newpath.setAttributeNS(null, "stroke", settings.strokeColor);
newpath.setAttributeNS(null, "stroke-width", settings.strokeWidth);
newpath.setAttributeNS(null, "opacity", settings.opacity);
newpath.setAttributeNS(null, "fill", settings.fill);
newpath.setAttributeNS(null, "marker-end", "url(#arrow)");
svgnode.appendChild(newpath);
svgnode.appendChild(defs);
$(svgnode).css({
left: svgleft,
top: svgtop - settings.strokeWidth,
position: 'absolute',
width: svgwidth,
height: svgheight + settings.strokeWidth * 2,
minHeight: '20px',
'pointer-events': 'none'
});
$ropebag.append(svgnode);
if (settings.animate) {
// THANKS to http://jakearchibald.com/2013/animated-line-drawing-svg/
var pl = newpath.getTotalLength();
// Set up the starting positions
newpath.style.strokeDasharray = pl + ' ' + pl;
if (settings.animationDirection == 'right') {
newpath.style.strokeDashoffset = pl;
} else {
newpath.style.strokeDashoffset = -pl;
}
// Trigger a layout so styles are calculated & the browser
// picks up the starting position before animating
// WON'T WORK IN IE. If you want that, use requestAnimationFrame to update instead of CSS animation
newpath.getBoundingClientRect();
newpath.style.transition = newpath.style.WebkitTransition = 'stroke-dashoffset ' + settings.animationDuration + 's ease-in-out';
// Go!
newpath.style.strokeDashoffset = '0';
}
}
}
me.off = function () {
$("#ropebag").empty();
}
return me;
}());
/*********************** Custom JavaScript **********************************/
$(document).ready(function () {
$(".draggable").draggable({
drag: function (event, ui) {
Belay.off();
drawConnectors();
}
});
function drawConnectors() {
$(".parent").each(function () {
var theID = this.id;
$("." + theID).each(function (i, e) {
var rand = Math.random() * .7 + .3;
Belay.set('animationDuration', rand)
Belay.on($("#" + theID), e)
});
})
}
$(window).resize(function () {
Belay.off();
drawConnectors();
});
Belay.init({
strokeWidth: 1
});
Belay.set('strokeColor', '#999');
drawConnectors();
});
.row{
margin-top:2in;
}
.box{
border:1px solid #ccc;
padding:10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js"></script>
<link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet"/>
<body>
<div class="row">
<div class="col-md-4">
<div class="pull-left ">
<div class="parent draggable box" id="parent1">Left drag</div>
</div>
<div class="pull-right">
<div class="child parent1 draggable box">Right drag</div>
</div>
</div>
</div>
</body>
Hi as per this example how-to-change-the-lines-between-two-points-from-curved-to-straight-lines-with-on how to draw arrow at the end of the path dynamically
can someone tell me what is wrong in adding marker end in my code
var Belay = (function ()
{
var settings = {
strokeColor: 'red',
strokeWidth: 2,
opacity: 1,
fill: 'none',
animate: true,
animationDirection: 'right',
animationDuration: .75
};
var me = {};
me.init = function (initObj) {
if (initObj) {
$.each(initObj, function (index, value) {
//TODO validation on settings
settings[index] = value;
});
}
}
me.set = function (prop, val) {
//TODO validate
settings[prop] = val;
}
me.on = function (el1, el2) {
var $el1 = $(el1);
var $el2 = $(el2);
if ($el1.length && $el2.length) {
var svgheight, p, svgleft, svgtop, svgwidth
var el1pos = $(el1).offset();
var el2pos = $(el2).offset();
var el1H = $(el1).outerHeight();
var el1W = $(el1).outerWidth();
var el2H = $(el2).outerHeight();
var el2W = $(el2).outerWidth();
var node1X = Math.round(el1pos.left + el1W);
var node2X = Math.round(el2pos.left);
var overlapping = node1X >= node2X;
var svgwidth = Math.abs(node2X - node1X);
var svgleft = Math.min(node1X, node2X);
var node1Y = Math.round(el1pos.top + el1H / 2);
var node2Y = Math.round(el2pos.top + el1H / 2);
var svgheight = Math.abs(node1Y - node2Y);
var svgtop = Math.min(node1Y, node2Y);
var pt1x = node1X - svgleft;
var pt1y = node1Y - svgtop + settings.strokeWidth;
var pt2x = node2X - svgleft;
var pt2y = node2Y - svgtop + settings.strokeWidth;
// cpt is the length of the control point vector
// variew with distance netween boxes
var cpt = Math.round(svgwidth * Math.min(svgheight / 300, 1));
if (overlapping) {
// Need to widen the svg because otherwise the bezier control
// points (and hence the curve) will extend outside it.
svgleft -= cpt;
svgwidth += 2 * cpt;
pt1x += cpt;
pt2x += cpt;
}
p = "M" + pt1x + "," + pt1y +
" L" + (pt1x + pt2x) / 8 + "," + pt1y +
" L" + (pt1x + pt2x) / 8 + "," + pt2y +
" L" + (pt2x) / 1.03 + "," + pt2y +
" L" + (pt2x) / 1.03 + "," + (pt2y - 5) +
" L" + pt2x + "," + pt2y +
" L" + (pt2x) / 1.03 + "," + (pt2y + 5) +
" L" + (pt2x) / 1.03 + "," + pt2y;
//ugly one-liner
$ropebag = $('#ropebag').length ? $('#ropebag') : $('body').append($("<div id='ropebag' />")).find('#ropebag');
var svgnode = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
//try
var defs = document.createElementNS('http://www.w3.org/2000/svg', "defs");
var marker = document.createElementNS('http://www.w3.org/2000/svg', "marker");
var path = document.createElementNS('http://www.w3.org/2000/svg', "path");
marker.setAttributeNS(null,"id","arrow");
marker.setAttributeNS(null,"markerWidth",10);
marker.setAttributeNS(null,"markerHeight",10);
marker.setAttributeNS(null,"refX",0);
marker.setAttributeNS(null,"refY",5);
marker.setAttributeNS(null,"viewbox","0 0 10 10");
marker.setAttributeNS(null,"orient","auto");
marker.setAttributeNS(null,"markerUnits","strokeWidth");
path.setAttributeNS(null,"d","M 0 0 L 10 5 L 0 10");
////try
marker.appendChild(path);
defs.appendChild(marker);
var newpath = document.createElementNS('http://www.w3.org/2000/svg', "path");
newpath.setAttributeNS(null, "d", p);
newpath.setAttributeNS(null, "stroke", settings.strokeColor);
newpath.setAttributeNS(null, "stroke-width", settings.strokeWidth);
newpath.setAttributeNS(null, "opacity", settings.opacity);
newpath.setAttributeNS(null, "fill", settings.fill);
newpath.setAttributeNS(null, "marker-end", "url(#arrow)");
newpath.appendChild(defs);
svgnode.appendChild(newpath);
//for some reason, adding a min-height to the svg div makes the lines appear more correctly.
$(svgnode).css({
left: svgleft,
top: svgtop - settings.strokeWidth,
position: 'absolute',
width: svgwidth,
height: svgheight + settings.strokeWidth * 2,
minHeight: '20px',
'pointer-events': 'none'
});
$ropebag.append(svgnode);
if (settings.animate) {
// THANKS to http://jakearchibald.com/2013/animated-line-drawing-svg/
var pl = newpath.getTotalLength();
// Set up the starting positions
newpath.style.strokeDasharray = pl + ' ' + pl;
if (settings.animationDirection == 'right') {
newpath.style.strokeDashoffset = pl;
} else {
newpath.style.strokeDashoffset = -pl;
}
// Trigger a layout so styles are calculated & the browser
// picks up the starting position before animating
// WON'T WORK IN IE. If you want that, use requestAnimationFrame to update instead of CSS animation
newpath.getBoundingClientRect();
newpath.style.transition = newpath.style.WebkitTransition = 'stroke-dashoffset ' + settings.animationDuration + 's ease-in-out';
// Go!
newpath.style.strokeDashoffset = '0';
}
}
}
me.off = function ()
{
$("#ropebag").empty();
}
return me;
} ());
function drawConnectors()
{
}
You don't seem to be appending newpath to anything.
Also you are appending defs to newpath. I'm not sure that works (I've never tried it). You should probably avoid doing that. Append the defs to the SVG instead.

D3 - Transition Arcs in Sunburst Chart

I have a sunburst chart made in D3. Each 'petal' represents a subset of data. When a user clicks on one of the 'petals', I would like it to transition, fanning out to only show that subset (see image):
I'm having trouble getting the code to properly transition.
On click, all 'petals' (besides the selected one) should disappear and the remain paths should animate along the circle (using attrTween, arcTween, and interpolate?). The primary value that would be changing is the angleSize (var angleSize = (2 * Math.PI) / theData.length;).
I've tried using this, this, this, and this as reference without much success. What's the best way to handle the animation?
Thanks for your time!
--> See Plunker Here. <--
Code is below:
var colors = {
'Rank1' : '#3FA548',
'Rank2' : '#00B09E',
'Rank3' : '#8971B3',
'Rank4' : '#DFC423',
'Rank5' : '#E74341'
};
var $container = $('.chart'),
m = 40,
width = $container.width() - m,
height = $container.height() - m,
r = Math.min(width, height) / 2;
var study = null;
var arc = d3.svg.arc();
d3.csv('text.csv', ready);
function ready(err, data) {
if (err) console.warn('Error', err);
var svg = d3.select('.chart')
.append('svg')
.attr({
'width' : (r + m) * 2,
'height' : (r + m) * 2,
'class' : 'container'
})
.append('g')
.attr('transform', 'translate(' + (width / 4) + ', ' + (height / 2) + ' )');
var slice = svg.selectAll('.slice');
function updateChart(study) {
if (study) {
var theData = data.filter(function(d) {
return d.study_name === study;
});
} else {
var theData = data;
}
slice = slice.data(theData);
slice.enter()
.append('g')
.attr('class', 'slice');
var angleSize = (2 * Math.PI) / theData.length;
var startRadArr = [],
endRadArr = [];
for ( var i = 0; i < data.length; i++ ) {
var startRadius = (width / 20),
endRadius = startRadius;
for ( var x = 0; x < 4; x++ ) {
startRadArr.push(startRadius);
if ( x == 0 ) {
endRadius += Number(data[i].group1_score) * (width / 500);
} else if ( x == 1 ) {
endRadius += Number(data[i].group2_score) * (width / 500);
} else if ( x == 2 ) {
endRadius += Number(data[i].group3_score) * (width / 500);
} else {
endRadius += Number(data[i].group4_score) * (width / 500);
}
endRadArr.push(endRadius);
startRadius = endRadius + 0.3;
}
}
var startRadGroup = [],
endRadGroup = [];
for (i = 0; i < startRadArr.length; i += 4) {
startRadGroup.push(startRadArr.slice(i, i + 4));
}
for (i = 0; i < endRadArr.length; i += 4) {
endRadGroup.push(endRadArr.slice(i, i + 4));
}
slice.selectAll('path')
.remove();
for ( var x = 0; x < 4; x++ ) {
slice.append('path')
.attr({
'class' : function(d, i) {
if ( x == 0 ) {
return d.group1_class;
} else if ( x == 1 ) {
return d.group2_class;
} else if ( x == 2 ) {
return d.group3_class;
} else {
return d.group4_class;
}
},
'company' : function(d, i) {
return d.brand_name;
},
'cat' : function(d, i) {
if ( x == 0 ) {
return 'Group1';
} else if ( x == 1 ) {
return 'Group2';
} else if ( x == 2 ) {
return 'Group3';
} else {
return 'Group4';
}
},
'study' : function(d, i) {
return d.study_name;
},
'companyid' : function(d, i) {
return d.brand_id;
},
'startradius' : function(d, i) {
return startRadGroup[i][x];
},
'endradius' : function(d, i) {
return endRadGroup[i][x];
},
'startangle' : function(d, i) {
return angleSize * i;
},
'endangle' : function(d, i) {
return angleSize * (i + 1);
}
})
.on('click', selectStudy);
}
slice.exit()
.remove();
slice.selectAll('path')
.attr({
'd' : function(d) {
return arc({
innerRadius : +d3.select(this)[0][0].attributes.startradius.nodeValue,
outerRadius : +d3.select(this)[0][0].attributes.endradius.nodeValue,
startAngle : +d3.select(this)[0][0].attributes.startangle.nodeValue,
endAngle : +d3.select(this)[0][0].attributes.endangle.nodeValue
})
}
});
}
function selectStudy(d) {
study = $(this).attr('study');
updateChart(study);
}
updateChart();
}
EDIT
Updated the code (based on this) to include a properly working enter, update, and exit pattern. Still unsure about the transition however. Most of the examples I've linked to use something similar to d3.interpolate(this._current, a);, tweening between differing data.
In this chart, this._current and a are the same, angleSize (var angleSize = (2 * Math.PI) / theData.length;), startAngle, and endAngle are the only thing that changes.
Your problem is that you are not really binding data to the elements, and therefore the transition is not possible. I mangled your code a little bit so the data contains all the nested information about the starting and ending angles, so that it can be bound to the paths inside each slice.
Take a look at this Plunker: https://plnkr.co/edit/a7cxRplzy66Pc1arM2a9?p=preview
Here's the listing of the modified version:
var colors = {
Rank1: '#3FA548',
Rank2: '#00B09E',
Rank3: '#8971B3',
Rank4: '#DFC423',
Rank5: '#E74341'
};
// Configuration
var $container = $('.chart'),
m = 40,
width = $container.width() - m,
height = $container.height() - m,
r = Math.min(width, height) / 2;
var study = null;
var arc = d3.svg.arc();
// Load data
d3.csv('text.csv', ready);
// Data loaded callback
function ready(err, data) {
if (err) console.warn('Error', err);
var svg = d3.select('.chart')
.append('svg')
.attr({
'width': (r + m) * 2,
'height': (r + m) * 2,
'class': 'container'
})
.append('g')
.attr('transform', 'translate(' + (width / 4) + ', ' + (height / 2) + ' )');
var slices = svg.selectAll('.slice');
function updateChart(study) {
var theData = data;
if (study) {
theData = data.filter(function (d) {
return d.study_name === study;
});
}
var angleSize = (2 * Math.PI) / theData.length;
theData.forEach(function (item, i) {
var startRadius = (width / 20),
endRadius = startRadius,
groupName;
item.paths = [];
for (var g = 0; g < 4; g++) {
item.paths[g] = {};
item.paths[g].startRadius = startRadius;
groupName = 'group' + (g + 1) + '_score';
endRadius += Number(item[groupName]) * (width / 500);
item.paths[g].endRadius = endRadius;
startRadius = endRadius + 0.3;
}
});
// Set the data
slices = slices.data(theData);
// Enter
slices.enter()
.append('g')
.attr('class', 'slice');
// Exit
slices.exit()
.remove();
// Update
slices
.transition()
.duration(750)
.each(function (dSlice, iSlice) {
var slice = d3.select(this);
var paths = slice.selectAll('path');
// Set data
paths = paths.data(dSlice.paths);
// Exit
paths.exit()
.remove();
// Enter
paths.enter()
.append('path')
.attr('class', 'path');
// Update
paths
.transition()
.attr({
'class': function (d, i) {
return dSlice['group' + (i + 1) + '_class'];
},
'company': dSlice.brand_name,
'cat': function (d, i) {
return 'Group' + (i + 1);
},
'study': function (d, i) {
return dSlice.study_name;
},
'companyid': function (d, i) {
return dSlice.brand_id;
},
'startradius': function (d, i) {
return d.startRadius;
},
'endradius': function (d, i) {
return d.endRadius;
},
'startangle': function (d, i) {
return angleSize * iSlice;
},
'endangle': function (d, i) {
return angleSize * (iSlice + 1);
},
'd': function (d) {
return arc({
innerRadius: +d.startRadius,
outerRadius: +d.endRadius,
startAngle: +angleSize * iSlice,
endAngle: +angleSize * (iSlice + 1)
})
}
})
.duration(750);
paths.on('click', selectStudy);
});
function selectStudy(d, i) {
study = $(this).attr('study');
updateChart(study);
}
}
updateChart();
}
As you can see, the key is correctly preparing the data (let's say the format in your example .tsv file is not the best choice, but sometimes we can't choose our data sources...)
Then afterwards, by putting the code for the paths generation inside the .each call on the slices, the data can be accessed from the function (d, i) { ... } callbacks and every element happens to receive the corresponding data.
Another trick is using the slices data (accessed inside the .each function via the dSlice and iSlice vars) on the paths' callbacks. This way the paths can consume this data for their own purposes. In this case, the company and study_name properties.
Now in order to tweak the transition and make it more accurate, the starting attributes can change. You can try by setting up some attributes for the paths in the .enter() phase.

Make D3 Graph Responsive to Windows Size

i am building a d3 Graph based on a json-File with an svg and groups:
Presentation.prototype.drawGraph = function() {
var _this;
this.margin = {
top: 0,
right: 0,
bottom: 0,
left: 0
};
this.width = 1000 - this.margin.left - this.margin.right;
this.height = 720 - this.margin.top - this.margin.bottom;
this.singleWidth = 50;
this.singleHeight = 32;
_this = this;
return d3.json("result.json", (function(_this) {
return function(err, json) {
if (err) {
console.error(err);
return;
}
// do something with the data
_this.x = d3.scale.ordinal().rangeBands([0, _this.width], 0, 0);
_this.y = d3.scale.linear().range([_this.height, 0]);
_this.svg = d3.select("#graph").append("svg").attr("width", _this.width + _this.margin.right + _this.margin.left).attr("height", _this.height + _this.margin.top + _this.margin.bottom).append("g").attr("transform", "translate(" + 0 + "," + 50 + ")");
_this.svg.append("rect").attr("x", -200).attr("y", -200).attr("width", 1200).attr("height", 1200).attr("fill", "#FFFFFF").on("click", function(data, index) {
_this.removeRenderedContent();
return _this.clickOnBackgroundRect();
});
_this.groups = _this.svg.selectAll("g").data(json).enter().append("g").attr("class", function(data, index) {return "group-case " + data.type;});
_this.groups.on("click", function(data, index) {
_this.removeRenderedContent();
_this.clickOnGroupCaseRect(data, index, this);
return false;
});
_this.groups.attr("transform", function(data, index) {
return "translate(" + data.x + "," + data.y + ")";
});
_this.groups.append("rect").attr("width", _this.widthFilter).attr("height", _this.heightFilter).attr("x", 0).attr("y", 0).attr("rx", 10).attr("ry", 10);
_this.groups.append("g").append("text").attr("y", 7).attr("x", 7).text(function(data) {return data.title;}).call(function(data, xpadding) {return _this.wrapGroup(data, xpadding);}, 7);
_this = _this;
return _this.groups.append("g").each(function(data, index) {
return _this.drawChildren(this, data, index);
});
};
})(this));
};
What i want to do is to make this graph responsive to the current browser-window size. Does anyone know the easiest way to do this?
Thanks in advance.

Javascript Nested Function Call

I am trying to develop a javascript library that makes it easier for me to generate DOM Elements and edit their attributes. The problem I am having is that there is so many attributes for some elements that it is making for messy code. For instance I have to programmatically set the color of border, background, shadow, etc. using a method call before generation.
See setBorder Nested in function Div in jLibrary.php
function Div() {
Div.POSITION = {
STATIC : 'static',
ABSOLUTE : 'absolute',
RELATIVE : 'relative',
FIXED : 'fixed'
}
Div.BORDER = {
SOLID : 'solid',
DOTTED : 'dotted'
}
Div.ALIGN = {
LEFT : 0,
CENTER : 1,
RIGHT : 2,
TOP : 0,
MIDDLE : 1,
BOTTOM : 2
}
var ELEMENT;
var CSS;
var horizontalAlign;
var verticalAlign;
var colorQueue;
(function() {
this.div = document.createElement('div');
ELEMENT = this.div;
CSS = this.div.style;
colorQueue = 'rgb(' + new Array(0, 0, 0) + ')';
document.body.appendChild(this.div);
}());
this.setPosition = function(postype) {
if (!horizontalAlign && !verticalAlign) {
CSS.position = postype;
}
}
this.setBounds = function(x, y, width, height) {
CSS.left = x + 'px';
CSS.top = y + 'px';
CSS.width = width + 'px';
CSS.height = height + 'px';
}
this.setColorQueue = function(r, g, b) {
colorQueue = 'rgb(' + new Array(r, g, b) + ')';
alert(colorQueue);
}
this.setBackgroundColorToQueue = function(){
CSS.backgroundColor = colorQueue;
}
this.createShadow = function(x, y, width, height){
CSS.boxShadow = y + 'px ' + x + 'px ' + width + 'px ' + height + 'px ' + colorQueue;
}
this.createBorder = function(width,style){
CSS.border = width + 'px ' + style + ' ' + colorQueue;
/* Theoretical Method.
this.setColor = function(r,g,b){ //This will not work the way I want it...
CSS.border = 'rgb(' + new Array(r, g, b) + ')';
}
*/
}
this.rotateDiv = function(degrees){
CSS.transform = 'rotate(' + degrees + 'deg)';
}
this.horizontalAlign = function(horiz) {
var freeSpaceX = ((window.innerWidth - ELEMENT.offsetWidth) / 2);
var defPadding = '8px';
var defPaddingCenter;
var defPaddingRight;
var defPaddingLeft;
horizontalAlign = true;
if (CSS.position == 'relative' || CSS.position == 'static' || CSS.position == 'absolute') {
CSS.position = 'absolute';
defPaddingCenter = 4;
defPaddingRight = 4;
defPaddingLeft = 8;
} else if (CSS.position == 'fixed') {
defPaddingCenter = 4;
defPaddingRight = 4;
defPaddingLeft = 8;
}
if (horiz == 0) {
if (!verticalAlign) {
CSS.marginTop = defPadding;
}
CSS.marginLeft = defPaddingLeft + 'px';
} else if (horiz == 1) {
if (!verticalAlign) {
CSS.marginTop = defPadding;
}
CSS.marginLeft = freeSpaceX - defPaddingCenter + 'px';
} else if (horiz == 2) {
if (!verticalAlign) {
CSS.marginTop = defPadding;
}
CSS.marginLeft = (freeSpaceX - defPaddingRight) * 2 + 'px';
}
}
this.verticalAlign = function(vertical) {
var freeSpaceY = ((window.innerHeight - ELEMENT.offsetHeight) / 2);
var defPadding = '8px';
var defPaddingTop;
var defPaddingMid;
var defPaddingBot;
verticalAlign = true;
if (CSS.position == 'relative' || CSS.position == 'static') {
CSS.position = 'absolute';
}
defPaddingTop = 8;
defPaddingMid = 8;
defPaddingBot = 8;
if (vertical == 0) {
if (!horizontalAlign) {
CSS.marginLeft = defPadding;
}
CSS.marginTop = defPadding;
} else if (vertical == 1) {
if (!horizontalAlign) {
CSS.marginLeft = defPadding;
}
CSS.marginTop = freeSpaceY - defPaddingMid + 'px';
} else if (vertical == 2) {
if (!horizontalAlign) {
CSS.marginLeft = defPadding;
}
CSS.marginTop = (freeSpaceY * 2) - defPaddingBot + 'px';
}
}
}
setBorder Example in index.php
var div1 = new Div();
div1.setPosition(Div.POSITION.ABSOLUTE);
div1.setBounds(0,700, 200,200);
div1.setColorQueue(0,0,0); //This method must be called every time a different color is needed for a certain attribute.
div1.createBorder(5, Div.BORDER.SOLID); // I really want something like this --> div1.createBorder(5,Div.BORDER.SOLID).setColor(0,0,0);
You can try using the Builder pattern:
function DivBuilder() {
var color;
var border;
var position;
var bounds;
this.DivBuilder = function() {}
this.color = function(c) {
//set the color
this.color = c;
return this;
}
this.border = function(b) {
//set the border
this.border = b;
return this;
}
this.position = function(p) {
//set position
this.position = p;
return this;
}
this.bounds = function(b) {
//set bounds
this.border = b;
return this;
}
this.build = function () {
//build the new Div object with the properties of the builder
var d = new Div(this);
return d;
}
}
and then:
var divb = new DivBuilder();
divb.position().bounds().border().color();
var div = divb.buid();
Main advantage over the telescopic constructor pattern (well, the adaptation of it to javascript) is that you can choose easier which property you want to initialize without having to create many different constructor cases.
If you want to write this.createBorder(...).setColor(...) It means that createBorder should return an object with the setColor method...
Thanks to Sebas I was able to come up with this... Please vote up Sebas if this helped you.
Nested in this.createBorder()
this.createBorder = function(width) {
CSS.border = width + 'px';
function DivBorderBuilder() {
this.setColor = function(r, b, g) {
alert('color');
CSS.borderColor = 'rgb(' + new Array(r, g, b) + ')';
return this;
}
this.setStyle = function(s){
CSS.borderStyle = s;
return this;
}
}return new DivBorderBuilder();
}
Creating Border in index.php
<script>
var div1 = new Div();
div1.setPosition(Div.POSITION.ABSOLUTE);
div1.setBounds(0,700, 200,200);
div1.createBorder(5).setStyle(Div.BORDER.SOLID).setColor(255,0,0);// Works Perfectly Now !
</script>

Categories