I am able to populate a stacked bar chart first time, but my requirement is to update the stacked bar chart with new data on button click. On button click, i m making call to backend and getting the data, Could you please guide me as how to update the stacked bar char chart. My problem is passing the new data to bar chart.
d3.json("http://myhost/ITLabourEffortChart/effort/effort",function(error, data){
color.domain(d3.keys(data.effort[0]).filter(function(key) {
return key !== "qName"; }));
data.effort.forEach(function(d) {
var y0 = 0;
d.effortHr = color.domain().map(function(name) {
return {name: name, y0: y0, y1: y0 += +d[name]}; });
d.total = d.effortHr[d.effortHr.length - 1].y1;
});
x.domain(data.effort.map(function(d) { return d.qName; }));
y.domain([0, d3.max(data.effort, function(d) {
return d.total; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("FTE");
var state = svg.selectAll(".layer")
.data(data.effort)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x(d.qName) + ",0)"; });
rect = state.selectAll("rect")
.attr("id", "barchart")
.data(function(d) {
return d.effortHr; })
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function(d) {
return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.style("fill", function(d) { return color(d.name); });
On Update i am calling below method
function redraw() {
d3.json("http://localhost:8080/ITLabourEffortChart/effort/effort/YrReports",function(error, data){
color.domain(d3.keys(data.effort[0]).filter(function(key) {
return key !== "qName"; }));
data.effort.forEach(function(d) {
var y0 = 0;
d.ages = color.domain().map(function(name) {
return {name: name, y0: y0, y1: y0 += +d[name]}; });
d.total = d.ages[d.ages.length - 1].y1;
});
var updatebar = svg.selectAll("#barchart");
// Update…
updatebar
.transition()
.duration(500)
.delay(function(d, i) { return i * 10; })
.transition()
.attr("width", x.rangeBand())
.attr("y", function(d) {
return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.style("fill", function(d) { return color(d.name); }
.attr("x", function(d) {
return x(d.x); })
);
});
.attr("x", function(d) {
return x(d.x); })
);
});
To update your data you will just need to select the svg elements again and rebind the data. In your example you are already selecting the #barchart, now you just need to rebind the data. And you can do that in the same way you did it when you first created the svg Elements. So something like this should do the trick:
var updatebar = svg.selectAll("#barchart");
.data(newdata)
.transition()
.duration(500)
... (etc.)
Here you can find a more detailed explaination:
http://chimera.labs.oreilly.com/books/1230000000345/ch09.html#_updating_data
Update:
Ok, unfortunately I cannot use Fiddle so I just post my working code here. As far as I could see you have a problem with your selectAll, because there is no element called .effort. Here is the updated code for your redraw-function:
function redraw() {
var effort = [];
var obj = {
pfte: "20",
efte: "50",
qName: "Q1"
};
var obj2 = {
pfte: "10",
efte: "13",
qName: "Q2"
};
effort[0] = obj;
effort[1] = obj2;
var newDataSet = new Object();
newDataSet.effort = effort;
color.domain(d3.keys(newDataSet.effort[0]).filter(function (key) {
return key !== "qName";
}));
effortDataSet = newDataSet.effort;
effortDataSet.forEach(function (d) {
var y0 = 0;
d.effortHr = color.domain().map(function (name) {
return { name: name, y0: y0, y1: y0 += +d[name] };
});
d.total = d.effortHr[d.effortHr.length - 1].y1;
});
state = svg.selectAll(".g")
.data(effortDataSet)
.attr("class", "g")
.attr("transform", function (d) { return "translate(" + x(d.qName) + ",0)"; });
state = state.selectAll("rect")
.data(function (d) {
return d.effortHr;
})
.attr("width", x.rangeBand())
.attr("y", function (d) {
return y(d.y1);
})
.attr("height", function (d) {
//console.log(y(d.y0) - y(d.y1));
return y(d.y0) - y(d.y1);
})
.style("fill", function (d) { return color(d.name); });
}
Related
I am beginner in d3 v3. I created multiple donut charts. When I move my mouse over a slice, I get the tooltip that appears. But I would also like the slice to grow a little bit.
I have already tried several codes but I can't get there. I wonder if the problem may be related to d3 tip
this is my js file :
//Source : http://bl.ocks.org/mbostock/1305337
var m = 15,
r = 80,
z = d3.scale.ordinal()
.range(["#50FFC5", "#54E868", "#54CCE8", "#6395FF"]);
var pie = d3.layout.pie()
.value(function (d) {
return +d.count;
})
.sort(function (a, b) {
return b.count - a.count;
});
var arc = d3.svg.arc()
.innerRadius(r / 2)
.outerRadius(r);
var radius = d3.scale.linear()
.range([10, r])
var tip = d3.tip()
.attr('class', 'd3-tip')
.html(function (d) {
return d.data.genre + ": " + d.data.count;
})
.direction('s');
d3.csv("../CSV/genreHameau.csv", function (error, hameau) {
if (error) throw error;
var datas = d3.nest()
.key(function (d) {
return d.origine;
})
.entries(hameau);
datas.forEach(function (d) {
totalOrigin = d3.sum(d.values, function (d) {
return +d.count;
})
d.values.forEach(function (dd) {
dd.totalOrigin = totalOrigin
})
})
datas.sort(function (a, b) {
return d3.descending(a.values[0].totalOrigin, b.values[0].totalOrigin)
})
// définir le radius / rayon des arcs (rendre fonction de 'count')
var max = d3.max(datas, function (d) {
return d.values[0].totalOrigin
})
var min = d3.min(datas, function (d) {
return d.values[0].totalOrigin
})
radius.domain([min, max])
arc
.innerRadius(function (d) {
return radius(d.data.totalOrigin) / 2
})
.outerRadius(function (d) {
return radius(d.data.totalOrigin)
})
function size(d) {
return radius(d.values[0].totalOrigin) + m
}
var div = d3.select("body").selectAll("div")
.data(datas)
.enter().append("div") // http://code.google.com/p/chromium/issues/detail?id=98951
.style("display", "inline-block")
.style("width", function (d) {
return 2 * size(d) + "px"
})
.style("height", function (d) {
return 2 * size(d) + "px"
})
.style("min-width", "80px")
.style("min-height", "80px")
div.append("span")
.attr("class", "nomhameau")
.text(function (d) {
return d.key;
})
.append("span")
.attr("class", "nombrehameau")
.text(function (d) {
return " (" + d.values[0].totalOrigin + ")"
});
var svg = div.append("svg")
//.attr("width", (r + m) * 2)
//.attr("height", (r + m) * 2)
.attr("width", function (d) {
return 2 * size(d)
})
.attr("height", function (d) {
return 2 * size(d)
})
.append("g")
.attr("transform", function (d) {
return "translate (" + size(d) + "," + size(d) + ")"
});
svg.call(tip);
var g = svg.selectAll("g")
.data(function (d) {
return pie(d.values);
})
.enter().append("g")
.on("mouseover", tip.show)
.on("mouseout", tip.hide)
g.append("path")
.attr("d", arc)
.style("fill", function (d) {
return z(d.data.genre);
})
.append("title")
});
Thank you for your help
I would do it this way:
Just like you created the arc, create a new one with a greater radius, either inner, outer or both, something like:
var arcHighlight = d3.svg.arc()
.innerRadius(r / 2)
.outerRadius(r*1.1);
Then you add mouseover and mouseout events to the slice and modify the slice doing something like:
// Add a colored arc path, with a mouseover title showing the count.
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return z(d.data.carrier); })
// new code
.on('mouseover', function(d) {
d3.select(this)
.transition()
.attr('d', arcHighlight(d));
})
.on('mouseout', function(d) {
d3.select(this)
.transition()
.attr('d', arc(d));
})
.append("title")
.text(function(d) { return d.data.carrier + ": " + d.data.count; });
The transition is optional, but it looks fancier ;)
I have prototype code I am working with here:
jsfiddle
The example shows how to add a segment when new data is added but not how to remove it again when the data changes [back]. I am fairly new to d3 and don't quite get the exit() function yet...
if you reverse the initial and second dataset you will see that the grapes segment is never removed. Thanks in advance!
any help would be great!
The update code: (my final chart needs to update on a timer when data changes)
var arcs = arc_grp.selectAll("path")
.data(donut(data));
arcs.enter()
.append("path")
.attr("stroke", "white")
.attr("stroke-width", 0.8)
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc)
.each(function(d) { return this.current = d; });
var sliceLabel = label_group.selectAll("text")
.data(donut(data));
sliceLabel.enter()
.append("text")
.attr("class", "arcLabel")
.attr("transform", function(d) { return "translate(" + (arc.centroid(d)) +
")"; })
.attr("text-anchor", "middle")
.style("fill-opacity", function(d) {
if (d.value === 0) { return 1e-6; }
else { return 1; }
})
.text(function(d) { return d.data.label; });
};
To remove elements without data, you have to use exit(), which...
Returns the exit selection: existing DOM elements in the selection for which no new datum was found.
So, inside your updateChart function, you need an exit selection for both paths and texts:
var newArcs = arcs.data(donut(dataset));
newArcs.exit().remove();
var newSlices = sliceLabel.data(donut(dataset));
newSlices.exit().remove();
Here is your updated code:
// Setup all the constants
var duration = 500;
var width = 500
var height = 300
var radius = Math.floor(Math.min(width / 2, height / 2) * 0.9);
var colors = ["#d62728", "#ff9900", "#004963", "#3497D3"];
// Test Data
var d2 = [{
label: 'apples',
value: 20
}, {
label: 'oranges',
value: 50
}, {
label: 'pears',
value: 100
}];
var d1 = [{
label: 'apples',
value: 100
}, {
label: 'oranges',
value: 20
}, {
label: 'pears',
value: 20
}, {
label: 'grapes',
value: 20
}];
// Set the initial data
var data = d1
var updateChart = function(dataset) {
var newArcs = arcs.data(donut(dataset));
newArcs.exit().remove();
newArcs.enter()
.append("path")
.attr("stroke", "white")
.attr("stroke-width", 0.8)
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc);
newArcs.transition()
.duration(duration)
.attrTween("d", arcTween);
var newSlices = sliceLabel.data(donut(dataset));
newSlices.exit().remove();
newSlices.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + (arc.centroid(d)) + ")";
})
.style("fill-opacity", function(d) {
if (d.value === 0) {
return 1e-6;
} else {
return 1;
}
});
sliceLabel.data(donut(dataset)).enter()
.append("text")
.attr("class", "arcLabel")
.attr("transform", function(d) {
return "translate(" + (arc.centroid(d)) + ")";
})
.attr("text-anchor", "middle")
.style("fill-opacity", function(d) {
if (d.value === 0) {
return 1e-6;
} else {
return 1;
}
})
.text(function(d) {
return d.data.label;
});
};
var color = d3.scale.category20();
var donut = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var arc = d3.svg.arc()
.innerRadius(radius * .4)
.outerRadius(radius);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var arc_grp = svg.append("g")
.attr("class", "arcGrp")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")");
var label_group = svg.append("g")
.attr("class", "lblGroup")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")");
var arcs = arc_grp.selectAll("path")
.data(donut(data));
arcs.enter()
.append("path")
.attr("stroke", "white")
.attr("stroke-width", 0.8)
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc)
.each(function(d) {
return this.current = d;
});
var sliceLabel = label_group.selectAll("text")
.data(donut(data));
sliceLabel.enter()
.append("text")
.attr("class", "arcLabel")
.attr("transform", function(d) {
return "translate(" + (arc.centroid(d)) + ")";
})
.attr("text-anchor", "middle")
.style("fill-opacity", function(d) {
if (d.value === 0) {
return 1e-6;
} else {
return 1;
}
})
.text(function(d) {
return d.data.label;
});
// Update the data
setInterval(function(model) {
return updateChart(d2);
}, 2000);
// Tween Function
var arcTween = function(a) {
var i = d3.interpolate(this.current, a);
this.current = i(0);
return function(t) {
return arc(i(t));
};
};
.arcLabel {
font: 10px sans-serif;
fill: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
In Normalized stacked bar I am trying to bind data in all rect in a bar but wrong value is passed. I adopted my code from this example and made it horizontal. Below is my code and I have created a plunker as well. In .text function entire object is passed. Can someone help me where I am going wrong
var svg = d3.select("svg"),
margin = {
top: 20,
right: 60,
bottom: 30,
left: 40
},
/*width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,*/
width = 120,
height = 120,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var y = d3.scaleBand()
.rangeRound([0, width])
.padding(0.1)
.align(0.1);
var x = d3.scaleLinear()
.rangeRound([height, 0]);
var z = d3.scaleOrdinal()
.range(['#02CA22', '#FB5652', '#FFB005']);
var stack = d3.stack()
.offset(d3.stackOffsetExpand);
d3.csv("data.csv", type, function (error, data) {
if (error) throw error;
/*data.sort(function(a, b) {
return b[data.columns[1]] / b.total - a[data.columns[1]] / a.total;
});*/
y.domain(data.map(function (d) {
return d.State;
}));
z.domain(data.columns.slice(1));
var serie = g.selectAll(".serie")
.data(stack.keys(data.columns.slice(1))(data))
.enter().append("g")
.attr("class", "serie")
.attr("fill", function (d) {
return z(d.key);
});
var rect = serie.selectAll("rect")
.data(function (d) {
return d;
}).enter();
rect.append("rect")
.attr("y", function (d) {
return y(d.data.State);
})
.attr("x", function (d) {
return x(d[1]);
})
.attr("width", function (d) {
return x(d[0]) - x(d[1]);
})
.attr("height", y.bandwidth());
rect.append("text")
.text(function (d) {
console.log('d');
console.log(d);
console.log(d.data.key);
return 'val';
})
.attr("y", function (d) { return y(d.data.State) + y.bandwidth() / 2; })
.attr("x", function (d) {
return x(d[1]);
});
/* g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(2, "%"));*/
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y));
var legend = serie.append("g")
.attr("class", "legend")
.attr("transform", function (d) {
var d = d[0];
return "translate(" + ((x(d[0]) + x(d[1])) / 2) + ", " + (y(d.data.State) - y.bandwidth()) + ")";
});
/*legend.append("line")
.attr("y1", 5)
.attr("x1", 15)
.attr("x2", 15)
.attr("y2", 12)
.attr("stroke", "#000");
legend.append("text")
.attr("x", 9)
.attr("dy", "0.35em")
.attr("fill", "#000")
.style("font", "10px sans-serif")
.text(function (d) {
return d.key;
}); */
});
function type(d, i, columns) {
var t;
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
d.total = t;
return d;
}
I think the best way to do this is to modify your subselection data-binding to include that information:
var rect = serie.selectAll("rect")
.data(function (d) {
// return all the data you need as flat as possible
var rv = d.map(function(da){
return {p: da, key: d.key, state: da.data.State}
});
return rv;
}).enter();
The text is then available as:
rect.append("text")
.text(function (d) {
return d.key;
})
.attr("y", function (d) { return y(d.state) + y.bandwidth() / 2; })
.attr("x", function (d) {
return x(d.p[1]);
});
Updated Plunker.
Continuing to try and master the enter-update-exit pattern...
I've got a relatively simple reusable d3.js chart, and I want to be able to update the chart between two data sets. I'm getting close, but the chart is not updating properly.
You can see a fiddle here: http://jsfiddle.net/rolfsf/vba6n4sh/2/
Where did I mess up the enter-update-exit pattern?
The chart code looks like this:
function relativeSizeChart() {
var width = 1200,
margin = 0,
padding = 16,
r = d3.scale.linear(),
onTotalMouseOver = null,
onTotalClick = null,
onClusterMouseOver = null,
onClusterClick = null,
val = function(d){return d;};
totalFormat = function(d){return d;};
clusterFormat = function(d){return d;};
clusterFormat2 = function(d){return d;};
function chart(selection) {
selection.each(function(data) {
//console.log(data);
var clusterCount = data.Clusters.length,
totalColWidth = 0.3*width,
colWidth = (width - totalColWidth)/clusterCount,
height = colWidth + 2*padding,
maxRadius = (colWidth - 10)/2;
var svg = d3.select(this).selectAll("svg")
.data([data]);
var svgEnter = svg
.enter().append("svg")
.attr('class', function(d){
if( onTotalMouseOver !== null || onTotalClick !== null ||onClusterMouseOver !== null || onClusterClick !== null){
return 'clickable';
}else{
return 'static';
}
})
.attr("width", width)
.attr("height", height);
var background, clusterLines;
background = svgEnter.append("g")
.attr('class', 'background');
var headers = svgEnter.append("g")
.attr('class', 'headers')
.selectAll("text.header")
.data(data.Headers, function(d){return d;});
var total = svgEnter.append("g")
.attr('class', 'total');
var cluster = svgEnter.selectAll('g.cluster')
.data(data.Clusters,function(d){ return d;});
var clusterEnter = cluster
.enter().append("g")
.attr('class', 'cluster')
.attr('transform', function (d, i) {
return 'translate(' + (totalColWidth + i*colWidth) + ',0)';
});
var clusters = svg.selectAll('g.cluster');
r = d3.scale.linear()
.domain([0, d3.max(data.Clusters, function(d){return d[1];})])
.range([40, maxRadius]);
background .append("rect")
.attr("class", "chart-bg")
.attr('x', 0)
.attr('y', padding)
.attr('height', (height-padding))
.attr('width', width)
.attr('class', 'chart-bg');
background .append("g")
.attr('class', 'cluster-lines');
background .append("line")
.attr("class", "centerline")
.attr('x1', (totalColWidth - padding))
.attr('x2', width - (colWidth/2))
.attr('y1', (height+padding)/2)
.attr('y2', (height+padding)/2);
clusterLines = background.select('g.cluster-lines')
.selectAll("line")
.data(data.Clusters,function(d){ return d;})
.enter().append('line')
.attr('class', 'cluster-line');
headers .enter().append('text')
.attr('class', 'header');
total .append("rect")
.attr("class", "total-cluster")
.attr('x', padding)
.attr('y', 0.2*(height+(4*padding)))
.attr('height', 0.5*(height))
.attr('width', totalColWidth-(2*padding))
.attr('rx', 4)
.attr('ry', 4)
.on('mouseover', onTotalMouseOver)
.on('click', onTotalClick);
total .append("text")
.attr("class", "total-name")
.attr('x', totalColWidth/2 )
.attr('y', function(d, i) { return ((height+padding)/2) + (padding + 10); });
total .append("text")
.attr("class", "total-value")
.attr('x', totalColWidth/2 )
.attr('y', function(d, i) { return ((height+padding)/2); })
.text(totalFormat(0));
clusterEnter.append('circle')
.attr('class', 'bubble')
.attr('cx', function(d, i) { return colWidth/2; })
.attr('cy', function(d, i) { return (height+padding)/2;})
.attr("r", "50")
.on('mouseover', function(d, i, j) {
if (onClusterMouseOver != null) onClusterMouseOver(d, i, j);
})
.on('mouseout', function() { /*do something*/ })
.on('click', function(d, i){
onClusterClick(this, d, i);
});
clusterEnter.append('text')
.attr('class', 'cluster-value')
.attr('x', function(d, i) { return colWidth/2; })
.attr('y', function(d, i) { return ((height+padding)/2); })
.text(clusterFormat(0));
clusterEnter.append('text')
.attr('class', 'cluster-value-2')
.attr('x', function(d, i) { return colWidth/2; })
.attr('y', function(d, i) { return ((height+padding)/2) + (padding + 10); })
.text(clusterFormat2(0));
//update attributes
clusterLines.attr('x1', function(d, i) { return totalColWidth + i*colWidth })
.attr('x2', function(d, i) { return totalColWidth + i*colWidth })
.attr('y1', function(d, i) { return padding })
.attr('y2', function(d, i) { return (height); });
headers .attr('x', function(d, i) {
if(i === 0){
return (totalColWidth/2);
}else{
return (totalColWidth + (i*colWidth) - (colWidth/2))
}
})
.attr('y', 12);
//clean up old
svg .exit().remove();
cluster .exit().selectAll('circle.bubble')
.style("opacity", 1)
.style("fill", "#DDD")
.style("stroke", "#DDD")
.transition()
.duration(500)
.style("opacity", 0);
cluster .exit().remove();
headers .exit().remove();
function update(data) {
//update with data
svg .selectAll('text.total-value')
.transition()
.delay(400)
.duration(1000)
.tween( 'text', function(d, i) {
var currentValue = +this.textContent.replace(/\D/g,'');
var interpolator = d3.interpolateRound( currentValue, d.Total[1] );
return function( t ) {
this.textContent = totalFormat(interpolator(t));
};
});
svg .selectAll('text.total-name')
.text(val(data.Total[0]));
svg .selectAll('circle')
.attr('class', function(d, i) {
if(d[1] === 0){ return 'bubble empty';}
else {return 'bubble';}
})
.transition()
.duration(1000)
.delay(function(d, i) { return 500 + (i * 100); })
.ease('elastic')
.attr("r", function (d, i) { return r(d[1]); });
svg .selectAll('text.cluster-value')
.transition()
.delay(function(d, i) { return 500 + (i * 100); })
.duration(1000)
.tween( 'text', function(d, i) {
var currentValue = +this.textContent.replace(/\D/g,'');
var interpolator = d3.interpolateRound( currentValue, d[1] );
return function( t ) {
this.textContent = clusterFormat(interpolator(t));
};
});
svg .selectAll('text.cluster-value-2')
.transition()
.delay(function(d, i) { return 500 + (i * 100); })
.duration(1000)
.tween( 'text', function(d, i) {
var currentValue = +this.textContent.replace(/\D/g,'');
var interpolator = d3.interpolateRound( currentValue, d[0] );
return function( t ) {
this.textContent = clusterFormat2(interpolator(t));
};
});
headers .text(function(d, i){return d});
}
update(data);
});
}
chart.totalFormat = function(_) {
if (!arguments.length) return totalFormat;
totalFormat = _;
return chart;
};
chart.clusterFormat = function(_) {
if (!arguments.length) return clusterFormat;
clusterFormat = _;
return chart;
};
chart.clusterFormat2 = function(_) {
if (!arguments.length) return clusterFormat2;
clusterFormat2 = _;
return chart;
};
chart.width = function(_) {
if (!arguments.length) return width;
width = _;
return chart;
};
chart.onTotalClick = function(_) {
if (!arguments.length) return onTotalClick;
onTotalClick = _;
return chart;
};
chart.onTotalMouseOver = function(_) {
if (!arguments.length) return onTotalMouseOver;
onTotalMouseOver = _;
return chart;
};
chart.onClusterClick = function(_) {
if (!arguments.length) return onClusterClick;
onClusterClick = _;
return chart;
};
chart.onClusterMouseOver = function(_) {
if (!arguments.length) return onClusterMouseOver;
onClusterMouseOver = _;
return chart;
};
return chart;
}
my sample data looks like this
var data = {
"data1": {
Headers: ["Total", "Col 1A", "Col 2A", "Col 3A", "Col 4A"],
Total: ["Total # of Widgets", 1200],
Clusters: [
[100, 1200],
[67, 800],
[42, 500],
[17, 198]
]
},
"data2": {
Headers: ["Total", "Col 1B", "Col 2B", "Col 3B", "Col 4B"],
Total: ["Total # of Widgets", 1200],
Clusters: [
[20, 245],
[31, 371],
[32, 386],
[12, 145]
]
}
}
Thanks!!
There are a couple of issues with your enter/update/exit pattern:
background = svgEnter.append("g")
.attr('class', 'background');
var headers = svgEnter.append("g")
.attr('class', 'headers')
.selectAll("text.header")
.data(data.Headers, function(d){return d;});
var total = svgEnter.append("g")
.attr('class', 'total');
This code is referencing svgEnter, which is OK the first time through, as svgEnter has a non-empty selection (it contains the svg you create a little earlier).
On subsequent calls to this function, svgEnter will contain an empty selection, as the svg element already exists. So, I have modified this part of your code to:
svgEnter.append('g')
.attr('class', 'background');
var background = svg.selectAll('g.background');
svgEnter.append('g')
.attr('class', 'headers')
var headers = svg.selectAll('g.headers').selectAll('text.header')
.data(data.Headers, function(d) { return d; });
svgEnter.append('g')
.attr('class', 'total');
var total = svg.selectAll('g.total');
This will create the g elements if we also have to create the svg element. It will then create the variables similar to your existing code using selections from the svg element.
I think that they're the only changes I made, the rest of your code works as expected.
An updated fiddle is at http://jsfiddle.net/vba6n4sh/9/
I am new to D3 and I am trying to create some charts based on weather (temperature) data. I have created a separate js file with my code so that all the charts are called from one HTML page. However, the x-axis is very cluttered because I have daily data displayed.
Here is the main js file - how do I make the x-axis rotate and insert space between the days to display? Or alternatively, just show each month (April, May, June, etc.) rather than each day?
(function (){
var VIZ = {};
var margin = {top: 20, right: 55, bottom: 50, left: 20},
width = 1000 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%d/%m/%Y").parse;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.rangeRound([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var color = d3.scale.ordinal()
.range(['blue', 'red', 'green','yellow']);
var svg = d3.select("#chart").append("svg")
.attr("id", "thesvg")
.attr("viewBox", "0 0 1000 500")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
function make_x_axis() {
return d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(20)
}
function make_y_axis() {
return d3.svg.axis()
.scale(y)
.orient("left")
.ticks(20)
}
VIZ.stackChart = function (data, offset) {
var stack = d3.layout.stack()
.values(function (d) { return d.values; })
.x(function (d) { return x(d.label) + x.rangeBand() / 2; })
.y(function (d) { return d.value; });
var area = d3.svg.area()
.interpolate("cardinal")
.x(function (d) { return x(d.label) + x.rangeBand() / 2; })
.y0(function (d) { return y(d.y0); })
.y1(function (d) { return y(d.y0 + d.y); });
var labelVar = 'date';
var varNames = d3.keys(data[0])
.filter(function (key) { return key !== labelVar;});
color.domain(varNames);
var seriesArr = [], series = {};
varNames.forEach(function (name) {
series[name] = {name: name, values:[]};
seriesArr.push(series[name]);
});
data.forEach(function (d) {
varNames.map(function (name) {
series[name].values.push({name: name, label: d[labelVar], value: +d[name]});
});
});
x.domain(data.map(function (d) { return d.date; }));
stack.offset(offset)
stack(seriesArr);
y.domain([0, d3.max(seriesArr, function (c) {
return d3.max(c.values, function (d) { return d.y0 + d.y; });
})]);
var selection = svg.selectAll(".series")
.data(seriesArr)
.enter().append("g")
.attr("class", "series");
selection.append("path")
.attr("class", "streamPath")
.attr("d", function (d) { return area(d.values); })
.style("fill", function (d) { return color(d.name); })
.style("stroke", "grey");
var points = svg.selectAll(".seriesPoints")
.data(seriesArr)
.enter().append("g")
.attr("class", "seriesPoints");
points.selectAll(".point")
.data(function (d) { return d.values; })
.enter().append("circle")
.attr("class", "point")
.attr("cx", function (d) { return x(d.label) + x.rangeBand() / 2; })
.attr("cy", function (d) { return y(d.y0 + d.y); })
.attr("r", "10px")
.style("fill",function (d) { return color(d.name); })
.on("mouseover", function (d) { showPopover.call(this, d); })
.on("mouseout", function (d) { removePopovers(); })
drawAxis();
drawLegend(varNames);
}
VIZ.lineChart = function (data) {
var line = d3.svg.line()
.interpolate("linear")
.x(function (d) { return x(d.label) + x.rangeBand() / 2; })
.y(function (d) { return y(d.value); });
var labelVar = 'date';
var varNames = d3.keys(data[0]).filter(function (key) { return key !== labelVar;});
color.domain(varNames);
var seriesData = varNames.map(function (name) {
return {
name: name,
values: data.map(function (d) {
return {name: name, label: d[labelVar], value: +d[name]};
})
};
});
x.domain(data.map(function (d) { return d.date; }));
y.domain([
d3.min(seriesData, function (c) {
return d3.min(c.values, function (d) { return d.value; });
}),
d3.max(seriesData, function (c) {
return d3.max(c.values, function (d) { return d.value; });
})
]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
var series = svg.selectAll(".series")
.data(seriesData)
.enter().append("g")
.attr("class", "series");
series.append("path")
.attr("class", "line")
.attr("d", function (d) { return line(d.values); })
.style("stroke", function (d) { return color(d.name); })
.style("stroke-width", "4px")
.style("fill", "none")
series.selectAll(".linePoint")
.data(function (d) { return d.values; })
.enter().append("circle")
.attr("class", "linePoint")
.attr("cx", function (d) { return x(d.label) + x.rangeBand()/2; })
.attr("cy", function (d) { return y(d.value); })
.attr("r", "5px")
.style("fill", function (d) { return color(d.name); })
.style("stroke", "grey")
.style("stroke-width", "1px")
.on("mouseover", function (d) { showPopover.call(this, d); })
.on("mouseout", function (d) { removePopovers(); })
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + height + ")")
.call(make_x_axis()
.tickSize(-height, 0, 0)
.tickFormat("")
)
svg.append("g")
.attr("class", "grid")
.call(make_y_axis()
.tickSize(-width, 0, 0)
.tickFormat("")
)
drawAxis();
drawLegend(varNames);
}
VIZ.stackBarChart = function (data) {
var labelVar = 'date';
var varNames = d3.keys(data[0]).filter(function (key) { return key !== labelVar;});
color.domain(varNames);
data.forEach(function (d) {
var y0 = 0;
d.mapping = varNames.map(function (name) {
return {
name: name,
label: d[labelVar],
y0: y0,
y1: y0 += +d[name]
};
});
d.total = d.mapping[d.mapping.length - 1].y1;
});
x.domain(data.map(function (d) { return d.date; }));
y.domain([0, d3.max(data, function (d) { return d.total; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", function(d) {
return "rotate(-65)"
});
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("");
var selection = svg.selectAll(".series")
.data(data)
.enter().append("g")
.attr("class", "series")
.attr("transform", function (d) { return "translate(" + x(d.date) + ",0)"; });
selection.selectAll("rect")
.data(function (d) { return d.mapping; })
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function (d) { return y(d.y1); })
.attr("height", function (d) { return y(d.y0) - y(d.y1); })
.style("fill", function (d) { return color(d.name); })
.style("stroke", "grey")
.on("mouseover", function (d) { showPopover.call(this, d); })
.on("mouseout", function (d) { removePopovers(); })
drawAxis();
drawLegend(varNames);
}
VIZ.clearAll = function () {
svg.selectAll("*").remove()
}
function drawAxis() {
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Maximum Temperature");
}
function drawLegend (varNames) {
var legend = svg.selectAll(".legend")
.data(varNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function (d, i) { return "translate(55," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 10)
.attr("width", 10)
.attr("height", 10)
.style("fill", color)
.style("stroke", "grey");
legend.append("text")
.attr("x", width - 12)
.attr("y", 6)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function (d) { return d; });
}
function removePopovers () {
$('.popover').each(function() {
$(this).remove();
});
}
function showPopover (d) {
$(this).popover({
title: d.name,
placement: 'auto top',
container: 'body',
trigger: 'manual',
html : true,
content: function() {
return "Date: " + d.label +
"<br/>Temperature: " + d3.format(",")(d.value ? d.value: d.y1 - d.y0); }
});
$(this).popover('show')
}
VIZ.onResize = function () {
var aspect = 1000 / 500, chart = $("#thesvg");
var targetWidth = chart.parent().width();
chart.attr("width", targetWidth);
chart.attr("height", targetWidth / aspect);
}
window.VIZ = VIZ;
}())
You can rotate texts in x axis using transform attribute.
svg.select(".x.axis")
.selectAll("text")
.attr("transform"," translate(0,15) rotate(-65)"); // To rotate the texts on x axis. Translate y position a little bit to prevent overlapping on axis line.
.style("font-size","15px"); //To change the font size of texts
Use tickformat function of d3 scale for date formatting. To display only month from date, you can use the code below.
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom')
.tickFormat(d3.time.format('%B'));
Update:
I think, it would be better if you reduce the number of ticks using ticks method to make texts readable.
xAxis.ticks(d3.time.day, 20);
You can refer more about d3 svg axis from here and about time formats from here
I guess things must have changed since the original answer was posted in 2014. In any case, in D3.js versions 5 and up, the mentioned selection code no longer works.
Since the structure of the tick is now a group, for example:
<g class="tick" opacity="1" transform="translate(0.5,0)">
<line stroke="currentColor" y2="6"></line>
<text fill="currentColor" y="9" dy="0.71em">0.0</text>
</g>
...
The selection code needs to be modified like this:
svg.select(".x.axis")
.selectAll(".tick text")
...