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
});
});
Related
I have following code.. I just want to add button in one column and rect in one column of table.
var data = [{
"name": "a",
"section": 1,
"stars": "d1"
}, {
"name": "b",
"section": 2,
"stars": "d2"
}, {
"name": "c",
"section": 1,
"stars": "d3"
}];
var columns = ['name', 'section', 'stars']
// create table
var table = d3.select("#table").append("table");
var thead = table.append("thead").append("tr");
thead.selectAll("th")
.data(columns)
.enter()
.append("th")
.text(function(d) {
return d;
});
var tbody = table.append("tbody");
thead.append("th").text('Action');
data.forEach(function(d, i) {
trow = tbody.append("tr")
trow.selectAll("td")
.data(columns)
.enter()
.append("td")
.text(function(e) {
return d[e]
});
trow.selectAll("td.button")
//use a class so you don't re-select the existing <td> elements
.data(function(d) {
return [d];
})
.enter()
.append("td")
.attr("class", "button")
.append("button")
.text(function(d) {
return "ADD"
})
.on("click", function(d) {
console.log(d);
alert("particular row data" + JSON.stringify(d))
});
});
div {
height: 250px;
width: 500px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="table"></div>
how to add button in section column and how to insert rect in stars column.
Hope you are looking for a similar output in following code snippet.
var data = [{
"name": "a",
"section": 1,
"stars": "d1"
}, {
"name": "b",
"section": 2,
"stars": "d2"
}, {
"name": "c",
"section": 1,
"stars": "d3"
}];
var columns = ['name', 'section', 'stars']
// create table
var table = d3.select("#table").append("table");
var thead = table.append("thead").append("tr");
thead.selectAll("th")
.data(columns)
.enter()
.append("th")
.text(function(d) {
return d;
});
var tbody = table.append("tbody");
thead.append("th").text('Action');
data.forEach(function(d, i) {
trow = tbody.append("tr");
trow.append("td")
.text(d["name"]);
trow.append("td")
.attr("class", "button")
.append("button")
.text("ADD")
.on("click", function() {
alert("particular row data" + JSON.stringify(d))
});
var svg = trow.append("td")
.append("svg")
.attr("width",30)
.attr("height",20);
svg.append("rect")
.attr("x",0)
.attr("y",0)
.attr("width",30)
.attr("height",20)
.style("stroke","red")
.style("fill","black");
svg.append("text")
.attr("x",15)
.attr("dy",10)
.text(d["stars"])
.attr("text-anchor","middle")
.style("stroke","white")
.style("alignment-baseline","central");
});
div {
height: 250px;
width: 500px;;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="table"></div>
What Gilsha said is right but here is another way of doing the same.
You can do it this way as well using a condition in the append function like below.
trow.selectAll("td")
.data(columns)
.enter()
.append("td")
.append(function(d) {
if (d == "stars") {
return document.createElement('button');
} else
return document.createElement('div');
})
.attr("class", function(d) {
if (d == "section") {
return "rect"//rect using style.
}
})
.text(function(e) {
return d[e]
});
for rectangle I used style.
.rect {
outline: 1px solid green;
}
working code here
This jsfiddle works on this format of data to create a piechart:
var data = [ { label: 'mylabel1', value: '1342' },
{ label: 'mylabel2', value: '1505' } ]
How do i get it to run on this format of data in this fiddle?
data=[{ country: 'Australia',
lat: '-25.274398',
lng: '133.775136',
values:
[ { label: 'ham', value: '1342' },
{ label: 'kpr', value: '1505' } ]
}]
This is the line I think I have to change but I just don't quite have it:
var pie = d3.layout.pie().value(function(d){return d.value;});
I have to get it to work on the values array in the new data object.
All my code from the 2nd jsfiddle:
var w = 400;
var h = 400;
var r = h/2;
var color = d3.scale.category20c();
var data = [{"label":"Category A", "value":20},
{"label":"Category B", "value":50},
{"label":"Category C", "value":30}];
var data = [ { label: 'mylabel1', value: '1342' },
{ label: 'mylabel2', value: '1505' } ]
data=[{ country: 'Australia',
lat: '-25.274398',
lng: '133.775136',
values:
[ { label: 'ham', value: '1342' },
{ label: 'kpr', value: '1505' } ]
}]
var vis = d3.select('#chart').append("svg:svg").data([data]).attr("width", w).attr("height", h).append("svg:g").attr("transform", "translate(" + r + "," + r + ")");
var pie = d3.layout.pie().value(function(d){return d.value;});
// declare an arc generator function
var arc = d3.svg.arc().outerRadius(r);
// select paths, use arc generator to draw
var arcs = vis.selectAll("g.slice").data(pie).enter().append("svg:g").attr("class", "slice");
arcs.append("svg:path")
.attr("fill", function(d, i){
return color(i);
})
.attr("d", function (d) {
// log the result of the arc generator to show how cool it is :)
console.log(arc(d));
return arc(d);
});
// add the text
arcs.append("svg:text").attr("transform", function(d){
d.innerRadius = 0;
d.outerRadius = r;
return "translate(" + arc.centroid(d) + ")";}).attr("text-anchor", "middle").text( function(d, i) {
return data[i].label;}
);
you have to change the data that you pass for data join. in this case it's the values array of your data:
var vis = d3.select('#chart').append("svg:svg").data([data[0].values])
also, to adjust the label drawing to this new data format, you need change return data[i].label; toreturn d.data.label;
updated jsFiddle
On the DC.js github, Stock Market Selection Strategy by Lon Riesberg is listed as an example of using the dc.js library.
Lon was able to create a stacked row chart and display it as a single row.
I'd like to be able to accomplish the same thing. I've only been able to figure out how create a row chart, as shown in my codepen, and below.
HTML
<script src="https://rawgit.com/mbostock/d3/master/d3.js" charset="utf-8"></script>
<script type="text/javascript" src="https://rawgithub.com/NickQiZhu/dc.js/master/web/js/crossfilter.js"></script>
<script type="text/javascript" src="https://rawgit.com/dc-js/dc.js/master/dc.js" ></script>
<div id="rowChart"></div>
Javascript
items = [
{Id: "01", Name: "Red", Price: "1.00", Quantity: "1",TimeStamp:111},
{Id: "02", Name: "White", Price: "10.00", Quantity: "1",TimeStamp:222},
{Id: "04", Name: "Blue", Price: "9.50", Quantity: "10",TimeStamp:434},
{Id: "03", Name: "Red", Price: "9.00", Quantity: "2",TimeStamp:545},
{Id: "06", Name: "Red", Price: "100.00", Quantity: "2",TimeStamp:676},
{Id: "05",Name: "Blue", Price: "1.20", Quantity: "2",TimeStamp:777}
];
var ndx = crossfilter(items);
var Dim = ndx.dimension(function (d) {return d.Name;})
var RowBarChart1 = dc.rowChart("#rowChart")
RowBarChart1
.width(250).height(500)
.margins({top: 20, left: 15, right: 10, bottom: 20})
.dimension(Dim)
.group(Dim.group().reduceCount())
.elasticX(true)
.label(function (d) {return d.key + " " + d.value;})
.ordering(function(d) { return -d.value })
.xAxis().tickFormat(function(v){return v}).ticks(3);
dc.renderAll();
How would I make this a stacked row chart where each section is 'Red','White,' or 'Blue' and is displayed in one row?
My goal is to have a working example that I can build off of. The answer thus far has helped, but I still haven't been able to build this.
you can create a div with d3.js and add the attribute for flex...
http://codepen.io/luarmr/pen/BNQYov
var chart = d3.select("#rowChart");
var bar = chart.selectAll("div")
.data(data)
.enter().append("div")
.attr('style',function(d,i){
return (
'flex:' + d.Quantity + '; '
+ 'background:' + color(i) + ';'
)
})
The attr.style could improve.
You can add the prefix for webkit
http://caniuse.com/#search=flex
Edit
http://codepen.io/luarmr/pen/yNVZMN
The javascript code used to produce that stacked bar chart does not use DC.js at all. It only uses D3.js. This can be seen from a beautified conversion of app.min.js; one (or both?) of these functions are the ones producing that stacked bar chart:
G = function(e, t) {
var r = (o - 40) / t;
f = "";
var a = d3.select("#categories-chart").append("svg").attr("height", 50).attr("width", o),
s = 0;
a.selectAll("rect").data(e).enter().append("rect").attr("category", function(e) {
return e.key
}).attr("x", function(e) {
var t = s,
a = Math.floor(r * e.value);
return s += a, t
}).attr("y", 7).attr("width", function(e) {
var t = Math.floor(r * e.value);
return t
}).attr("height", 25).style("fill", function(e) {
return "" != e ? "" === f || f === e.key ? d3.rgb(i[e.key]) : d3.rgb(i[e.key]).darker(1.75) : void 0
}).on("click", function(e) {
f = e.key, d3.select("#categories-chart").select(".reset").style("display", null), m.filter(f).top(t), C(m, t), dc.renderAll()
}).on("mouseover", function() {
d3.select(this).style("cursor", "pointer")
}), $("rect").popover({
container: "body",
trigger: "hover",
placement: "top",
content: function() {
return d3.select(this).attr("category")
}
})
},
C = function(e, t) {
var r = (o - 40) / t,
a = 0,
s = d3.select("#categories-chart");
s.selectAll("rect").data(e).transition().duration(150).attr("x", function(e) {
var t = a,
s = Math.floor(r * e.value);
return a += s, t
}).attr("y", 7).attr("width", function(e) {
var t = Math.floor(r * e.value);
return t
}).attr("height", 25).attr("category", function(e) {
return e.key
}).style("fill", function(e) {
return "" != e ? "" === f || f === e.key ? d3.rgb(i[e.key]) : d3.rgb(i[e.key]).darker(1.75) : void 0
}), $("rect").popover({
container: "body",
trigger: "hover",
placement: "top",
content: function() {
return d3.select(this).attr("category")
}
})
},
As you can see, no DC.js. Looking around elsewhere, there doesn't seem to be a DC.js native solution to this. For now, you might have to use D3.js (e.g. jsFiddle).
I didn't find any api to create stacked row chat from DC.js, so used D3.js with the help of https://www.dashingd3js.com/d3js-scales
var items = [
{Id: "01", Name: "Red", Price: "1.00", Quantity: 1,TimeStamp:111},
{Id: "02", Name: "Green", Price: "10.00", Quantity: 1,TimeStamp:222},
{Id: "04", Name: "Blue", Price: "9.50", Quantity: 4,TimeStamp:434},
{Id: "03", Name: "Orange", Price: "9.00", Quantity: 2,TimeStamp:545},
{Id: "06", Name: "Red", Price: "100.00", Quantity: 2,TimeStamp:676},
{Id: "05",Name: "purple", Price: "1.20", Quantity: 2,TimeStamp:777}
];
var max_x = 700; //maximum width of the graph
var height = 20; //maximum height
var temp_x = 0 ;
// calculating the quantity of all items
for (var i = 0; i < items.length; i++) {
temp_x = temp_x + items[i].Quantity;
}
var svgContainer = d3.select("body").append("svg")
.attr("width", max_x)
.attr("height", height)
var rectangles = svgContainer.selectAll("rect")
.data(items)
.enter()
.append("rect");
//temporary variable to mark start and end of an item.
var start=0;
var end=0;
var end1=0;
var rectangleAttributes = rectangles
.attr("x", function (d) {
// dynamically calculate the starting point of each item
start=end;
end=end+(d.Quantity * max_x)/temp_x;
return start;
})
.attr("height", height)
.attr("width", function (d) {
//dynamically calculate the width of each item
end1=(d.Quantity * max_x)/temp_x;
return end1; })
.style("fill", function(d) { return d.Name; });
Html code
<script src="https://rawgit.com/mbostock/d3/master/d3.js" charset="utf-8"> </script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.js" ></script>
<div id="rowChart"></div>
example: http://codepen.io/anon/pen/vOXPBq?editors=101
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,