Related
I have created a D3 donut chart by using a code on codepen but I am unable to add the labels to it. I want the labels to be added on the side of every portion. For creating this donut chart I have used D3.js:
This is the code I have used:
<script type="text/javascript">
var dataset = [
{ name: 'Savings', count: 3250 },
{ name: 'Housing', count: 1707 },
{ name: 'Transportation', count: 377 },
{ name: 'Misc', count: 365 },
{ name: 'Insurance', count: 314 },
{ name: 'Utilities', count: 294 },
{ name: 'Student Loans', count: 262 },
{ name: 'Food', count: 250 },
{ name: 'Phone', count: 10 },
];
var total=0;
dataset.forEach(function(d){
total+= d.count;
});
var pie=d3.layout.pie()
.value(function(d){return d.count})
.sort(null);
var data_ready = pie(d3.entries(dataset))
var w=300,h=300;
var outerRadiusArc=w/2;
var innerRadiusArc=100;
var shadowWidth=10;
var outerRadiusArcShadow=innerRadiusArc+1;
var innerRadiusArcShadow=innerRadiusArc-shadowWidth;
var color = d3.scale.ordinal()
.range(['#f5e232', '#64eb34' , '#2d72e0', '#e3251b', '#d61be3', '#f0b00e', '#0ef0c3', '#e61240', '#db12e6']).domain(["Saving", "Housing", "Transportayion", "Misc", "Insurance", "Utilities", "Student Loan", "Food", "Phone"])
;
var svg=d3.select("#chart")
.append("svg")
.attr({
width:w,
height:h,
class:'shadow'
}).append('g')
.attr({
transform:'translate('+w/2+','+h/2+')'
});
var createChart=function(svg,outerRadius,innerRadius,fillFunction,className){
var arc=d3.svg.arc()
.innerRadius(outerRadius)
.outerRadius(innerRadius);
var path=svg.selectAll('.'+className)
.data(pie(dataset))
.enter()
.append('path')
.attr({
class:className,
d:arc,
fill:fillFunction
});
path.transition()
.duration(1000)
.attrTween('d', function(d) {
var interpolate = d3.interpolate({startAngle: 0, endAngle: 0}, d);
return function(t) {
return arc(interpolate(t));
};
});
};
createChart(svg,outerRadiusArc,innerRadiusArc,function(d,i){
return color(d.data.name);
},'path1');
createChart(svg,outerRadiusArcShadow,innerRadiusArcShadow,function(d,i){
var c=d3.hsl(color(d.data.name));
return d3.hsl((c.h+5), (c.s -.07), (c.l -.15));
},'path2');
var addText= function (text,y,size) {
svg.append('text')
.text(text)
.attr({
'text-anchor':'middle',
y:y
})
.style({
fill:'black',
'font-size':size,
});
};
var addTexttwo= function (text,x,y,size) {
svg.append('text')
.text(text)
.attr({
'text-anchor':'middle',
y:y,
x:x,
})
.style({
fill:'white',
'font-size':size,
});
};
var restOfTheData=function(){
addText(function(){
return "$6,830";
},40,'30px');
addText(function(){
return "Shine's";
},-20, '20px');
addText(function(){
return "Monthly Budget";
},0, '20px');
};
setTimeout(restOfTheData,1000);
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
</script>
I want the result to look somewhat like this. With the labels on the side of the donut
This code puts the labels around the pie:
const textRadius = d => outerRadiusArc + (d.data.count < 50 ? 15 : 5);
const textX = d => textRadius(d) * Math.sin((d.startAngle + d.endAngle) / 2);
const textY = d => textRadius(d) * -Math.cos((d.startAngle + d.endAngle) / 2);
const textAnchor = d => (d.startAngle + d.endAngle) / 2 > Math.PI ? 'end' : 'start';
svg.selectAll('text.label')
.data(pie(dataset))
.enter()
.append('text')
.classed('label', true)
.text(d => d.data.name)
.attr('x', textX)
.attr('y', textY)
.attr('text-anchor', textAnchor)
.attr('alignment-baseline', 'middle')
var dataset = [
{ name: 'Savings', count: 3250 },
{ name: 'Housing', count: 1707 },
{ name: 'Transportation', count: 377 },
{ name: 'Misc', count: 365 },
{ name: 'Insurance', count: 314 },
{ name: 'Utilities', count: 294 },
{ name: 'Student Loans', count: 262 },
{ name: 'Food', count: 250 },
{ name: 'Phone', count: 10 },
];
var total=0;
dataset.forEach(function(d){
total+= d.count;
});
var pie=d3.layout.pie()
.value(function(d){return d.count})
.sort(null);
var data_ready = pie(d3.entries(dataset))
var w=400,h=300;
var outerRadiusArc=120;
var innerRadiusArc=90;
var shadowWidth=10;
var outerRadiusArcShadow=innerRadiusArc+1;
var innerRadiusArcShadow=innerRadiusArc-shadowWidth;
var color = d3.scale.ordinal()
.range(['red', '#f5e232', 'orange' , '#2d72e0', '#e3251b', '#d61be3', '#f0b00e', '#0ef0c3', '#e61240', '#db12e6']).domain(["Saving", "Housing", "Transportayion", "Misc", "Insurance", "Utilities", "Student Loan", "Food", "Phone"])
;
var svg = d3.select("#chart")
.append("svg")
.attr({
width:w,
height:h,
class:'shadow'
}).append('g')
.attr({
transform:'translate('+w/2+','+h/2+')'
});
var createChart=function(svg,outerRadius,innerRadius,fillFunction,className){
var arc=d3.svg.arc()
.innerRadius(outerRadius)
.outerRadius(innerRadius);
var path=svg.selectAll('.'+className)
.data(pie(dataset))
.enter()
.append('path')
.attr({
class:className,
d:arc,
fill:fillFunction
});
path.transition()
.duration(1000)
.attrTween('d', function(d) {
var interpolate = d3.interpolate({startAngle: 0, endAngle: 0}, d);
return function(t) {
return arc(interpolate(t));
};
})
.each(d => {
console.log(d);
})
const textRadius = d => outerRadiusArc + (d.data.count < 50 ? 15 : 5);
const textX = d => textRadius(d) * Math.sin((d.startAngle + d.endAngle) / 2);
const textY = d => textRadius(d) * -Math.cos((d.startAngle + d.endAngle) / 2);
const textAnchor = d => (d.startAngle + d.endAngle) / 2 > Math.PI ? 'end' : 'start';
svg.selectAll('text.label')
.data(pie(dataset))
.enter()
.append('text')
.classed('label', true)
.text(d => d.data.name)
.attr('x', textX)
.attr('y', textY)
.attr('text-anchor', textAnchor)
.attr('alignment-baseline', 'middle')
/*
svg.selectAll('circle.point')
.data(pie(dataset))
.enter()
.append('circle')
.classed('point', true)
.attr('r', 3)
.attr('cx', textX)
.attr('cy', textY)
*/
};
createChart(svg,outerRadiusArc,innerRadiusArc,function(d,i){
return color(d.data.name);
},'path1');
createChart(svg,outerRadiusArcShadow,innerRadiusArcShadow,function(d,i){
var c=d3.hsl(color(d.data.name));
return d3.hsl((c.h+5), (c.s -.07), (c.l -.15));
},'path2');
var addText= function (text,y,size) {
svg.append('text')
.text(text)
.attr({
'text-anchor':'middle',
y:y
})
.style({
fill:'black',
'font-size':size,
});
};
var addTexttwo= function (text,x,y,size) {
svg.append('text')
.text(text)
.attr({
'text-anchor':'middle',
y:y,
x:x,
})
.style({
fill:'white',
'font-size':size,
});
};
var restOfTheData=function(){
addText(function(){
return "$6,830";
},40,'30px');
addText(function(){
return "Shine's";
},-20, '20px');
addText(function(){
return "Monthly Budget";
},0, '20px');
};
setTimeout(restOfTheData,1000);
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
.label {
font-family: 'Ubuntu';
font-size: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div id="chart" />
I've been trying to get my head around how to update only those d3 nodes where the data has changed but I still don't have it right. In the little test example below, I am still changing everything it seems.
Am I doing this completely wrong or just a little wrong?
In the example, a click on a shape toggles the shape to be a circle or square and updates a "clickCnt" property. Then it redraws the data. It's sort of working but seems to be redrawing everything. Also clicking on the "red" shape is not working for some reason yet it is the exact same code.
var dataArray = [];
dataArray.push({ "label": 'red', "shape": "circle", "clickCnt": 0, x: 30, y: 100 });
dataArray.push({ "label": 'orange', "shape": "square", "clickCnt": 0, x: 110, y: 100 });
dataArray.push({ "label": 'yellow', "shape": "circle", "clickCnt": 0, x: 210, y: 100 });
dataArray.push({ "label": 'green', "shape": "square", "clickCnt": 0, x: 310, y: 100 });
dataArray.push({ "label": 'blue', "shape": "circle", "clickCnt": 0, x: 30, y: 200 });
dataArray.push({ "label": 'indigo', "shape": "square", "clickCnt": 0, x: 110, y: 200 });
dataArray.push({ "label": 'violet', "shape": "circle", "clickCnt": 0, x: 210, y: 200 });
dataArray.push({ "label": 'white', "shape": "square", "clickCnt": 0, x: 310, y: 200 });
var width = 400;
var height = 400;
d3.select("div#svg-container").select("svg").remove();
var svg = d3.select("#svg-container").append("svg")
.attr("width", width)
.attr("height", height);
var content = svg.append("g")
function create(data) {
var groups = content.selectAll("g")
.data(data, function (d) {
return d;
});
groups.exit().remove();
groups.enter()
.append("g")
.attr('transform', function (d, i) {
return 'translate(' + d.x + ',' + d.y + ')'
})
.each(function (d) {
var e = d3.select(this);
e.append("text")
.classed("small-text", true)
.classed("label", true)
.text(function (d) {
return d.label;
})
.style("fill", function (d) {
return d.label;
});
e.append("text")
.classed("small-text", true)
.classed("clickCnt", true)
.attr("y", 20)
.text(function (d) {
return d.clickCnt;
})
.style("fill", function (d) {
return d.label;
})
if (d.shape == "circle") {
e.append("circle")
.attr("class", "circle")
.attr("r", 15)
.attr("cx", 10)
.attr("cy", -40)
.on("click", iconClicked)
.style("cursor", "pointer");
} else if (d.shape == "square") {
e.append("rect")
.attr("class", "square")
.attr("width", 30)
.attr("height", 30)
.attr("x", 0)
.attr("y", -55)
.on("click", iconClicked)
.style("cursor", "pointer");
}
});
}
create(dataArray);
function iconClicked(evt) {
if (evt.shape == "circle") {
evt.shape = "square"
} else if (evt.shape == "square") {
evt.shape = "circle"
}
evt.clickCnt++;
document.getElementById('output').innerHTML = "item clicked: " + evt.label + " " + evt.clickCnt;
create(dataArray);
}
.circle {
stroke: red;
stroke-width: 2px;
}
.square {
stroke: blue;
stroke-width: 2px;
}
#timeline-background {
background: slategray;
}
.label {
fill: blue;
}
.small-text {
font-size: 16px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<body>
<label id="output">out</label>
<div id="timeline-background" style="width: 100%; height: 100%;">
<div id="svg-container"></div>
</div>
</body>
Your problem here is the key function when you bind the data.
If you look at the documentation, you'll see that:
A key function may be specified to control which datum is assigned to which element, replacing the default join-by-index, by computing a string identifier for each datum and element. (emphasis mine)
However, in your case, instead of using a string, you're returning a whole object:
var groups = content.selectAll("g")
.data(data, function (d) {
return d;
// ^--- this is an object
});
And, of course, this won't work.
Because of that, we have the behaviour you described: your exit selection contains all groups and they are all removed. Then, the enter selection contains all elements, and they are all painted again.
Let's see it, click the elements and have a look at the console:
var dataArray = [];
dataArray.push({ "label": 'red', "shape": "circle", "clickCnt": 0, x: 30, y: 100 });
dataArray.push({ "label": 'orange', "shape": "square", "clickCnt": 0, x: 110, y: 100 });
dataArray.push({ "label": 'yellow', "shape": "circle", "clickCnt": 0, x: 210, y: 100 });
dataArray.push({ "label": 'green', "shape": "square", "clickCnt": 0, x: 310, y: 100 });
dataArray.push({ "label": 'blue', "shape": "circle", "clickCnt": 0, x: 30, y: 200 });
dataArray.push({ "label": 'indigo', "shape": "square", "clickCnt": 0, x: 110, y: 200 });
dataArray.push({ "label": 'violet', "shape": "circle", "clickCnt": 0, x: 210, y: 200 });
dataArray.push({ "label": 'white', "shape": "square", "clickCnt": 0, x: 310, y: 200 });
var width = 400;
var height = 400;
d3.select("div#svg-container").select("svg").remove();
var svg = d3.select("#svg-container").append("svg")
.attr("width", width)
.attr("height", height);
var content = svg.append("g")
function create(data) {
var groups = content.selectAll("g")
.data(data, function (d) {
return d;
});
console.log("The exit selection size is: " + groups.exit().size())
groups.exit().remove();
groups.enter()
.append("g")
.attr('transform', function (d, i) {
return 'translate(' + d.x + ',' + d.y + ')'
})
.each(function (d) {
var e = d3.select(this);
e.append("text")
.classed("small-text", true)
.classed("label", true)
.text(function (d) {
return d.label;
})
.style("fill", function (d) {
return d.label;
});
e.append("text")
.classed("small-text", true)
.classed("clickCnt", true)
.attr("y", 20)
.text(function (d) {
return d.clickCnt;
})
.style("fill", function (d) {
return d.label;
})
if (d.shape == "circle") {
e.append("circle")
.attr("class", "circle")
.attr("r", 15)
.attr("cx", 10)
.attr("cy", -40)
.on("click", iconClicked)
.style("cursor", "pointer");
} else if (d.shape == "square") {
e.append("rect")
.attr("class", "square")
.attr("width", 30)
.attr("height", 30)
.attr("x", 0)
.attr("y", -55)
.on("click", iconClicked)
.style("cursor", "pointer");
}
});
}
create(dataArray);
function iconClicked(evt) {
if (evt.shape == "circle") {
evt.shape = "square"
} else if (evt.shape == "square") {
evt.shape = "circle"
}
evt.clickCnt++;
document.getElementById('output').innerHTML = "item clicked: " + evt.label + " " + evt.clickCnt;
create(dataArray);
}
.as-console-wrapper { max-height: 20% !important;}
.circle {
stroke: red;
stroke-width: 2px;
}
.square {
stroke: blue;
stroke-width: 2px;
}
#timeline-background {
background: slategray;
}
.label {
fill: blue;
}
.small-text {
font-size: 16px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<body>
<label id="output">out</label>
<div id="timeline-background" style="width: 100%; height: 100%;">
<div id="svg-container"></div>
</div>
</body>
(partial) Solution: Use a unique string as the returned value, such as the label property:
var groups = content.selectAll("g")
.data(data, function (d) {
return d.label;
});
Have a look:
var dataArray = [];
dataArray.push({ "label": 'red', "shape": "circle", "clickCnt": 0, x: 30, y: 100 });
dataArray.push({ "label": 'orange', "shape": "square", "clickCnt": 0, x: 110, y: 100 });
dataArray.push({ "label": 'yellow', "shape": "circle", "clickCnt": 0, x: 210, y: 100 });
dataArray.push({ "label": 'green', "shape": "square", "clickCnt": 0, x: 310, y: 100 });
dataArray.push({ "label": 'blue', "shape": "circle", "clickCnt": 0, x: 30, y: 200 });
dataArray.push({ "label": 'indigo', "shape": "square", "clickCnt": 0, x: 110, y: 200 });
dataArray.push({ "label": 'violet', "shape": "circle", "clickCnt": 0, x: 210, y: 200 });
dataArray.push({ "label": 'white', "shape": "square", "clickCnt": 0, x: 310, y: 200 });
var width = 400;
var height = 400;
d3.select("div#svg-container").select("svg").remove();
var svg = d3.select("#svg-container").append("svg")
.attr("width", width)
.attr("height", height);
var content = svg.append("g")
function create(data) {
var groups = content.selectAll("g")
.data(data, function (d) {
return d.label;
});
console.log("The exit selection size is: " + groups.exit().size())
groups.exit().remove();
groups.enter()
.append("g")
.attr('transform', function (d, i) {
return 'translate(' + d.x + ',' + d.y + ')'
})
.each(function (d) {
var e = d3.select(this);
e.append("text")
.classed("small-text", true)
.classed("label", true)
.text(function (d) {
return d.label;
})
.style("fill", function (d) {
return d.label;
});
e.append("text")
.classed("small-text", true)
.classed("clickCnt", true)
.attr("y", 20)
.text(function (d) {
return d.clickCnt;
})
.style("fill", function (d) {
return d.label;
})
if (d.shape == "circle") {
e.append("circle")
.attr("class", "circle")
.attr("r", 15)
.attr("cx", 10)
.attr("cy", -40)
.on("click", iconClicked)
.style("cursor", "pointer");
} else if (d.shape == "square") {
e.append("rect")
.attr("class", "square")
.attr("width", 30)
.attr("height", 30)
.attr("x", 0)
.attr("y", -55)
.on("click", iconClicked)
.style("cursor", "pointer");
}
});
}
create(dataArray);
function iconClicked(evt) {
if (evt.shape == "circle") {
evt.shape = "square"
} else if (evt.shape == "square") {
evt.shape = "circle"
}
evt.clickCnt++;
document.getElementById('output').innerHTML = "item clicked: " + evt.label + " " + evt.clickCnt;
create(dataArray);
}
.as-console-wrapper { max-height: 20% !important;}
.circle {
stroke: red;
stroke-width: 2px;
}
.square {
stroke: blue;
stroke-width: 2px;
}
#timeline-background {
background: slategray;
}
.label {
fill: blue;
}
.small-text {
font-size: 16px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<body>
<label id="output">out</label>
<div id="timeline-background" style="width: 100%; height: 100%;">
<div id="svg-container"></div>
</div>
</body>
Now, as you can see, the exit selection's size is always zero.
However, I wrote partial for a reason: you're not changing the elements anymore! The explanation is that you don't have a proper update selection. Since the exit selection has no element anymore, the size of the enter selection is also zero. Nothing is updated here.
Creating such update selection is beyond the scope of this answer and I'll leave that work to you.
I have been struggling to get this chart work. I tried so many methods. It didn't show any error but doesn't draw the chart properly.
I want to use strings as x axis domain. I had even tried to set domain for x-scale. Still no use. The code which I have tried so far:
$(".wrapper").delay(600).fadeIn(500);
var barDataset = [{
"name": "aA",
"date": 1,
"successful": 1,
"unsuccessful": 2
},
{
"name": "bB",
"date": 2,
"successful": 41,
"unsuccessful": 8
},
{
"name": "cC",
"date": 3,
"successful": 44,
"unsuccessful": 4
},
{
"name": "dD",
"date": 4,
"successful": 2,
"unsuccessful": 5
},
{
"name": "eE",
"date": 5,
"successful": 21,
"unsuccessful": 1
},
{
"name": "fF",
"date": 6,
"successful": 14,
"unsuccessful": 6
},
{
"name": "gG",
"date": 7,
"successful": 42,
"unsuccessful": 1
},
{
"name": "hH",
"date": 8,
"successful": 10,
"unsuccessful": 1
},
{
"name": "iI",
"date": 9,
"successful": 24,
"unsuccessful": 10
},
{
"name": "jJ",
"date": 10,
"successful": 23,
"unsuccessful": 6
},
{
"name": "kK",
"date": 11,
"successful": 21,
"unsuccessful": 15
},
{
"name": "lL",
"date": 12,
"successful": 28,
"unsuccessful": 15
}
]
function drawBarGraph(data) {
var status = ["successful", "unsuccessful"];
var colors = [
["Successful", "#50E3C2"],
["Unsuccessful", "#EF5C6E"]
];
var margin = {
top: 30,
right: 30,
bottom: 40,
left: 60
},
width = 860 - margin.left - margin.right,
height = 290 - margin.top - margin.bottom;
var z = d3.scale.ordinal()
.range(["#50E3C2", "#EF5C6E"]);
/* var n = 12;
var x = d3.scale.linear()
.domain([1, n - 1])
.rangeRound([0, width], .1);*/
var x = d3.scale.ordinal()
.domain(data.map(function(d) {
return d['name'];
}))
.rangeRoundBands([0, width], .5);
var y = d3.scale.linear()
.rangeRound([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
/*.tickFormat(d3.format("d"))
.ticks(30)*/
;
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.tickFormat(d3.format("d"));
var svg = d3.select("#chart-bar")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var layers = d3.layout.stack()
(status.map(function(c) {
return data.map(function(d) {
return {
x: d.date,
y: d[c]
};
});
}));
y.domain([
0, d3.max(layers[layers.length - 1], function(d) {
return d.y0 + d.y;
})
]);
// gridlines in y axis function
function make_y_gridlines() {
return d3.svg.axis().scale(y)
.orient("left").ticks(5);
}
// add the Y gridlines
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines()
.tickSize(-width)
.tickFormat("")
);
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(6," + height + ")")
.call(xAxis)
.append("text")
.attr("transform", "translate(364,0)")
.attr("y", "3em")
.style("text-anchor", "middle")
.text("Days");
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("x", "-5em")
.attr("y", "-2.5em")
.style("text-anchor", "end")
.text("Number of calls sent");
function type(d) {
// d.date = parseDate(d.date);
d.date;
status.forEach(function(c) {
d[c] = +d[c];
});
return d;
}
var tooltip = d3.select("#chart-bar").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) {
return z(i);
});
layer.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.on("mouseover", function(d) {
tooltip.transition()
.duration(200)
.style("opacity", 1);
tooltip.html("<span>" + d.y + " calls" + "</span>")
.style("left", (d3.event.pageX - 25) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
})
.attr("x", function(d) {
return x(d.x);
})
.attr("y", function(d) {
return height;
})
.attr("width", 12)
.attr("height", 0)
.transition().duration(1500)
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
});
}
drawBarGraph(barDataset);
$('.count').each(function() {
$(this).prop('Counter', 0).animate({
Counter: $(this).text()
}, {
duration: 1500,
easing: 'swing',
step: function(now) {
$(this).text(Math.ceil(now));
}
});
});
#import url("https://fonts.googleapis.com/css?family=Overpass");
* {
box-sizing: border-box;
font: normal 14px/1.5 "Overpass", sans-serif;
}
body {
background: #76daff;
}
.chart-wrapper {
background: #333B66;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
clear: both;
padding: 20px 0 10px;
}
text {
font-size: 12px;
fill: #A7B2EB;
}
.axis path,
.axis line,
.gridline line {
fill: none;
stroke: #fff;
opacity: 0.1;
shape-rendering: crispEdges;
}
.line {
stroke: #17EAD9;
stroke-width: 3px;
fill: none;
}
path.domain {
fill: none;
opacity: 0.1;
}
div.tooltip {
color: #4a4a4a;
position: absolute;
text-align: center;
padding: 3px 6px;
font-size: 12px;
background: #fff;
border-radius: 4px;
pointer-events: none;
}
.tick text {
font-size: 10px;
}
.vbroadcast-legend {
float: right;
margin-right: 40px;
margin-top: 16px;
}
.vbroadcast-legend li {
color: #fff;
font-size: 13px;
float: left;
list-style: none;
margin-left: 20px;
padding-left: 18px;
position: relative;
}
.vbroadcast-legend li:before {
content: "";
height: 12px;
left: 0;
position: absolute;
top: 3px;
width: 12px;
}
.vbroadcast-legend li.successful:before {
background: #50e3c2;
}
.vbroadcast-legend li.unsuccessful:before {
background: #ef5c6e;
}
<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>
<div id="chart-bar" class="chart-wrapper"></div>
Kindly help me in fixing it. Thanks in advance.
Thanks for getting back with the data. It's a minor fix in your code.
You're using name for drawing the X axis but it's not being mapped while being assigned to the layers array.
Relevant changes:
Changed d.date to d.name
return data.map(function(d) {
return {
x: d.name,
y: d[c]
};
});
Assigning x to the rects using this d.x and axes rangeBand to align it with the ticks.
.attr("x", function(d) {
return x(d.x) + x.rangeBand()/2;
})
Snippet:
$(".wrapper").delay(600).fadeIn(500);
var barDataset = [{
"name": "aA",
"date": 1,
"successful": 1,
"unsuccessful": 2
},
{
"name": "bB",
"date": 2,
"successful": 41,
"unsuccessful": 8
},
{
"name": "cC",
"date": 3,
"successful": 44,
"unsuccessful": 4
},
{
"name": "dD",
"date": 4,
"successful": 2,
"unsuccessful": 5
},
{
"name": "eE",
"date": 5,
"successful": 21,
"unsuccessful": 1
},
{
"name": "fF",
"date": 6,
"successful": 14,
"unsuccessful": 6
},
{
"name": "gG",
"date": 7,
"successful": 42,
"unsuccessful": 1
},
{
"name": "hH",
"date": 8,
"successful": 10,
"unsuccessful": 1
},
{
"name": "iI",
"date": 9,
"successful": 24,
"unsuccessful": 10
},
{
"name": "jJ",
"date": 10,
"successful": 23,
"unsuccessful": 6
},
{
"name": "kK",
"date": 11,
"successful": 21,
"unsuccessful": 15
},
{
"name": "lL",
"date": 12,
"successful": 28,
"unsuccessful": 15
}
]
function drawBarGraph(data) {
var status = ["successful", "unsuccessful"];
var colors = [
["Successful", "#50E3C2"],
["Unsuccessful", "#EF5C6E"]
];
var margin = {
top: 30,
right: 30,
bottom: 40,
left: 60
},
width = 860 - margin.left - margin.right,
height = 290 - margin.top - margin.bottom;
var z = d3.scale.ordinal()
.range(["#50E3C2", "#EF5C6E"]);
/* var n = 12;
var x = d3.scale.linear()
.domain([1, n - 1])
.rangeRound([0, width], .1);*/
var x = d3.scale.ordinal()
.domain(data.map(function(d) {
return d['name'];
}))
.rangeRoundBands([0, width], .5);
var y = d3.scale.linear()
.rangeRound([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
/*.tickFormat(d3.format("d"))
.ticks(30)*/
;
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.tickFormat(d3.format("d"));
var svg = d3.select("#chart-bar")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var layers = d3.layout.stack()
(status.map(function(c) {
return data.map(function(d) {
return {
x: d.name,
y: d[c],
};
});
}));
y.domain([
0, d3.max(layers[layers.length - 1], function(d) {
return d.y0 + d.y;
})
]);
// gridlines in y axis function
function make_y_gridlines() {
return d3.svg.axis().scale(y)
.orient("left").ticks(5);
}
// add the Y gridlines
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines()
.tickSize(-width)
.tickFormat("")
);
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(6," + height + ")")
.call(xAxis)
.append("text")
.attr("transform", "translate(364,0)")
.attr("y", "3em")
.style("text-anchor", "middle")
.text("Days");
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("x", "-5em")
.attr("y", "-2.5em")
.style("text-anchor", "end")
.text("Number of calls sent");
function type(d) {
// d.date = parseDate(d.date);
d.date;
status.forEach(function(c) {
d[c] = +d[c];
});
return d;
}
var tooltip = d3.select("#chart-bar").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) {
return z(i);
});
layer.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.on("mouseover", function(d) {
tooltip.transition()
.duration(200)
.style("opacity", 1);
tooltip.html("<span>" + d.y + " calls" + "</span>")
.style("left", (d3.event.pageX - 25) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
})
.attr("x", function(d) {
return x(d.x) + x.rangeBand()/2;
})
.attr("y", function(d) {
return height;
})
.attr("width", 12)
.attr("height", 0)
.transition().duration(1500)
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
});
}
drawBarGraph(barDataset);
$('.count').each(function() {
$(this).prop('Counter', 0).animate({
Counter: $(this).text()
}, {
duration: 1500,
easing: 'swing',
step: function(now) {
$(this).text(Math.ceil(now));
}
});
});
#import url("https://fonts.googleapis.com/css?family=Overpass");
* {
box-sizing: border-box;
font: normal 14px/1.5 "Overpass", sans-serif;
}
body {
background: #76daff;
}
.chart-wrapper {
background: #333B66;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
clear: both;
padding: 20px 0 10px;
}
text {
font-size: 12px;
fill: #A7B2EB;
}
.axis path,
.axis line,
.gridline line {
fill: none;
stroke: #fff;
opacity: 0.1;
shape-rendering: crispEdges;
}
.line {
stroke: #17EAD9;
stroke-width: 3px;
fill: none;
}
path.domain {
fill: none;
opacity: 0.1;
}
div.tooltip {
color: #4a4a4a;
position: absolute;
text-align: center;
padding: 3px 6px;
font-size: 12px;
background: #fff;
border-radius: 4px;
pointer-events: none;
}
.tick text {
font-size: 10px;
}
.vbroadcast-legend {
float: right;
margin-right: 40px;
margin-top: 16px;
}
.vbroadcast-legend li {
color: #fff;
font-size: 13px;
float: left;
list-style: none;
margin-left: 20px;
padding-left: 18px;
position: relative;
}
.vbroadcast-legend li:before {
content: "";
height: 12px;
left: 0;
position: absolute;
top: 3px;
width: 12px;
}
.vbroadcast-legend li.successful:before {
background: #50e3c2;
}
.vbroadcast-legend li.unsuccessful:before {
background: #ef5c6e;
}
<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>
<div id="chart-bar" class="chart-wrapper"></div>
Hope this helps solve the issue.
If you have a list of 'aa' .. 'jj' and you set the domain of x to this list you most likely do not get tick marks like '0-15' and '15-30'.
Extract the x-domain values from the dataset or some other means of extracting the unique values. This is with the small dataset in the question.
var strn = ['aa', 'bb', 'cc', 'dd', 'ee', 'ff', 'gg', 'hh', 'ii', 'jj'];
// ...
//x.domain(strn);
x.domain(data.map(d=>d.DB));
I have circles that is dynamically generated on my view with different sizes (d3.pack()) . Now I added a click event on it so that as it gets clicked it expands. Now, I want to elegantly reset when another circle is clicked? I did my reset similar to this answer D3 - Resetting an SVG object animation
But here's a snippet of my code
var objects= [
{ id: '1477', amounts: 7, color: '#ffd800' },
{ id: '1490', amounts: 10, color: '#b65959' },
{ id: '1300', amounts: 90, color: '#ff006e' },
{ id: '4000', amounts: 50, color: '#ffd800' },
{ id: '9000', amounts: 20, color: '#b20101' },
{ id: '1212', amounts: 28, color: '#ff006e' },
{ id: '2323', amounts: 7, color: '#ffd800' }
]
var width = 700,
height = 800,
color = d3.scale.category20b(),
maxDiameter = 500;
var container = d3.select('.chart')
var svg = container.append('svg')
.attr('width', maxDiameter * 2)
.attr('height', maxDiameter)
.attr('class', 'bubble')
var bubble = d3.layout.pack()
.sort(null)
.size([maxDiameter, maxDiameter])
.value(function (d) { return d.size; })
.padding(1.5)
var nodes = bubble
.nodes(processData(objects))
.filter(function (d) {
return !d.children;
})
var gCircle = svg.append('g')
var circle = gCircle.selectAll('circle')
.data(nodes)
.enter()
.append('circle')
.attr('transform', function (d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
.attr('r', function (d) {
return d.r;
})
.attr('fill', function (d) { return d.color;})
.attr('class', function (d) { return d.className; })
// onclick
circle.on('click', function (e, i) {
d3.select(this).transition()
.attr("x", function (d) {
console.log('d x ' + d.x);
return d.x;
})
.attr("y", function (d) {
console.log('d y ' + d.y);
return d.y;
})
.attr("r", function (d) {
console.log('d r ' + d.r);
return d3.select(this).attr('r') == d.r ? (d.r * 100) : d.r;
})
.duration(500);
});
function processData(data) {
var obj = data;
var newDataSet = [];
for (var l = 0; l < obj.length; l++) {
var objInData= obj[l];
newDataSet.push({ name: objInData.id, className: objInData.id, size: objInData.amounts, color: objInData.color });
}
return { children: newDataSet };
}
Before expanding the clicked circle, set all other circles to the initial size:
circle.transition()
.duration(500)
.attr('r', function (d) {
return d.r;
});
Here is the demo:
var objects= [
{ id: '1477', amounts: 7, color: '#ffd800' },
{ id: '1490', amounts: 10, color: '#b65959' },
{ id: '1300', amounts: 90, color: '#ff006e' },
{ id: '4000', amounts: 50, color: '#ffd800' },
{ id: '9000', amounts: 20, color: '#b20101' },
{ id: '1212', amounts: 28, color: '#ff006e' },
{ id: '2323', amounts: 7, color: '#ffd800' }
]
var width = 500,
height = 400,
color = d3.scale.category20b(),
maxDiameter = 500;
var container = d3.select('body')
var svg = container.append('svg')
.attr('width', maxDiameter * 2)
.attr('height', maxDiameter)
.attr('class', 'bubble')
var bubble = d3.layout.pack()
.sort(null)
.size([maxDiameter, maxDiameter])
.value(function (d) { return d.size; })
.padding(1.5)
var nodes = bubble
.nodes(processData(objects))
.filter(function (d) {
return !d.children;
})
var gCircle = svg.append('g')
var circle = gCircle.selectAll('circle')
.data(nodes)
.enter()
.append('circle')
.attr('transform', function (d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
.attr('r', function (d) {
return d.r;
})
.attr('fill', function (d) { return d.color;})
.attr('class', function (d) { return d.className; })
// onclick
circle.on('click', function (e, i) {
circle.transition().duration(500).attr('r', function (d) {
return d.r;
})
d3.select(this).transition()
.attr("x", function (d) {
return d.x;
})
.attr("y", function (d) {
return d.y;
})
.attr("r", function (d) {
return d3.select(this).attr('r') == d.r ? (d.r * 2) : d.r;
})
.duration(500);
});
function processData(data) {
var obj = data;
var newDataSet = [];
for (var l = 0; l < obj.length; l++) {
var objInData= obj[l];
newDataSet.push({ name: objInData.id, className: objInData.id, size: objInData.amounts, color: objInData.color });
}
return { children: newDataSet };
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
PS: instead of expanding to r*100, in this demo the circles are expanding to just r*2.
This question already has an answer here:
Using d3 to shade area between two lines
(1 answer)
Closed 6 years ago.
I want to fill the lines between two area graphs defined below. I am hitting a bit of a wall -- the issue I seem to have that each path I created does NOT have the other value to compare with, and my efforts to find a work around seem to have hit a bit of a wall.
Any tips?
jQuery(document).ready(function ($) {
var margin = {top: 20, right: 30, bottom: 40, left: 24},
width = 430 - margin.left - margin.right,
height = 225 - margin.top - margin.bottom,
dotRadius = function() { return 3 };
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom')
.tickFormat(d3.time.format('%b'));
var yAxis = d3.svg.axis()
.scale(y)
.orient('left');
// This is a function that determines the colours of the lines drawn, up to 10.
var color = d3.scale.category10();
// This is used to format the time for our data.
var formatTime = d3.time.format("%Y-%m-%d");
var line = d3.svg.line()
.x(function(d) { return x(d.Period); })
.y(function(d) { return y(d.Value); })
var areaBetweenGraphs = d3.svg.area()
.x(function(d) {
console.log('ABG x is: ', d);
return x(formatTime.parse(d.Time));
})
.y0(function(d) {
console.log('ABG y0 is: ', d);
return y(d.Quantity);
})
.y1(function(d) {
console.log('ABG y1 is: ', d);
return y(d.Amount);
});
var svg = d3.select("#pipeline-chart-render")
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
// This separates the data into the lines we want, although the data is stored
// In the same original object.
color.domain(d3.keys(data[0].values[0]).filter(function(key) {
if (key === 'Amount'
|| key === 'Quantity') {
return key
}
}));
// This returns the data into two separate objects which can be graphed.
// In this case, Amount and Quantity.
var datasets = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {
Period: formatTime.parse(d.values[0].Time),
Value: +d.values[0][name]};
})
};
});
console.log('datasets is: ', datasets);
// set the minYDomainValue to zero instead of letting it be a lingering magic number.
var minDomainValue = 0
var minDate = d3.min(datasets, function(d0){
return d3.min(d0.values, function(d1){
return d1.Period;
})
}),
maxDate = d3.max(datasets, function(d0){
return d3.max(d0.values, function(d1){
return d1.Period;
})
});
x.domain([minDate, maxDate]);
y.domain([
minDomainValue,
d3.max(datasets, function(c) { return d3.max(c.values, function(v) { return v.Value; }); })
])
// Append the x-axis class and move axis around.
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
// Append the y-axis class.
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append('g')
var pipeline = svg.selectAll('.pipeline')
.data(datasets);
pipeline.enter()
.append('g')
.attr('class', 'pipeline');
pipeline.append('path')
.attr('class', 'line')
.attr('id', function(d, i) {
return 'pipeline-'+(i+1);
})
.attr('d', function(d) { console.log('line d is: ', d); return line(d.values); })
.attr("data-legend",function(d) { return d.name})
.style("stroke", function(d) { return color(d.name); })
pipeline.exit().remove()
// Rendering the points on the graph.
var points = svg.selectAll('.pipelinePoint')
.data(datasets);
points
.enter()
.append('g')
.attr('class', 'pipelinePoint');
points.selectAll('.point')
.data(function(d) {
return d.values;
})
.enter()
.append('circle')
.attr('circleId', function(d, i) {
return 'circleId-'+i;
})
.attr('cx', function(d) {
return x(d.Period);
})
.attr('cy', function(d) {
return y(d.Value);
})
.attr('r', function(d) {
return dotRadius()
});
});
var data = [
{
key: 1,
values: [
{
Amount: 33,
Quantity: 22,
Time: '2015-01-01'
}
]
},
{
key: 2,
values: [
{
Amount: 52,
Quantity: 20,
Time: '2015-02-01'
}
]
},
{
key: 3,
values: [
{
Amount: 63,
Quantity: 30,
Time: '2015-03-01'
}
]
},
{
key: 4,
values: [
{
Amount: 92,
Quantity: 60,
Time: '2015-04-01'
}
]
},
{
key: 5,
values: [
{
Amount: 50,
Quantity: 29,
Time: '2015-05-01'
}
]
},
{
key: 6,
values: [
{
Amount: 53,
Quantity: 25,
Time: '2015-06-01'
}
]
},
{
key: 7,
values: [
{
Amount: 46,
Quantity: 12,
Time: '2015-07-01'
}
]
},
{
key: 8,
values: [
{
Amount: 52,
Quantity: 15,
Time: '2015-08-01'
}
]
},
{
key: 9,
values: [
{
Amount: 55,
Quantity: 20,
Time: '2015-09-01'
}
]
},
{
key: 10,
values: [
{
Amount: 35,
Quantity: 17,
Time: '2015-10-01'
}
]
},
{
key: 11,
values: [
{
Amount: 80,
Quantity: 45,
Time: '2015-11-01'
}
]
},
{
key: 12,
values: [
{
Amount: 64,
Quantity: 24,
Time: '2015-12-01'
}
]
}
]
CSS if you want it to be a less ugly render:
/* Line Chart CSS */
.axis path,
.axis line {
fill: none;
stroke: #000;
stroke-width: 3px;
shape-rendering: crispEdges;
}
#pipeline-1,
#pipeline-2 {
fill: none;
stroke-width: 1.5px;
stroke-linecap: round;
transition: stroke-width 250ms linear;
-moz-transition: stroke-width 250ms linear;
-webkit-transition: stroke-width 250ms linear;
transition-delay: 250ms
-moz-transition-delay: 250ms;
-webkit-transition-delay: 250ms;
}
.x.axis path {
/* Uncomment below if I want to remove x-axis line */
/* display: none;*/
}
.line.hover path {
stroke-width: 6px;
}
#pipeline-chart-render {
padding-left: -50px;
}
.area {
fill: steelblue;
}
This ended up working.
// The following is for defining the area BETWEEN graphs.
var areaAboveQuantity = d3.svg.area()
.x(line.x())
.y0(line.y())
.y1(0);
var areaBelowQuantity = d3.svg.area()
.x(line.x())
.y0(line.y())
.y1(height);
var areaAboveAmount = d3.svg.area()
.x(line.x())
.y0(line.y())
.y1(0);
var areaBelowAmount = d3.svg.area()
.x(line.x())
.y0(line.y())
.y1(height);
var defs = svg.append('defs');
defs.append('clipPath')
.attr('id', 'clip-quantity')
.append('path')
.datum(datasets)
.attr('d', function(d) {
return areaAboveQuantity(d[1].values);
});
defs.append('clipPath')
.attr('id', 'clip-amount')
.append('path')
.datum(datasets)
.attr('d', function(d) {
return areaAboveAmount(d[0].values);
});
svg.append('path')
.datum(datasets)
.attr('class', 'area')
.attr('d', function(d) {
return areaBelowQuantity(d[1].values)
});
// Quantity IS ABOVE Amount
svg.append('path')
.datum(datasets)
.attr('d', function(d) {
areaBelowQuantity(d[1].values);
})
.attr('clip-path', 'url(#clip-amount)')
.style('fill', 'steelblue')
.style('opacity', '0.2');
// Amount IS ABOVE Quanity
svg.append('path')
.datum(datasets)
.attr('d', function(d) {
return areaBelowAmount(d[0].values);
})
.attr('clip-path', 'url(#clip-quantity)')
.style('fill', 'steelblue')
.style('opacity', '0.2');