For some reason my graph is going into my padding, even though I've incorporated the padding into the linear scale of y values. Here's my code:
$(document).ready(function(){
//padding
var padding = {
top: 20,
right: 20,
bottom: 20,
left: 20,
};
//graph dimensions
var w = 1000
var h = 500
//append and assign variable reference to svg
var svg = d3.select('section').append('svg').attr('id', 'graph').attr('width', w).attr('height', h);
//get json data
d3.json('https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/GDP-data.json', function(error, data){
//error handling
if (error){
console.warn(error);
}
var dataset = data.data;
//minimum and maximum date data points
var minDate = new Date(dataset[0][0]);
var maxDate = new Date(dataset[274][0]);
//scale data to svg dimensions
var xScale = d3.time.scale().domain([minDate, maxDate]).range([padding.left, w - padding.right]);
var yScale = d3.scale.linear().domain([0, d3.max(dataset, function(d){return d[1];})]).range([h - padding.bottom, padding.top]);
console.log(yScale(0));
//make x and y axis
var xAxis = d3.svg.axis().orient('bottom').scale(xScale);
//generate graph
svg.selectAll('rect')
.data(dataset)
.enter()
.append('rect')
.attr('x', function(d, i){
return i * ((w - padding.left - padding.right) / dataset.length) + padding.left;
})
.attr('y', function(d){
return yScale(d[1]);
})
.attr('width', (w - padding.left - padding.right) / dataset.length)
.attr('height', function(d){return d[1]})
.attr('fill', '#4682B4');
//generate x and y axix
svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(0,' + (h - padding.bottom) + ')')
.call(xAxis);
});
});
Here's the page on Codepen.
Does anyone know why the graph is cutting into the x axis like that? How do I fix it so that the bars start padding.bottom pixels away from the bottom
I've found the answer, I changed height attribute of svg to:
.attr('height', function(d){return h - padding.bottom - yScale(d[1])});
and it worked
Related
I have made two separate graph on separate page of Bar and pie chart respectively and now i wanted to combine this two graph in the single page so that I can have a dashboard. but when i start to combine to two graph in the main page its not happening and they overlap of each other.
Code:
https://github.com/Mustafa2911/d3-design/blob/main/combine.html
Combine file contain: Code of both pie and bar chart.
Bar file contain: Code of bar chart.
Pie chart contain: Code of pie chart.
Tried this with your code.
Scroll to see the bar graph axis.
NOTE: The bar graph data will not be available ∵ it is from the demo1.csv file in your repository.
Hope this helps.
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<style>
#my_dataviz {
display: inline-block;
width: 50%;
}
</style>
</head>
<body>
<div id="my_dataviz"></div>
<script>
// set the dimensions and margins of the graph
var width = 800
height = 450
margin = 40
// The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
var radius = Math.min(width, height) / 2 - margin
// append the svg object to the div called 'my_dataviz'
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
// Create dummy data
var data = {
Corporation_Tax: 15,
Income_Tax: 15,
Customs: 5,
Union_Excise_Duties: 7,
Good_and_Service_tax: 16,
Non_tax_Revenue: 5,
Non_Dept_Capital_Receipt: 2,
Borrowings_Liabilities: 35
}
// set the color scale
var color = d3.scaleOrdinal()
.domain(["a", "b", "c", "d", "e", "f", "g", "h"])
.range(d3.schemeSet1);
// Compute the position of each group on the pie:
var pie = d3.pie()
.sort(null) // Do not sort group by size
.value(function(d) {
return d.value;
})
var data_ready = pie(d3.entries(data))
// The arc generator
var arc = d3.arc()
.innerRadius(radius * 0.5) // This is the size of the donut hole
.outerRadius(radius * 0.8)
// Another arc that won't be drawn. Just for labels positioning
var outerArc = d3.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9)
// Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
svg
.selectAll('allSlices')
.data(data_ready)
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d) {
return (color(d.data.key))
})
.attr("stroke", "white")
.style("stroke-width", "2px")
.style("opacity", 1)
// Add the polylines between chart and labels:
svg
.selectAll('allPolylines')
.data(data_ready)
.enter()
.append('polyline')
.attr("stroke", "black")
.style("fill", "none")
.attr("stroke-width", 1)
.attr('points', function(d) {
var posA = arc.centroid(d) // line insertion in the slice
var posB = outerArc.centroid(d) // line break: we use the other arc generator that has been built only for that
var posC = outerArc.centroid(d); // Label position = almost the same as posB
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 // we need the angle to see if the X position will be at the extreme right or extreme left
posC[0] = radius * 0.95 * (midangle < Math.PI ? 1 : -1); // multiply by 1 or -1 to put it on the right or on the left
return [posA, posB, posC]
})
// Add the polylines between chart and labels:
svg
.selectAll('allLabels')
.data(data_ready)
.enter()
.append('text')
.text(function(d) {
console.log(d.data.key);
return d.data.key
})
.attr('transform', function(d) {
var pos = outerArc.centroid(d);
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2
pos[0] = radius * 0.99 * (midangle < Math.PI ? 1 : -1);
return 'translate(' + pos + ')';
})
.style('text-anchor', function(d) {
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2
return (midangle < Math.PI ? 'start' : 'end')
})
</script>
<style>
#my_dataviz {
display: inline-block;
width: 50%;
}
</style>
<div id="my_dataviz_es"></div>
<script>
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 30,
bottom: 40,
left: 160
},
width = 460,
height = 400;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz_es")
.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 + ")");
// Parse the Data
d3.csv("demo1.csv", function(data) {
// Add X axis
var x = d3.scaleLinear()
.domain([0, 550000])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end");
// Y axis
var y = d3.scaleBand()
.range([0, height])
.domain(data.map(function(d) {
return d.Country;
}))
.padding(.1);
svg.append("g")
.call(d3.axisLeft(y))
//Bars
svg.selectAll("myRect")
.data(data)
.enter()
.append("rect")
.attr("x", x(0))
.attr("y", function(d) {
return y(d.Country);
})
.attr("width", function(d) {
return x(d.Value);
})
.attr("height", y.bandwidth())
.attr("fill", "#69b3a2")
// .attr("x", function(d) { return x(d.Country); })
// .attr("y", function(d) { return y(d.Value); })
// .attr("width", x.bandwidth())
// .attr("height", function(d) { return height - y(d.Value); })
// .attr
})
</script>
</body>
</html>
EDIT: See here - https://codepen.io/KZJ/pen/rNpqvdq?editors=1011 - for changes made reg. the below comment
what if I want to have my bar chart at the top and on right side i want to have my pie chart
Changed -
a) Both charts were using the same name 'svg' to d3.select() the divs. This caused the charts to overlap.
b) Modified width, height, transform, and added some border CSS - only for demonstration purposes - It can be removed/edited as required.
FYR this is how it looks now -
I'm drawing a bar chart with axes, and yScale is behaving differently on my yAxis than on my appended bars.
I set my yScale range to start at (h - yPadding) to leave extra room at the bottom for xAxis labels.
var yScale = d3.scale.linear()
.domain([0, d3.max(val)])
.range([h - yPadding, 0]);
-- The range is inverted, otherwise my yAxis labels are upside down.
When I call the yAxis using yScale, it obeys the starting point of (h - yPadding) and leaves room at the bottom.
But all the "rects" I'm appending to the chart, start at h, instead of (h - yPadding) even though I'm calling yScale on these "rects" just like on yAxis.
If I change the range to [h, 0] instead of [h - yPadding, 0], only the yAxis reacts to the change, and the bars still start at h.
Why are the bars ignoring the yScale?
<script type="text/javascript">
var xhr = new XMLHttpRequest();
function makeRequest(){
xhr.open("GET", "https://api.coinmarketcap.com/v1/ticker/", true);
xhr.send();
xhr.onreadystatechange = processRequest;
}
function processRequest(){
console.log("testing, state: ", xhr.readyState)
if(xhr.readyState == 4 && xhr.status == 200){
dataset = [];
for(var i = 0; i < 10; i++){
addingId = JSON.parse(xhr.responseText)[i];
addingId.id = i;
dataset.push(addingId);
}
console.log("this is dataset: ", dataset);
makeChart();
}
}
function makeChart(){
var w = 1000;
var h = 600;
var padding = 40;
var yPadding = 80;
var val = [];
dataset.forEach(function(ele){
val.push(parseInt(ele.market_cap_usd));
})
var max = d3.max(val)
var xAxisNames = []
dataset.forEach(function(ele){ xAxisNames.push(ele.name); })
// console.log(">>>>>>>>", xAxisNames)
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([padding, w - padding], 0.05)
var yScale = d3.scale.linear()
.domain([0, d3.max(val)])
.range([h - yPadding, 0]);
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.tickFormat(function(d){
if(d > 0){ return d / 1000000000 + " b"; }
return "";
})
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.tickFormat(function(d, i){
return xAxisNames[i]
})
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i){
return xScale(i);
})
.attr("y", function(d){
return yScale(d.market_cap_usd);
})
.attr("width", xScale.rangeBand())
.attr("height", function(d, i){
return h - yScale(d.market_cap_usd)
})
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + padding + ", 0)")
.call(yAxis);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0, " + (h - yPadding) + ")")
.call(xAxis)
.selectAll("text")
.attr("y", 15)
.attr("font-size", 12)
.attr("x", xScale.rangeBand() / 2)
.attr("transform", "rotate(45)")
}
makeRequest();
</script>
A scale just maps an input domain to an output range, nothing more. You have to set the positions and the dimensions of the SVG elements accordingly. Let's see:
Right now, given your scale, when you pass it the minimum value in your domain it will return:
h - yPadding
You want such bars having a height of zero pixels, of course. To get that zero the equation is simple, you have to subtract from that value:
(h - yPadding) - yScale(minimumDomainValue)
That will give you zero for the minimum value in the domain.
Therefore, this should be the height of the rectangles:
.attr("height", function(d, i){
return (h - yPadding) - yScale(d.market_cap_usd)
})
PS: by the way, in D3, one of the few situations where a scale determines the dimensions of a SVG element is the path/lines created by an axis generator. That's why you're seeing a different behaviour in your axis.
I'm trying to build a basic, responsive bar chart with data loaded from a CSV. My chart isn't reading the CSV and gives me NaNs. Here's a screenshot of what my chart looks like. Here is my current code:
<script type="text/javascript">
var url = "data/data.csv",
margin = {top: 30, right: 10, bottom: 30, left: 10},
width = parseInt(d3.select('#chart').style('width'), 10),
width = width - margin.left - margin.right,
height = 200, // placeholder
barHeight = 20,
spacing = 5,
total = d3.format('n');
// scales and axes
var x = d3.scale.linear()
.range([0, width])
.domain([0, 4000]); // hard-coding this because I know the data
var y = d3.scale.ordinal();
var xAxis = d3.svg.axis()
.scale(x)
.ticks(3);
// create the chart
var chart = d3.select('#chart').append('svg')
.style('width', (width + margin.left + margin.right) + 'px')
.append('g')
.attr('transform', 'translate(' + [margin.left, margin.top] + ')');
d3.csv(url).row(function(d) {
d.total = +d.total;
d.name = +d.name;
return d;
}).get(function(err, data) {
// sort
data = _.sortBy(data, 'total').reverse();
// set y domain
y.domain(d3.range(data.length))
.rangeBands([0, data.length * barHeight]);
// set height based on data
height = y.rangeExtent()[1];
d3.select(chart.node().parentNode)
.style('height', (height + margin.top + margin.bottom) + 'px')
// render the chart
// add top and bottom axes
chart.append('g')
.attr('class', 'x axis top')
.call(xAxis.orient('top'));
chart.append('g')
.attr('class', 'x axis bottom')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis.orient('bottom'));
var bars = chart.selectAll('.bar')
.data(data)
.enter().append('g')
.attr('class', 'bar')
.attr('transform', function(d, i) { return 'translate(0,' + y(i) + ')'; });
bars.append('rect')
.attr('class', 'background')
.attr('height', y.rangeBand())
.attr('width', width);
bars.append('rect')
.attr('class', 'total')
.attr('height', y.rangeBand())
.attr('width', function(d) { return x(d.total); })
bars.append('text')
.text(function(d) { return d.name; })
.attr('class', 'name')
.attr('y', y.rangeBand() - 5)
.attr('x', spacing);
bars.append('line')
.attr('class', 'median')
.attr('x1', x(median))
.attr('x2', x(median))
.attr('y1', 1)
.attr('y2', y.rangeBand() - 1);
});
// resize
d3.select(window).on('resize', resize);
function resize() {
// update width
width = parseInt(d3.select('#chart').style('width'), 10);
width = width - margin.left - margin.right;
// resize the chart
x.range([0, width]);
d3.select(chart.node().parentNode)
.style('height', (y.rangeExtent()[1] + margin.top + margin.bottom) + 'px')
.style('width', (width + margin.left + margin.right) + 'px');
chart.selectAll('rect.background')
.attr('width', width);
chart.selectAll('rect.total')
.attr('width', function(d) { return x(d.total); });
// update axes
chart.select('.x.axis.top').call(xAxis.orient('top'));
chart.select('.x.axis.bottom').call(xAxis.orient('bottom'));
}
// highlight code blocks
hljs.initHighlighting();
</script>
Here's the data:
name, total
Brian, 1514
Frankie, 1439
Jeffery, 1615
Jerry, 685
Kenneth, 3233
Michael, 116
Roy, 817
Timothy, 2184
I can see a couple of issues with your code and data:
For the issues with the data, you can either clean up the rows by removing the spaces, eg:
name,total
Brian,1514
Frankie,1439
Jeffery,1615
Jerry,685
Kenneth,3233
Michael,116
Roy,817
Timothy,2184
Or you can reference them in your code using the [] notation, eg:
d[' total'] = +d[' total'];
d3 is adding the leading space to the attribute name for each record. When you reference the attributes as d.total, it's returning undefined and undefined coerced to a number is NaN.
Eg. data[0]['total'] is undefined but data[0][' total'] has "1514" as you'd expect. Removing the spaces from the data is the easiest way of dealing with this.
In your code, you're doing this:
d.name = +d.name;
This will translate the name value into NaN, as it's a text string beginning with an alpha character, so javascript doesn't know how to coerce it to a number.
You're also referencing a variable named median which isn't declared in your code.
I've created a jsfiddle to help: http://jsfiddle.net/BenLyall/eh1r6j2e/12/
Note: I've changed the d3.csv... line to work with an element in the DOM to store the data.
Why are you making name into Number
below code
d3.csv(url).row(function(d) {
d.total = +d.total;
d.name = +d.name;
return d;
})
remove conversion of name.Okay
I am new to D3.js and I am drawing a bullet chart for a website I am working on. I took the code from here as a starting point:
http://bl.ocks.org/jugglinmike/6004102
I have a chart drawn, and for a basic case, I do not need to worry about switching the data shown. I really need to, however, have the entire chart and all of its elements scale with the window resize.
Right now, I have two files, bullet.js and draw_bullet.js. This is the code for draw_bullet.js:
var margin = {top: 5, right: 40, bottom: 20, left: 120},
width = ($(window).width() * .3) - margin.left - margin.right,
height = 50 - margin.top - margin.bottom;
var chart = d3.bullet()
.width(width)
.height(height);
function fillChart() {
d3.json("/static/response.json", function(error, data) {
var svg = d3.select("#zone1").selectAll("svg")
.data(data)
.enter().append("svg")
.attr("class", "bullet")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(chart1);
var title = svg.append("g")
.style("text-anchor", "end")
.attr("transform", "translate(-6," + height / 2 + ")");
title.append("text")
.attr("class", "title")
.text(function(d) { return d.title; });
title.append("text")
.attr("class", "subtitle")
.attr("dy", "1em")
.text(function(d) { return d.subtitle; });
});
}
And my code for bullet.js:
d3.bullet = function() {
var orient = "left", // TODO top & bottom
reverse = false,
duration = 0,
ranges = bulletRanges,
measures = bulletMeasures,
width = parseInt(d3.select("#zone1").style("width"), 10),
height = 30,
tickFormat = null;
// For each small multiple…
function bullet(g) {
g.each(function(d, i) {
var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
measurez = measures.call(this, d, i).slice().sort(d3.descending),
g = d3.select(this);
// Compute the new x-scale.
var x1 = d3.scale.linear()
.domain([0, Math.max(rangez[0], measurez[0])])
.range(reverse ? [width, 0] : [0, width]);
// Retrieve the old x-scale, if this is an update.
var x0 = this.__chart__ || d3.scale.linear()
.domain([0, Infinity])
.range(x1.range());
// Stash the new scale.
this.__chart__ = x1;
// Derive width-scales from the x-scales.
var w0 = bulletWidth(x0),
w1 = bulletWidth(x1);
// Update the range rects.
var range = g.selectAll("rect.range")
.data(rangez);
range.enter().append("rect")
.attr("class", function(d, i) { return "range s" + i; })
.attr("width", w0)
.attr("height", height)
.attr("x", reverse ? x0 : 0)
.transition()
.duration(duration)
.attr("width", w1)
.attr("x", reverse ? x1 : 0);
range.transition()
.duration(duration)
.attr("x", reverse ? x1 : 0)
.attr("width", w1)
.attr("height", height);
// Update the measure rects.
var measure = g.selectAll("rect.measure")
.data(measurez);
measure.enter().append("rect")
.attr("class", function(d, i) { return "measure s" + i; })
.attr("width", w0)
.attr("height", height / 3)
.attr("x", reverse ? x0 : 0)
.attr("y", height / 3)
.transition()
.duration(duration)
.attr("width", w1)
.attr("x", reverse ? x1 : 0);
measure.transition()
.duration(duration)
.attr("width", w1)
.attr("height", height / 3)
.attr("x", reverse ? x1 : 0)
.attr("y", height / 3);
// Compute the tick format.
var format = tickFormat || x1.tickFormat(8);
// Update the tick groups.
var tick = g.selectAll("g.tick")
.data(x1.ticks(8), function(d) {
return this.textContent || format(d);
});
// Initialize the ticks with the old scale, x0.
var tickEnter = tick.enter().append("g")
.attr("class", "tick")
.attr("transform", bulletTranslate(x0))
.style("opacity", 1e-6);
tickEnter.append("line")
.attr("y1", height)
.attr("y2", height * 7 / 6);
tickEnter.append("text")
.attr("text-anchor", "middle")
.attr("dy", "1em")
.attr("y", height * 7 / 6)
.text(format);
// Transition the entering ticks to the new scale, x1.
tickEnter.transition()
.duration(duration)
.attr("transform", bulletTranslate(x1))
.style("opacity", 1);
// Transition the updating ticks to the new scale, x1.
var tickUpdate = tick.transition()
.duration(duration)
.attr("transform", bulletTranslate(x1))
.style("opacity", 1);
tickUpdate.select("line")
.attr("y1", height)
.attr("y2", height * 7 / 6);
tickUpdate.select("text")
.attr("y", height * 7 / 6);
// Transition the exiting ticks to the new scale, x1.
tick.exit().transition()
.duration(duration)
.attr("transform", bulletTranslate(x1))
.style("opacity", 1e-6)
.remove();
});
d3.timer.flush();
}
// left, right, top, bottom
bullet.orient = function(x) {
if (!arguments.length) return orient;
orient = x;
reverse = orient == "right" || orient == "bottom";
return bullet;
};
// ranges (bad, satisfactory, good)
bullet.ranges = function(x) {
if (!arguments.length) return ranges;
ranges = x;
return bullet;
};
// measures (actual, forecast)
bullet.measures = function(x) {
if (!arguments.length) return measures;
measures = x;
return bullet;
};
bullet.width = function(x) {
if (!arguments.length) return width;
width = x;
return bullet;
};
bullet.height = function(x) {
if (!arguments.length) return height;
height = x;
return bullet;
};
bullet.tickFormat = function(x) {
if (!arguments.length) return tickFormat;
tickFormat = x;
return bullet;
};
bullet.duration = function(x) {
if (!arguments.length) return duration;
duration = x;
return bullet;
};
return bullet;
};
function bulletRanges(d) {
return d.ranges;
}
function bulletMeasures(d) {
return d.measures;
}
function bulletTranslate(x) {
return function(d) {
return "translate(" + x(d) + ",0)";
};
}
function bulletWidth(x) {
var x0 = x(0);
return function(d) {
return Math.abs(x(d) - x0);
};
}
})();
I am using jQuery and I know I have to embed a function in $(window).resize(), however I have tried many different things and none of them seem to adjust correctly. I try to set chart.width() to my new value in the function, and later when I call the width of the chart, it shows it as being my new value, but doesn't adjust its view on the screen. Is it necessary to redraw the entire chart and all its elements to resize? And then, also, when I tried to rescale the range for the chart, I found it very difficult to do so because the range variable is embedded in the anonymous function inside of bullet.js. Any help you can provide in pointing me in the right direction would be awesome. I tried using this tutorial, but it didn't seem to apply to my situation too much because they are different types of charts.
http://eyeseast.github.io/visible-data/2013/08/28/responsive-charts-with-d3/
Thanks!
I updated my code to use the scale method in D3. However, since then my bar chart won't display. What is the cause of this issue?
var dataset = [ ];
for (var i = 0; i < 14; i++) {var newNumber = Math.round(Math.random() * 70);
dataset.push(newNumber);}
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var widthScale = d3.scale.linear()
.domain([0,d3.max(dataset)])
.range([0,w]);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", 3)
.attr("y", function (d,i) {return i* 36;})
.attr("width", function(d) {return widthScale;})
.attr("height", h / dataset.length - barPadding)
.attr("fill", function(d) {return "rgb(0, 0, " + (d * 10) + ")";});
A D3 scale is just a function translates values from a given input domain (your data values, 0 to max) to a specified output range (the width of your chart). Thus you have to apply the scale to your data, e.g. widthScale( d ). Right now you are assigning the widthScale function to the width attribute of your rect instead of the output value.
See the working fiddle: http://jsfiddle.net/S92u4/