Related
Looking at this fiddle (http://jsfiddle.net/gs6rehnx/2106/), there are four arcs with four different colors. I expect the Pie Chart to have three colors after clicking the update button. However, there are still four arcs. I assue, the mapping of the specific colors to the values is not working properly? Or is something else not working?
const chart = {};
const duration = 750;
const width = 160;
const height = 160;
const min = Math.min(width, height);
const oRadius = min / 2 * 0.9;
const iRadius = min / 2.5 * 0.85;
const pie = d3
.pie()
.value(function(d) {
return d.value;
})
.sort(null);
const arc = d3
.arc()
.outerRadius(oRadius)
.innerRadius(iRadius);
function arcTween(a) {
const i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
};
const labels = ['1', '2', '3', '4'];
const color = ["rgba(126,211,33,1)", "rgba(39,173,232,1)", "rgba(229,5,1,1)", "rgba(245,166,35,1)"];
const scale = d3.scaleOrdinal()
.domain(labels)
.range(color);
const create = function(data) {
const svg = d3
.select('.foo')
.append('svg')
.attr('class', 'pie')
.attr('width', width)
.attr('height', height)
.attr('id', 'svgClass');
svg
.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')
.attr('id', 'bar');
draw(data);
}
const draw = function(data) {
const path = d3.select('#bar')
.selectAll('path')
.data(pie(data))
path
.enter()
.append('g')
.append('path')
.attr('d', arc)
.attr('fill', (d, i) => {
return scale(d.data.name)
});
path
.transition()
.duration(duration)
.attrTween('d', function(d) {
const interpolate = d3.interpolate({
startAngle: 0,
endAngle: 0
}, d);
return function(t) {
return arc(interpolate(t));
};
});
};
const data = [{
"name": "1",
"value": 2
}, {
"name": "2",
"value": 1
}, {
"name": "3",
"value": 2
}, {
"name": "4",
"value": 1
}];
const newData = [{
"name": "1",
"value": 2
}, {
"name": "2",
"value": 1
}, {
"name": "3",
"value": 2
}];
function createPie() {
create(data)
}
function updatePie() {
draw(newData)
}
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.js"></script>
<button type="button" onclick="createPie()">Click Me First!</button>
<button type="button" onclick="updatePie()">Update Diagram!</button>
<div class='foo'></div>
The reason this isn't working is because d3 has three main parts to a selection:
enter
update
exit
In your example above you're dealing with the enter, the update isn't quite right and the exit is completely missing. This is a really good article from the author discussing data joins.
Enter
The following grabs all the new data elements, and adds a new g element for each one.
path.enter().append('g')
Exit
The following which you're missing will take all the items in the DOM that are no longer represented in the data, and remove them.
path.exit().remove();
const chart = {};
const duration = 750;
const width = 160;
const height = 160;
const min = Math.min(width, height);
const oRadius = min / 2 * 0.9;
const iRadius = min / 2.5 * 0.85;
const pie = d3
.pie()
.value(function(d) {
return d.value;
})
.sort(null);
const arc = d3
.arc()
.outerRadius(oRadius)
.innerRadius(iRadius);
function arcTween(a) {
const i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
};
const labels = ['1', '2', '3', '4'];
const color = ["rgba(126,211,33,1)", "rgba(39,173,232,1)", "rgba(229,5,1,1)", "rgba(245,166,35,1)"];
const scale = d3.scaleOrdinal()
.domain(labels)
.range(color);
const create = function(data) {
const svg = d3
.select('.foo')
.append('svg')
.attr('class', 'pie')
.attr('width', width)
.attr('height', height)
.attr('id', 'svgClass');
svg
.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')
.attr('id', 'bar');
draw(data);
}
const draw = function(data) {
const path = d3.select('#bar')
.selectAll('path')
.data(pie(data))
path
.enter()
.append('g')
.append('path')
.attr('d', arc)
.attr('fill', (d, i) => {
return scale(d.data.name)
});
path.exit().remove();
path
.transition()
.duration(duration)
.attrTween('d', function(d) {
const interpolate = d3.interpolate({
startAngle: 0,
endAngle: 0
}, d);
return function(t) {
return arc(interpolate(t));
};
});
};
const data = [{
"name": "1",
"value": 2
}, {
"name": "2",
"value": 1
}, {
"name": "3",
"value": 2
}, {
"name": "4",
"value": 1
}];
const newData = [{
"name": "1",
"value": 2
}, {
"name": "2",
"value": 1
}, {
"name": "3",
"value": 2
}];
function createPie() {
create(data)
}
function updatePie() {
draw(newData)
}
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.js"></script>
<button type="button" onclick="createPie()">Click Me First!</button>
<button type="button" onclick="updatePie()">Update Diagram!</button>
<div class='foo'></div>
I am trying to create a merged pie/bubble chart.
-- looking for a starting bubble chart base - maybe this one.
http://jsfiddle.net/xsafy/
^ need to cluster these bubbles - maybe sub bubbles per slice.
//bubble chart base.
http://jsfiddle.net/NYEaX/1450/
(function() {
var diameter = 250;
var svg = d3.select('#graph').append('svg')
.attr('width', diameter)
.attr('height', diameter);
var bubble = d3.layout.pack()
.size([diameter, diameter])
.value(function(d) {
return d.size;
})
.padding(3);
var color = d3.scale.ordinal()
.domain(["Lorem ipsum", "dolor sit", "amet", "consectetur", "adipisicing"])
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56"]);
function randomData() {
var data1 = {
"children": [
{
name: "AA",
className: "aa",
size: 170
},
{
name: "BB",
className: "bb",
size: 393
},
{
name: "CC",
className: "cc",
size: 293
},
{
name: "DD",
className: "dd",
size: 89
}
]
};
var data2 = {
"children": [
{
name: "AA",
className: "aa",
size: 120
},
{
name: "BB",
className: "bb",
size: 123
},
{
name: "CC",
className: "cc",
size: 193
},
{
name: "DD",
className: "dd",
size: 289
}
]
};
var j = Math.floor((Math.random() * 2) + 1);
console.log("j", j);
if (j == 1) {
return data1;
} else {
return data2;
}
}
change(randomData());
d3.select(".randomize")
.on("click", function() {
change(randomData());
});
function change(data) {
console.log("data", data);
// generate data with calculated layout values
var nodes = bubble.nodes(data)
.filter(function(d) {
return !d.children;
}); // filter out the outer bubble
var vis = svg.selectAll('circle')
.data(nodes);
vis.enter()
.insert("circle")
.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
.attr('r', function(d) {
return d.r;
})
.style("fill", function(d) {
return color(d.name);
})
.attr('class', function(d) {
return d.className;
});
vis
.transition().duration(1000)
vis.exit()
.remove();
};
})();
//doughnut chart base.
derived from these examples.
http://bl.ocks.org/dbuezas/9306799
https://bl.ocks.org/mbostock/1346410
http://jsfiddle.net/NYEaX/1452/
var svg = d3.select("#graph")
.append("svg")
.append("g")
svg.append("g")
.attr("class", "slices");
svg.append("g")
.attr("class", "labels");
svg.append("g")
.attr("class", "lines");
var width = 560,
height = 450,
radius = Math.min(width, height) / 2;
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var arc = d3.svg.arc()
.outerRadius(radius * 0.85)
.innerRadius(radius * 0.83);
var outerArc = d3.svg.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9);
svg.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var key = function(d) {
return d.data.label;
};
var color = d3.scale.ordinal()
.domain(["Lorem ipsum", "dolor sit", "amet", "consectetur", "adipisicing"])
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56"]);
function randomData() {
var data1 = [{
"label": "AA",
"value": 0.911035425558026
}, {
"label": "BB",
"value": 0.08175111844879179
}, {
"label": "CC",
"value": 0.25262439557273275
}, {
"label": "DD",
"value": 0.8301366989535612
}, {
"label": "EE",
"value": 0.0517762265780517
}];
var data2 = [{
"label": "AA",
"value": 0.243879179
}, {
"label": "BB",
"value": 0.243879179
}, {
"label": "CC",
"value": 0.2342439557273275
}, {
"label": "DD",
"value": 0.2349535612
}, {
"label": "EE",
"value": 0.2345780517
}];
var j = Math.floor((Math.random() * 2) + 1);
if (j == 1) {
return data1;
} else {
return data2;
}
}
change(randomData());
d3.select(".randomize")
.on("click", function() {
change(randomData());
});
function change(data) {
/* ------- PIE SLICES -------*/
var slice = svg.select(".slices").selectAll("path.slice")
.data(pie(data), key);
slice.enter()
.insert("path")
.style("fill", function(d) {
return color(d.data.label);
})
.attr("class", "slice");
slice
.transition().duration(1000)
.attrTween("d", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
return arc(interpolate(t));
};
})
slice.exit()
.remove();
};
This is as close I have got so far.. I've merged the two charts together as such - although there is a bug still trying to update the bubbles -- at least get them to scale/morph/move/animate. Ideally I want them to stick close to their group segments..
here is the fiddle.
https://jsfiddle.net/tk5xog0g/1/
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>doughpie</title>
<link rel="stylesheet" href="css/generic.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<button class="randomize">randomize</button>
<div data-role="doughpie" data-width="450" data-height="450" id="graph"></div>
<script>
//__invoke pie chart
$('[data-role="doughpie"]').each(function(index) {
createDoughnut(this);
});
function bubbledata(data){
//loop through data -- and MERGE children
var childs = [];
$.each(data, function( index, value ) {
childs.push(value.children);
});
var merged = [].concat.apply([], childs);//flatterns multidimensional array
return $.extend(true, {}, {"children": merged});// return deep clone
}
function createDoughnut(el){
var width = $(el).data("width"),
height = $(el).data("height"),
radius = Math.min(width, height) / 2;
var svg = d3.select($(el)[0])
.append("svg")
.attr("width", width)
.attr("height", height)
//_create doughpie shell
var doughpie = svg.append("g")
.attr("class", "doughpie");
doughpie.append("g")
.attr("class", "slices");
doughpie.append("g")
.attr("class", "labels");
doughpie.append("g")
.attr("class", "lines");
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var arc = d3.svg.arc()
.outerRadius(radius * 0.85)
.innerRadius(radius * 0.83);
var outerArc = d3.svg.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9);
doughpie.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var key = function(d) {
return d.data.label;
};
var color = d3.scale.ordinal()
.range(["#46a2de", "#7b3cce", "#31d99c", "#de5942", "#ffa618"]);
//_create doughpie shell
//_create bubble
var diameter = width/2;//take half/width
var bubs = svg.append("g")
.attr("class", "bubs");
bubs.attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
var bubble = d3.layout.pack()
.size([diameter, diameter])
.value(function(d) {
return d.size;
})
.padding(3);
//_create bubble
function randomData() {
var data1 = [{
"label": "AA",
"value": 0.911035425558026,
"children": [
{
name: "some text aa",
group: "AA",
size: 120
}
]
}, {
"label": "BB",
"value": 0.08175111844879179,
"children": [
{
name: "some text bb",
group: "BB",
size: 123
}
]
}, {
"label": "CC",
"value": 0.25262439557273275,
"children": [
{
name: "some text cc",
group: "CC",
size: 193
}
]
}, {
"label": "DD",
"value": 0.8301366989535612,
"children": [
{
name: "some text dd",
group: "DD",
size: 29
},
{
name: "some text dd",
group: "DD",
size: 289
}
]
}, {
"label": "EE",
"value": 0.0517762265780517,
"children": [
{
name: "some text ee",
group: "EE",
size: 389
},
{
name: "some text ee",
group: "EE",
size: 89
}
]
}];
var data2 = [{
"label": "AA",
"value": 0.243879179,
"children": [
{
name: "some text aa",
group: "AA",
size: 120
}
]
}, {
"label": "BB",
"value": 0.243879179,
"children": [
{
name: "some text bb",
group: "BB",
size: 123
}
]
}, {
"label": "CC",
"value": 0.2342439557273275,
"children": [
{
name: "some text cc",
group: "CC",
size: 193
}
]
}, {
"label": "DD",
"value": 0.2349535612,
"children": [
{
name: "some text dd",
group: "DD",
size: 29
},
{
name: "some text dd",
group: "DD",
size: 289
}
]
}, {
"label": "EE",
"value": 0.2345780517,
"children": [
{
name: "some text ee",
group: "EE",
size: 389
},
{
name: "some text ee",
group: "EE",
size: 89
}
]
}];
var j = Math.floor((Math.random() * 2) + 1);
if (j == 1) {
return data1;
} else {
return data2;
}
}
change(randomData());
d3.select(".randomize")
.on("click", function() {
change(randomData());
});
function change(data) {
/* ------- ANIMATE PIE SLICES -------*/
var slice = doughpie.select(".slices").selectAll("path.slice")
.data(pie(data), key);
slice.enter()
.insert("path")
.style("fill", function(d) {
return color(d.data.label);
})
.style("transform", function(d, i){
//return "translate(0, 0)";
})
.attr("class", "slice");
slice
.transition().duration(1000)
.attrTween("d", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
return arc(interpolate(t));
};
})
slice.exit()
.remove();
/* ------- ANIMATE PIE SLICES -------*/
/* ------- ANIMATE BUBBLES -------*/
// generate data with calculated layout values
var data = bubbledata(data);
var nodes = bubble.nodes(data)
.filter(function(d) {
return !d.children;
}); // filter out the outer bubble
var bubbles = bubs.selectAll('circle')
.data(nodes);
bubbles.enter()
.insert("circle")
.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
.attr('r', function(d) {
return d.r;
})
.style("fill", function(d) {
return color(d.group);
});
bubbles
.transition().duration(1000)
bubbles.exit()
.remove();
/* ------- ANIMATE BUBBLES -------*/
};
}
</script>
</body>
</html>
I thought I will try and first plot mid dot points on the arcs -- and maybe from THOSE -- start to get a batch of zone coordinates to master control the coloured bubbles.
I've been trying to follow this -- How to get coordinates of slices along the edge of a pie chart?
https://jsfiddle.net/tk5xog0g/28/
/*placing mid dots*/
var midDotsArrayCooridnates = [];
slice
.each(function(d) {
x = 0 + (radius * 0.85) * Math.cos(d.startAngle);
y = 0 + (radius * 0.85) * Math.sin(d.startAngle);
var obj = {
"x": x,
"y": y
}
midDotsArrayCooridnates.push(obj);
});
$.each(midDotsArrayCooridnates, function(index, value) {
var dot = doughpie.select(".slicedots").append('circle')
.attr('cx', value.x)
.attr('cy', value.y)
.attr('r', 5)
.style("fill", "red")
.attr('class', "ccc")
});
/*placing mid dots*/
I have a treemap rendered with d3. Since I want to be responsive and economical (not running js if I do not really have to) I am using percentages for the divs. But the transitions are some kind of wired using percentages. After reading this issue I have tried several styleTweens but I do not have any luck ...
How can I use transitions for percentage values in d3?
Here is a fiddle of the below code: http://jsfiddle.net/0z7p68wb/ (just click somewhere on the treemap to start the animation)
var target = d3.select("#target")
render = function(data, oldData) {
// our custom d3 code
console.log("render!", data, oldData);
// draw rectangles
var margin = {margin: 0.2, padding: 2},
width = 100 - margin.margin * 2,
height = 100 - margin.margin * 2;
var treemap = d3.layout.treemap()
.size([100, 100])
//.sticky(true)
.value(function(d) { return d.size; });
// bind data
var nodes = target.datum(data)
.selectAll(".node")
.data(treemap.nodes);
// transform existing nodes
if (data !== oldData)
nodes.transition()
.duration(1500)
.call(position);
// append new nodes
nodes.enter().append("div")
.attr("class", "node")
.style("position", "absolute")
.style("display", function(d,i) { return i==0 ? "none" : "block"})
.style("background-color", "silver")
.call(position)
;
// remove obsolete nodes
nodes.exit().remove();
// set position of nodes
function position() {
this.style("left", function(d) { return d.x + "%"; })
.style("top", function(d) { return d.y + "%"; })
.style("width", function(d) { return Math.max(0, d.dx) + "%"; })
.style("height", function(d) { return Math.max(0, d.dy) + "%"; })
}
}
tree1 = {
name: "tree",
children: [
{ name: "Word-wrapping comes for free in HTML", size: 16000 },
{ name: "animate makes things fun", size: 8000 },
{ name: "data data everywhere...", size: 5220 },
{ name: "display something beautiful", size: 3623 },
{ name: "flex your muscles", size: 984 },
{ name: "physics is religion", size: 6410 },
{ name: "query and you get the answer", size: 2124 }
]
};
tree2 = {
name: "tree",
children: [
{ name: "Word-wrapping comes for free in HTML", size: 8000 },
{ name: "animate makes things fun", size: 10000 },
{ name: "data data everywhere...", size: 2220 },
{ name: "display something beautiful", size: 6623 },
{ name: "flex your muscles", size: 1984 },
{ name: "physics is religion", size: 3410 },
{ name: "query and you get the answer", size: 2124 }
]
};
tree = tree1;
render(tree, tree);
d3.select("#target").on("click", function(){
console.log("click");
tree = tree == tree1 ? tree2 : tree1;
render(tree, {});
});
Got it!
// transform existing nodes
if (data !== oldData)
nodes.transition()
.duration(1500)
.call(position)
.styleTween('left', function(d,i,a){
return d3.interpolateString(this.style.left, d.x + "%")
})
.styleTween('top', function(d,i,a){;
return d3.interpolateString(this.style.top, d.y + "%")
})
.styleTween('width', function(d,i,a){;
return d3.interpolateString(this.style.width, Math.max(0, d.dx) + "%")
})
.styleTween('height', function(d,i,a){;
return d3.interpolateString(this.style.height, Math.max(0, d.dy) + "%")
})
;
I'm trying to achieve the following display utilizing the d3js.org library:
I get the circle svg objects to display with varying radius based on an attribute I'm getting from the JSON, but where I'm getting stuck is grouping them together and displaying along a linear, horizontal axis.
Here is my JSON structure:
[
{
"category" : "Foo",
"radius" : "3"
},
{
"category" : "Bar",
"radius" : "2"
},
{
"category" : "Foo",
"radius" : "3"
},
{
"category" : "Bar",
"radius" : "1"
},
{
"category" : "Bar",
"radius" : "2"
},
{
"category" : "Foo",
"radius" : "1"
}
]
d3 Javascript
var height = 50,
width = 540;
var companyProfileVis = d3.select(".myDiv").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
d3.json("data/myData.json", function(data){
companyProfileVis.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("r", function (d) { return d.radius * 4; })
.attr("cx", function(d) { return d.radius * 20; })
.attr("cy", 20)
});
And finally my HTML
<div class="myDiv"></div>
Expanding on Pablo's answer a bit, you would also need to sort the values in the grouped elements to achieve the order you have in the picture. The code would look like this.
var nested = d3.nest()
.key(function(d) { return d.category; })
.sortValues(function(a, b) { return b.radius - a.radius; })
.entries(data);
The nested selection based on this would look as follows.
var gs = svg.selectAll("g").data(nested)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + (20 + i * 100) + ")"; });
gs.selectAll("circle").data(function(d) { return d.values; })
.enter().append("circle");
Note that you're moving the g elements according to the index, so you don't have to worry about the y coordinate of the circles later. The x coordinate is computed based on the index similar to how the y coordinate is computed for the g.
All that you then have to do is set a few more attributes and append the text elements. Complete demo here.
You can use d3.nest to group the data items by category, and then use nested selections to create both groups of circles.
// Nest the items by category
var nestedData = d3.nest(data)
.key(function(d) { return d.category; })
.map(data, d3.map)
.values();
This will give you the following array:
nestedData = [
[
{category: "Foo", radius: "3"},
{category: "Foo", radius: "3"},
{category: "Foo", radius: "1"}
],
[
{category: "Bar", radius: "2"},
{category: "Bar", radius: "1"},
{category: "Bar", radius: "2"}
]
]
Regards,
I have a bar chart with ordinal scale for the x-axis. I want to display the y-values on the top of each bar or in the bottom of each bar. It would be also acceptable to display the y-values when one hovers over the bar. Is there a function or a way in a dc.js to do that? Here is the jsfiddle and my code is below the pic>
Edit: Here is my code:
HTML
<body>
<div id='Chart'>
</div>
</body>
JS
var data = [{
Category: "A",
ID: "1"
}, {
Category: "A",
ID: "1"
}, {
Category: "A",
ID: "1"
}, {
Category: "A",
ID: "2"
}, {
Category: "A",
ID: "2"
}, {
Category: "B",
ID: "1"
}, {
Category: "B",
ID: "1"
}, {
Category: "B",
ID: "1"
}, {
Category: "B",
ID: "2"
}, {
Category: "B",
ID: "3"
}, {
Category: "B",
ID: "3"
}, {
Category: "B",
ID: "3"
}, {
Category: "B",
ID: "4"
}, {
Category: "C",
ID: "1"
}, {
Category: "C",
ID: "2"
}, {
Category: "C",
ID: "3"
}, {
Category: "C",
ID: "4"
}, {
Category: "C",
ID: "4"
},{
Category: "C",
ID: "5"
}];
var ndx = crossfilter(data);
var XDimension = ndx.dimension(function (d) {
return d.Category;
});
var YDimension = XDimension.group().reduceCount(function (d) {
return d.value;
});
dc.barChart("#Chart")
.width(480).height(300)
.dimension(XDimension)
.group(YDimension)
.transitionDuration(500)
.xUnits(dc.units.ordinal)
.label(function(d) {return d.value})
.x(d3.scale.ordinal().domain(XDimension))
dc.renderAll();
Check updated jsfiddle
.renderlet(function(chart){
var barsData = [];
var bars = chart.selectAll('.bar').each(function(d) { barsData.push(d); });
//Remove old values (if found)
d3.select(bars[0][0].parentNode).select('#inline-labels').remove();
//Create group for labels
var gLabels = d3.select(bars[0][0].parentNode).append('g').attr('id','inline-labels');
for (var i = bars[0].length - 1; i >= 0; i--) {
var b = bars[0][i];
//Only create label if bar height is tall enough
if (+b.getAttribute('height') < 18) continue;
gLabels
.append("text")
.text(barsData[i].data.value)
.attr('x', +b.getAttribute('x') + (b.getAttribute('width')/2) )
.attr('y', +b.getAttribute('y') + 15)
.attr('text-anchor', 'middle')
.attr('fill', 'white');
}
})
If you don't want the labels visible when the bars redraw (for example when bars change after user filters/clicks other chart) you can move the check of old values from de renderlet to the to a preRedraw
listener.
.on("preRedraw", function(chart){
//Remove old values (if found)
chart.select('#inline-labels').remove();
})
Alternative
D3-ish way
Demo jsfiddle
.renderlet(function (chart) {
//Check if labels exist
var gLabels = chart.select(".labels");
if (gLabels.empty()){
gLabels = chart.select(".chart-body").append('g').classed('labels', true);
}
var gLabelsData = gLabels.selectAll("text").data(chart.selectAll(".bar")[0]);
gLabelsData.exit().remove(); //Remove unused elements
gLabelsData.enter().append("text") //Add new elements
gLabelsData
.attr('text-anchor', 'middle')
.attr('fill', 'white')
.text(function(d){
return d3.select(d).data()[0].data.value
})
.attr('x', function(d){
return +d.getAttribute('x') + (d.getAttribute('width')/2);
})
.attr('y', function(d){ return +d.getAttribute('y') + 15; })
.attr('style', function(d){
if (+d.getAttribute('height') < 18) return "display:none";
});
})
In dc.js version 3.* . You can just use .renderLabel(true). It will print the value at top
Here's a bit of a hacky solution using the renderlet method. jsFiddle: http://jsfiddle.net/tpsc5f9f/4/
JS
var barChart = dc.barChart("#Chart")
.width(480).height(300)
.dimension(XDimension)
.group(YDimension)
.transitionDuration(500)
.xUnits(dc.units.ordinal)
.x(d3.scale.ordinal().domain(XDimension))
dc.renderAll();
barChart.renderlet(function(chart){
moveGroupNames();
});
function moveGroupNames() {
var $chart = $('#Chart'),
bar = $chart.find('.bar');
bar.each(function (i, item) {
var bar_top = this.height.baseVal.value;
var bar_left = this.width.baseVal.value;
var bar_offset_x = 30;
var bar_offset_y = 33;
var bar_val = $(this).find('title').html().split(':')[1];
$chart.append('<div class="val" style="bottom:'+(bar_top+bar_offset_y)+'px;left:'+((bar_left*i)+(bar_offset_x))+'px;width:'+bar_left+'px">'+bar_val+'</div>');
});
}
Added CSS
body {
margin-top:20px;
}
#Chart {
position:relative;
}
#Chart .val {
position:absolute;
left:0;
bottom:0;
text-align: center;
}
If you use a specialized valueAccessor with a chart, you can make the following substitution in dimirc's "D3-ish way" solution.
Change
.text(function(d){
return d3.select(d).data()[0].data.value
})
To
.text(function(d){
return chart.valueAccessor()(d3.select(d).data()[0].data)
});
This function will reposition a row chart's labels to the end of each row. A similar technique could be used for a bar chart using the "y" attribute.
Labels animate along with the row rect, using chart's transitionDuration
Uses chart's valueAccessor function
Uses chart's xAxis scale to calculate label position
rowChart.on('pretransition', function(chart) {
var padding = 2;
chart.selectAll('g.row > text') //find all row labels
.attr('x', padding) //move labels to starting position
.transition() //start transition
.duration(chart.transitionDuration()) //use chart's transitionDuration so labels animate with rows
.attr('x', function(d){ //set label final position
var dataValue = chart.valueAccessor()(d); //use chart's value accessor as basis for row label position
var scaledValue = chart.xAxis().scale()(dataValue); //convert numeric value to row width
return scaledValue+padding; //return scaled value to set label position
});
});