d3 area between two line graphs [duplicate] - javascript

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');

Related

Horizontal Bar Chart -- unexpected offsetting of y-axis

I am trying to make a horizontal stacked bar chart, starting with this code snippet, updating to d3 v7. Instead of getting a neatly stacked bar chart, each subsequent bar in a stack is getting offset vertically down from where it should be. When I inspect the yScale value, I get the expected value, so I'm extra-confused about this behavior.
I'd include just the relevant piece of the puzzle, but I honestly don't know where my problem is -- am I appending to the wrong 'g' element? Using enter() on the wrong piece of data?
<script src="https://d3js.org/d3.v7.min.js"></script>
<body>
<div id="bar_chart">
<script>
var data = [{
dep_time: "5:30",
risk: 100,
details: [{
time: 19,
source: 'Drive'
},
{
time: 10,
source: 'Margin'
},
{
time: 42,
source: 'Full'
},
{
time: 35,
source: 'Crossing'
},
{
time: 23,
source: 'Drive'
}
]
},
{
dep_time: "6:20",
risk: 80,
details: [{
time: 25,
source: 'Drive'
},
{
time: 1,
source: 'Margin'
},
{
time: 38,
source: 'Full'
},
{
time: 35,
source: 'Crossing'
},
{
time: 25,
source: 'Drive'
}
]
},
{
dep_time: "7:10",
risk: 5,
details: [{
time: 8,
source: 'Drive'
},
{
time: 28,
source: 'Margin'
},
{
time: 38,
source: 'Full'
},
{
time: 35,
source: 'Crossing'
},
{
time: 18,
source: 'Drive'
}
]
}
];
var chartContainer = '.chart-container';
var units = [];
var xMax = 0;
data.forEach(function(s) {
var total = 0;
s.details.forEach(function(s) {
s["x0"] = total; //Abs left
s["x"] = s.time; //Width
s["x1"] = total + s.time; //Abs right
total = total + s.time;
if (total > xMax) xMax = total;
});
s["y"] = s.dep_time;
units.push(s.dep_time);
});
//Need it to look like: newdata = [(Drive) [19, 25, 32.] Margin [0, 1, 28]. Full [42, 38, 38]. Crossing [35, 35, 35]. Drive [23, 25, 18].]
//So it's a row in the array for each column of data.
//re-arrange the data so it makes more sense to d3 (and less sense to any sane human)
var newdata = [];
for (var i = 0; i < data[0].details.length; i++) {
var row = [];
data.forEach(function(s) {
row.push({
x: s.details[i].x,
y: s.dep_time,
x0: s.details[i].x0
});
});
newdata.push(row);
}
console.log("newdata");
console.log(newdata);
var margins = {
left: 50,
bottom: 50,
top: 25,
right: 25
};
var sizes = {
width: 500,
height: 150
};
var width = sizes.width - margins.left - margins.right;
var height = sizes.height - margins.bottom - margins.top;
var svg = d3.select("#bar_chart")
.append('svg')
.attr('width', width + margins.left + margins.right)
.attr('height', height + margins.bottom)
.append('g')
.attr('transform', 'translate(' + margins.left + ', ' + margins.top + ")");
var yScale = d3.scaleBand()
.domain(units)
.rangeRound([0, height]);
var yAxis = d3.axisLeft(yScale);
var yAxisG = svg.append("g")
.attr("transform", "translate(0,0)")
.attr("id", "yaxis")
.call(yAxis);
const xScale = d3.scaleLinear()
.domain([0, xMax])
.range([0, width]);
var xAxis = d3.axisBottom(xScale);
var xAxisG = svg.append("g")
.attr("transform", "translate(0, " + height + ")")
.attr("id", "xaxis")
.call(xAxis
.ticks(8));
var bar_colors = ['red', 'purple', 'green', 'lightblue', 'yellow'];
var colors = function(i) {
return bar_colors[i];
}
var groups = svg.selectAll('g')
.data(newdata)
//.exit()
.append('g')
.style('fill', function(d, i) {
console.log("d");
console.log(d);
//console.log("i"); console.log(i);
return colors(i);
});
groups.selectAll('rect')
.data(function(d) {
//console.log(d);
return d;
})
.enter()
.append('rect')
.attr('x', function(d) {
//console.log("x0"); console.log(d.x0);
return xScale(d.x0);
})
.attr('y', function(d, i) {
//console.log(yScale(d.y));
//console.log(i);
return yScale(d.y);
})
.attr('height', 10) //function (d) {return yScale.rangeBand();})
.attr('width', function(d) {
return xScale(d.x);
});
</script>
</div>
</body>
You are appending the rectangles to existing translated groups (the axes) because of this:
var groups = svg.selectAll("g")
Instead, select nothing (and also remember to enter the selection):
var groups = svg.selectAll(null)
Here's your code with that change:
<script src="https://d3js.org/d3.v7.min.js"></script>
<body>
<div id="bar_chart">
<script>
var data = [{
dep_time: "5:30",
risk: 100,
details: [{
time: 19,
source: 'Drive'
},
{
time: 10,
source: 'Margin'
},
{
time: 42,
source: 'Full'
},
{
time: 35,
source: 'Crossing'
},
{
time: 23,
source: 'Drive'
}
]
},
{
dep_time: "6:20",
risk: 80,
details: [{
time: 25,
source: 'Drive'
},
{
time: 1,
source: 'Margin'
},
{
time: 38,
source: 'Full'
},
{
time: 35,
source: 'Crossing'
},
{
time: 25,
source: 'Drive'
}
]
},
{
dep_time: "7:10",
risk: 5,
details: [{
time: 8,
source: 'Drive'
},
{
time: 28,
source: 'Margin'
},
{
time: 38,
source: 'Full'
},
{
time: 35,
source: 'Crossing'
},
{
time: 18,
source: 'Drive'
}
]
}
];
var chartContainer = '.chart-container';
var units = [];
var xMax = 0;
data.forEach(function(s) {
var total = 0;
s.details.forEach(function(s) {
s["x0"] = total; //Abs left
s["x"] = s.time; //Width
s["x1"] = total + s.time; //Abs right
total = total + s.time;
if (total > xMax) xMax = total;
});
s["y"] = s.dep_time;
units.push(s.dep_time);
});
//Need it to look like: newdata = [(Drive) [19, 25, 32.] Margin [0, 1, 28]. Full [42, 38, 38]. Crossing [35, 35, 35]. Drive [23, 25, 18].]
//So it's a row in the array for each column of data.
//re-arrange the data so it makes more sense to d3 (and less sense to any sane human)
var newdata = [];
for (var i = 0; i < data[0].details.length; i++) {
var row = [];
data.forEach(function(s) {
row.push({
x: s.details[i].x,
y: s.dep_time,
x0: s.details[i].x0
});
});
newdata.push(row);
}
var margins = {
left: 50,
bottom: 50,
top: 25,
right: 25
};
var sizes = {
width: 500,
height: 150
};
var width = sizes.width - margins.left - margins.right;
var height = sizes.height - margins.bottom - margins.top;
var svg = d3.select("#bar_chart")
.append('svg')
.attr('width', width + margins.left + margins.right)
.attr('height', height + margins.bottom)
.append('g')
.attr('transform', 'translate(' + margins.left + ', ' + margins.top + ")");
var yScale = d3.scaleBand()
.domain(units)
.rangeRound([0, height]);
var yAxis = d3.axisLeft(yScale);
var yAxisG = svg.append("g")
.attr("transform", "translate(0,0)")
.attr("id", "yaxis")
.call(yAxis);
const xScale = d3.scaleLinear()
.domain([0, xMax])
.range([0, width]);
var xAxis = d3.axisBottom(xScale);
var xAxisG = svg.append("g")
.attr("transform", "translate(0, " + height + ")")
.attr("id", "xaxis")
.call(xAxis
.ticks(8));
var bar_colors = ['red', 'purple', 'green', 'lightblue', 'yellow'];
var colors = function(i) {
return bar_colors[i];
}
var groups = svg.selectAll(null)
.data(newdata)
.enter()
.append('g')
.style('fill', function(d, i) {
return colors(i);
});
groups.selectAll('rect')
.data(function(d) {
//console.log(d);
return d;
})
.enter()
.append('rect')
.attr('x', function(d) {
return xScale(d.x0);
})
.attr('y', function(d, i) {
return yScale(d.y);
})
.attr('height', 10) //function (d) {return yScale.rangeBand();})
.attr('width', function(d) {
return xScale(d.x);
});
</script>
</div>
</body>

D3 charts - Image in center of donut chart SVG

I want to append a image to the center donut svg in D3 charts. I am trying to change the SVG in JS, but I am not able to achieve this.
Currently I have added the image in html and it is not in center of donut chart.
Link to codepen : https://codepen.io/durairaj-the-looper/pen/xxbVWNG
JS :
var dataset = [
{ name: 'Stomach issues', percent: 29 },
{ name: 'Urinary tract infections (UTI)', percent: 13 },
{ name: 'Skin conditions', percent: 12 },
{ name: 'Cancer', percent: 12 },
{ name: 'Eye conditions', percent: 11 },
{ name: 'Ear infections', percent: 8 },
{ name: 'Pain', percent: 6 },
{ name: 'Growth', percent: 4 },
{ name: 'Foreign body obstruction', percent: 3 },
{ name: 'Allergies', percent: 2 }
];
var div = d3.select(".widget").append("div").attr("class", "toolTip");
var pie=d3.layout.pie()
.value(function(d){return d.percent})
.sort(null)
.padAngle(.03);
var w=300,h=300;
var outerRadius=w/2;
var innerRadius=100;
var color = d3.scale.category10();
var arc=d3.svg.arc()
.outerRadius(outerRadius)
.innerRadius(innerRadius);
var svg=d3.select("#chart")
.append("svg")
.attr({
width:700,
height:h,
class:'shadow'
}).append('g')
.attr({
transform:'translate('+w/2+','+h/2+')'
});
var path=svg.selectAll('path')
.data(pie(dataset))
.enter()
.append('path')
.attr({
d:arc,
fill:function(d,i){
return color(d.data.name);
}
});
path.transition()
.duration(1000)
.attrTween('d', function(d) {
var interpolate = d3.interpolate({startAngle: 0, endAngle: 0}, d);
return function(t) {
return arc(interpolate(t));
};
});
var restOfTheData=function(){
var text=svg.selectAll('text')
.data(pie(dataset))
.enter()
.append("text")
.transition()
.duration(200)
.attr("transform", function (d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("dy", ".4em")
.attr("text-anchor", "middle")
.text(function(d){
return d.data.percent+"%";
})
.style({
fill:'#fff',
'font-size':'10px'
});
var legendRectSize=20;
var legendSpacing=7;
var legendHeight=legendRectSize+legendSpacing;
var legend=svg.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr({
class:'legend',
transform:function(d,i){
//Just a calculation for x & y position
return 'translate(+200,' + ((i*legendHeight)-125) + ')';
}
});
legend.append('rect')
.attr({
width:legendRectSize,
height:legendRectSize,
rx:20,
ry:20
})
.style({
fill:color,
stroke:color
});
legend.append('text')
.attr({
x:30,
y:15
})
.text(function(d){
return d;
}).style({
'font-size':'14px'
});
};
setTimeout(restOfTheData,1000);
I want to append a image to donut svg in D3 charts. I am trying to change the SVG in JS, but I am not able to achieve this.
You can do that using CSS
here is the code which worked.
Here is the codepen example: https://codepen.io/chintuyadav/pen/VwYmxzv
var dataset = [
{ name: 'Stomach issues', percent: 29 },
{ name: 'Urinary tract infections (UTI)', percent: 13 },
{ name: 'Skin conditions', percent: 12 },
{ name: 'Cancer', percent: 12 },
{ name: 'Eye conditions', percent: 11 },
{ name: 'Ear infections', percent: 8 },
{ name: 'Pain', percent: 6 },
{ name: 'Growth', percent: 4 },
{ name: 'Foreign body obstruction', percent: 3 },
{ name: 'Allergies', percent: 2 }
];
var pie=d3.layout.pie()
.value(function(d){return d.percent})
.sort(null)
.padAngle(.03);
var w=300,h=300;
var outerRadius=w/2;
var innerRadius=100;
var color = d3.scale.category10();
var arc=d3.svg.arc()
.outerRadius(outerRadius)
.innerRadius(innerRadius);
var svg=d3.select("#chart")
.append("svg")
.attr({
width:w,
height:h,
class:'shadow'
}).append('g')
.attr({
transform:'translate('+w/2+','+h/2+')'
});
var path=svg.selectAll('path')
.data(pie(dataset))
.enter()
.append('path')
.attr({
d:arc,
fill:function(d,i){
return color(d.data.name);
}
});
path.transition()
.duration(1000)
.attrTween('d', function(d) {
var interpolate = d3.interpolate({startAngle: 0, endAngle: 0}, d);
return function(t) {
return arc(interpolate(t));
};
});
var restOfTheData=function(){
var text=svg.selectAll('text')
.data(pie(dataset))
.enter()
.append("text")
.transition()
.duration(200)
.attr("transform", function (d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("dy", ".4em")
.attr("text-anchor", "middle")
.text(function(d){
return d.data.percent+"%";
})
.style({
fill:'#fff',
'font-size':'10px'
});
var legendRectSize=20;
var legendSpacing=7;
var legendHeight=legendRectSize+legendSpacing;
var legend=svg.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr({
class:'legend',
transform:function(d,i){
//Just a calculation for x & y position
return 'translate(-35,' + ((i*legendHeight)-65) + ')';
}
});
legend.append('rect')
.attr({
width:legendRectSize,
height:legendRectSize,
rx:20,
ry:20
})
.style({
fill:color,
stroke:color
});
legend.append('text')
.attr({
x:30,
y:15
})
.text(function(d){
return d;
}).style({
fill:'#929DAF',
'font-size':'14px'
});
};
setTimeout(restOfTheData,1000);
body {
width: 100%;
font-family: 'Roboto', sans-serif;
height: 100%;
}
.legend {
display:none;
}
.widget {
margin: 0 auto;
width:350px;
margin-top:50px;
background-color: #222D3A;
border-radius: 5px;
}
.header{
background-color: #29384D;
height:40px;
color:#929DAF;
text-align: center;
line-height: 40px;
border-top-left-radius: 7px;
border-top-right-radius: 7px;
font-weight: 400;
font-size: 1.5em;
text-shadow: 1px 1px #06060d;
}
.chart-container{
padding:25px;
background: white url('https://www.iconsdb.com/icons/preview/gray/github-10-xxl.png') no-repeat center ;
background-size:200px 200px;
border: none !important;
}
.shadow {
-webkit-filter: drop-shadow( 0px 3px 3px rgba(0,0,0,.5) );
filter: drop-shadow( 0px 3px 3px rgba(0,0,0,.5) );
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="widget">
<div id="chart" class="chart-container">
</div>
</div>

Sortable bar chart with long x axis labels rotated

I need to adjust Mike Bostock's sortable bar chart (available here) for long X axis labels. I have rotated them as slightly vertical and the initial result is that they are well separated and under the X axis but when the sorting process begins, they fall back on the axis.
If anyone could help me set the labels in the right place, I would be very happy!
My JSFiddle is available here and the JavaScript code is as follows:
var margin = {
top: 20,
right: 20,
bottom: 60,
left: 40
},
width = 960 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
var formatPercent = d3.format(".0%");
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1, 1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(formatPercent);
var svg = d3.select("body").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 + ")");
data = [{
letter: "Alongname1",
frequency: 0.08167
},
{
letter: "Alongname2",
frequency: 0.01492
},
{
letter: "Alongname3",
frequency: 0.0278
},
{
letter: "Alongname4",
frequency: 0.04253
},
{
letter: "Alongname5",
frequency: 0.12702
},
{
letter: "Alongname6",
frequency: 0.02288
},
{
letter: "Alongname7",
frequency: 0.02022
},
{
letter: "Alongname8",
frequency: 0.06094
},
{
letter: "Alongname9",
frequency: 0.06973
},
{
letter: "Alongname10",
frequency: 0.00153
},
{
letter: "Alongname11",
frequency: 0.00747
},
{
letter: "Alongname12",
frequency: 0.04025
},
{
letter: "Alongname13",
frequency: 0.02517
},
{
letter: "Alongname14",
frequency: 0.06749
},
{
letter: "Alongname15",
frequency: 0.07507
},
{
letter: "Alongname16",
frequency: 0.01929
},
{
letter: "Alongname17",
frequency: 0.00098
},
{
letter: "Alongname18",
frequency: 0.05987
}
];
data.forEach(function(d) {
d.frequency = +d.frequency;
});
x.domain(data.map(function(d) {
return d.letter;
}));
y.domain([0, d3.max(data, function(d) {
return d.frequency;
})]);
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", ".35em")
.attr("transform", "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("Frequency");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) {
return x(d.letter);
})
.attr("width", x.rangeBand())
.attr("y", function(d) {
return y(d.frequency);
})
.attr("height", function(d) {
return height - y(d.frequency);
});
var sortTimeout = setTimeout(function() {
d3.select("input").property("checked", true).each(change);
}, 2000);
function change() {
clearTimeout(sortTimeout);
// Copy-on-write since tweens are evaluated after a delay.
var x0 = x.domain(data.sort(this.checked ?
function(a, b) {
return b.frequency - a.frequency;
} :
function(a, b) {
return d3.ascending(a.letter, b.letter);
})
.map(function(d) {
return d.letter;
}))
.copy();
svg.selectAll(".bar")
.sort(function(a, b) {
return x0(a.letter) - x0(b.letter);
});
var transition = svg.transition().duration(750),
delay = function(d, i) {
return i * 50;
};
transition.selectAll(".bar")
.delay(delay)
.attr("x", function(d) {
return x0(d.letter);
});
transition.select(".x.axis")
.call(xAxis)
.selectAll("g")
.delay(delay);
}
That behaviour is actually expected: when you call the axis again, the axis generator resets the dx and dy attributes.
You can apply them again after the second call, just as you did in the first one, but you'll notice that the texts will quickly flicker before going to their new positions.
So, an easy alternative, that avoids the flickering, is setting the labels' positions using the rotate itself. For instance (hardcoded values):
.attr("transform", "rotate(-65 12 32)");
Here is the code with that change:
var margin = {
top: 20,
right: 20,
bottom: 60,
left: 40
},
width = 960 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
var formatPercent = d3.format(".0%");
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1, 1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(formatPercent);
var svg = d3.select("body").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 + ")");
data = [{
letter: "Alongname1",
frequency: 0.08167
},
{
letter: "Alongname2",
frequency: 0.01492
},
{
letter: "Alongname3",
frequency: 0.0278
},
{
letter: "Alongname4",
frequency: 0.04253
},
{
letter: "Alongname5",
frequency: 0.12702
},
{
letter: "Alongname6",
frequency: 0.02288
},
{
letter: "Alongname7",
frequency: 0.02022
},
{
letter: "Alongname8",
frequency: 0.06094
},
{
letter: "Alongname9",
frequency: 0.06973
},
{
letter: "Alongname10",
frequency: 0.00153
},
{
letter: "Alongname11",
frequency: 0.00747
},
{
letter: "Alongname12",
frequency: 0.04025
},
{
letter: "Alongname13",
frequency: 0.02517
},
{
letter: "Alongname14",
frequency: 0.06749
},
{
letter: "Alongname15",
frequency: 0.07507
},
{
letter: "Alongname16",
frequency: 0.01929
},
{
letter: "Alongname17",
frequency: 0.00098
},
{
letter: "Alongname18",
frequency: 0.05987
}
];
data.forEach(function(d) {
d.frequency = +d.frequency;
});
x.domain(data.map(function(d) {
return d.letter;
}));
y.domain([0, d3.max(data, function(d) {
return d.frequency;
})]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("transform", "rotate(-65 12 32)");
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("Frequency");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) {
return x(d.letter);
})
.attr("width", x.rangeBand())
.attr("y", function(d) {
return y(d.frequency);
})
.attr("height", function(d) {
return height - y(d.frequency);
});
var sortTimeout = setTimeout(function() {
d3.select("input").property("checked", true).each(change);
}, 2000);
function change() {
clearTimeout(sortTimeout);
// Copy-on-write since tweens are evaluated after a delay.
var x0 = x.domain(data.sort(this.checked ?
function(a, b) {
return b.frequency - a.frequency;
} :
function(a, b) {
return d3.ascending(a.letter, b.letter);
})
.map(function(d) {
return d.letter;
}))
.copy();
svg.selectAll(".bar")
.sort(function(a, b) {
return x0(a.letter) - x0(b.letter);
});
var transition = svg.transition().duration(750),
delay = function(d, i) {
return i * 50;
};
transition.selectAll(".bar")
.delay(delay)
.attr("x", function(d) {
return x0(d.letter);
});
transition.select(".x.axis")
.call(xAxis)
.selectAll("g")
.delay(delay);
}
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
position: relative;
width: 960px;
}
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
fill-opacity: .9;
}
.x.axis path {
display: none;
}
label {
position: absolute;
top: 10px;
right: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<label><input type="checkbox"> Sort values</label>

d3 error displaying datetime on x-axis line chart

I am implementing a multi-line series chart using d3.js and I am getting an error pointing to my x-axis when trying to plot my dateTime from the data coming in. "Error: attribute d: Expected number, "MNaN,376.88020650…"."
Here is my function
var data = [{
"Brand": "Toyota",
"Count": 1800,
"Time": "2017-04-02 16"},
{
"Brand": "Toyota",
"Count": 1172,
"Time": "2017-04-02 17"},
{
"Brand": "Toyota",
"Count": 2000,
"Time": "2017-04-02 18"},
{
"Brand": "Honda",
"Count": 8765,
"Time": "2017-04-02 16"},
{
"Brand": "Honda",
"Count": 3445,
"Time": "2017-04-02 17"},
{
"Brand": "Honda",
"Count": 1232,
"Time": "2017-04-02 18"}
]
var dataGroup = d3.nest() //d3 method that groups data by Brand
.key(function(d) {return d.Brand;})
.entries(data);
console.log(JSON.stringify(dataGroup));
//var color = d3.scale.category10();
var vis = d3.select("#visualisation"),
WIDTH = 1000,
HEIGHT = 500,
MARGINS = {
top: 50,
right: 20,
bottom: 50,
left: 50
},
xScale = d3.scaleLinear().range([MARGINS.left, WIDTH - MARGINS.right]).domain([d3.min(data, function(d) { //set up x-axis based on data
return d.Time;
}), d3.max(data, function(d) {
return d.Time;
})]),
yScale = d3.scaleLinear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([d3.min(data, function(d) { //set up y-axis based on data
return d.Count;
}), d3.max(data, function(d) {
return d.Count;
})]),
xAxis = d3.axisBottom()
.scale(xScale),
yAxis = d3.axisLeft()
.scale(yScale)
vis.append("svg:g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom) + ")")
.call(xAxis);
vis.append("svg:g")
.attr("class", "y axis")
.attr("transform", "translate(" + (MARGINS.left) + ",0)")
.call(yAxis);
var lineGen = d3.line()
.x(function(d) {
return xScale(d.Time);
})
.y(function(d) {
return yScale(d.Count);
})
.curve(d3.curveBasis);
dataGroup.forEach(function(d,i) { //iterate over the dataGroup and create line graph for each brand
vis.append('svg:path')
.attr('d', lineGen(d.values))
.attr('stroke', function(d,j) {
return "hsl(" + Math.random() * 360 + ",100%,50%)"; //random color for each brand line on graph
})
.attr('stroke-width', 2)
.attr('id', 'line_'+d.key)
.attr('fill', 'none');
lSpace = WIDTH/dataGroup.length; //define the legend space based on number of brands
vis.append("text")
.attr("x", (lSpace/2)+i*lSpace)
.attr("y", HEIGHT)
.style("fill", "black")
.attr("class","legend")
.on('click',function(){
var active = d.active ? false : true;
var opacity = active ? 0 : 1;
d3.select("#line_" + d.key).style("opacity", opacity);
d.active = active;
})
.text(d.key);
});
My dates are in yyyy-mm-dd HH format and what I am trying to accomplish is this for example:
"Time": "2017-04-02 16" converted to 'April 02' on the x axis and have the hour (HH) just displayed as a tool tip...etc
Here is a jsfiddle link https://jsfiddle.net/rsov2s2s/
Any help is appreciated.
In your data objects, Time is only a string. Thus, you`ll have to parse it into an actual date:
data.forEach(function(d){
d.Time = d3.timeParse("%Y-%m-%d %H")(d.Time)
});
In this function, d3.timeParse uses "%Y-%m-%d %H" as a specifier, which matches the structure of your strings.
After that, don't forget to change the xScale from scaleLinear to scaleTime.
Here is your code with those changes only:
var data = [{
"Brand": "Toyota",
"Count": 1800,
"Time": "2017-04-02 16"
}, {
"Brand": "Toyota",
"Count": 1172,
"Time": "2017-04-02 17"
}, {
"Brand": "Toyota",
"Count": 2000,
"Time": "2017-04-02 18"
}, {
"Brand": "Honda",
"Count": 8765,
"Time": "2017-04-02 16"
}, {
"Brand": "Honda",
"Count": 3445,
"Time": "2017-04-02 17"
}, {
"Brand": "Honda",
"Count": 1232,
"Time": "2017-04-02 18"
}];
data.forEach(function(d) {
d.Time = d3.timeParse("%Y-%m-%d %H")(d.Time)
});
var dataGroup = d3.nest() //d3 method that groups data by Brand
.key(function(d) {
return d.Brand;
})
.entries(data);
//var color = d3.scale.category10();
var vis = d3.select("#visualisation"),
WIDTH = 1000,
HEIGHT = 500,
MARGINS = {
top: 50,
right: 20,
bottom: 50,
left: 50
},
xScale = d3.scaleTime().range([MARGINS.left, WIDTH - MARGINS.right]).domain([d3.min(data, function(d) { //set up x-axis based on data
return d.Time;
}), d3.max(data, function(d) {
return d.Time;
})]),
yScale = d3.scaleLinear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([d3.min(data, function(d) { //set up y-axis based on data
return d.Count;
}), d3.max(data, function(d) {
return d.Count;
})]),
xAxis = d3.axisBottom()
.scale(xScale),
yAxis = d3.axisLeft()
.scale(yScale)
vis.append("svg:g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom) + ")")
.call(xAxis);
vis.append("svg:g")
.attr("class", "y axis")
.attr("transform", "translate(" + (MARGINS.left) + ",0)")
.call(yAxis);
var lineGen = d3.line()
.x(function(d) {
return xScale(d.Time);
})
.y(function(d) {
return yScale(d.Count);
})
.curve(d3.curveBasis);
dataGroup.forEach(function(d, i) { //iterate over the dataGroup and create line graph for each brand
vis.append('svg:path')
.attr('d', lineGen(d.values))
.attr('stroke', function(d, j) {
return "hsl(" + Math.random() * 360 + ",100%,50%)"; //random color for each brand line on graph
})
.attr('stroke-width', 2)
.attr('id', 'line_' + d.key)
.attr('fill', 'none');
lSpace = WIDTH / dataGroup.length; //define the legend space based on number of brands
vis.append("text")
.attr("x", (lSpace / 2) + i * lSpace)
.attr("y", HEIGHT)
.style("fill", "black")
.attr("class", "legend")
.on('click', function() {
var active = d.active ? false : true;
var opacity = active ? 0 : 1;
d3.select("#line_" + d.key).style("opacity", opacity);
d.active = active;
})
.text(d.key);
});
.axis path {
fill: none;
stroke: #777;
shape-rendering: crispEdges;
}
.axis text {
font-family: Lato;
font-size: 13px;
}
.legend {
font-size: 14px;
font-weight: bold;
cursor: pointer;
<title>D3 Test</title>
<script src="https://d3js.org/d3.v4.js"></script>
<body>
<svg id="visualisation" width="1000" height="600"></svg>
<script src="InitChart.js"></script>
</body>

D3 (v3) resetting a previously animated circle after clicking another one

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.

Categories