D3 bar chart - how to display positive/negative values together? - javascript

I'm attempting to create a bar chart in D3 that replicates this design. The idea is that values can range from -100 to 100 and are displayed alongside each other. The scale must stay as 0-100, with colours being used to indicate whether the number is above or below 0.
I've managed to create a simple bar chart that displays positive numbers but as soon as a negative number is added, the chart breaks. The following code is used to create the x and y axis. Negative values are displayed if the x domain is changed to [-100, 100], but doing so renders the chart in a way that is too different from the original design.
var y = d3.scaleBand()
.range([height, 0])
.padding(0.1);
var x = d3.scaleLinear()
.range([0, width]);
x.domain([0, 100])
y.domain(data.map(function(d) { return d.sentiment; }));
Can anyone provide some tips/guidance on producing a graph that looks similar to the provided design, if it's even possible? Link to my current graph can be found in the JSFiddle below:
JSFiddle
Many thanks.

Just use Math.abs():
.attr("width", function(d){
return x(Math.abs(d.value));
})
Here is the demo:
var data = [{"sentiment":"Result","value":28},{"sentiment":"Result2","value":-56}]
var margin = {top: 20, right: 20, bottom: 50, left: 70},
width = 850 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var y = d3.scaleBand()
.range([height, 0])
.padding(0.1);
var x = d3.scaleLinear()
.range([0, width]);
var svg = d3.select("#graph").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.forEach(function(d) {
d.value = +d.value;
});
x.domain([0, 100])
y.domain(data.map(function(d) { return d.sentiment; }));
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("width", function(d) {return x(Math.abs(d.value)); } )
.attr("y", function(d) { return y(d.sentiment) + 15; })
.attr("height", y.bandwidth())
.attr("fill", function(d) {
if (d.value <= 0) {
return "#FC4E5C";
} else {
return "#34A232";
}
});
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
svg.append("g")
.call(d3.axisLeft(y));
svg.append("text")
.attr("class", "x label")
.attr("text-anchor", "end")
.attr("x", width - 300)
.attr("y", height + 40)
.text("Sentiment (%)");
.bar {
margin-top: 50px;
height: 30px;
}
text {
fill: black;
font-size: 14px;
}
path {
stroke: black;
}
line {
stroke: black;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
<div id="graph">
</div>

Related

D3 Area Chart with a Single Value Not Rendering

When building an area chart in D3.js, when you have only a single value the chart does not render.
For demonstration purposes, I modified the following example: https://d3-graph-gallery.com/graph/area_basic.html to illustrate the problem.
<script>
// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 50},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
//Read the data
d3.csv("https://raw.githubusercontent.com/holtzy/data_to_viz/master/Example_dataset/3_TwoNumOrdered_comma.csv",
// When reading the csv, I must format variables:
function(d){
return { date : d3.timeParse("%Y-%m-%d")(d.date), value : d.value }
},
// Now I can use this dataset:
function(data) {
data = [data[0]]
// Add X axis --> it is a date format
var x = d3.scaleTime()
.domain(d3.extent(data, function(d) { return d.date; }))
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return +d.value; })])
.range([ height, 0 ]);
svg.append("g")
.call(d3.axisLeft(y));
// Add the area
svg.append("path")
.datum(data)
.attr("fill", "#cce5df")
.attr("stroke", "#69b3a2")
.attr("stroke-width", 1.5)
.attr("d", d3.area()
.x(function(d) { return x(d.date) })
.y0(y(0))
.y1(function(d) { return y(d.value) })
)
})
</script>
I would expect that chart to look something like:
If you inspect the element the path element you can see it is rendering, just 0 width/height:

Bar Chart Labels stuck in top left corner

I am trying to make simple chart right now importing data from a CSV. Everything on the chart is working great except for the labels. In element inspect I can see that they are being appended and that their x and y coordinates are even correct, but for some reason they are all trapped in the top left corner in the SVG itself.
I have tried changing the x placement function at first because I thought it just wasn't giving the labels a x position, but upon further inspection the labels have the correct metadata.
//Graph Dimensions
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 1000 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
//Set Ranges
var x_scale = d3.scaleBand()
.range([0, width])
.padding(0.1);
var y_scale = d3.scaleLinear()
.range([height, 0]);
//Create SVG object
var svg = d3.select('#chart')
.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 + ")");
//Retrieve data
d3.csv('sales.csv').then(function(data){
//Set domains based on data
x_scale.domain(data.map(function(d) { return d.month; }));
y_scale.domain([0, d3.max(data, function(d) { return d.sales; })]);
//Create bars
svg.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x_scale(d.month); })
.attr("width", x_scale.bandwidth())
.attr("y", function(d) { return y_scale(d.sales); })
.attr("height", function(d) { return height - y_scale(d.sales); });
//Create labels
svg.selectAll('text')
.data(data)
.enter().append('text')
.attr('class', 'label')
.attr("x", function(d) { return x_scale(d.month); })
.attr("y", function(d) { return y_scale(d.sales); })
.attr( 'font-size', 14 )
.attr( 'fill', '#555555' )
.attr( 'text-anchor', 'middle' );
//Add Axes
svg.append("g") //X Axis
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x_scale));
svg.append("g") //Y Axis
.call(d3.axisLeft(y_scale));
})
The only thing im looking for is the labels actually appearing. I can change their location later if needed.

cant figure out d3.js error

var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scaleBand()
.range([0, width])
.padding(0.1);
var y = d3.scaleLinear()
.range([height, 0]);
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 + ")");
var temp = [ {"Gender":"Male","count":5}, {"Gender":"Female","count":2}];
var data=[]
data.push(temp);
x.domain(data.map(function(d) { return d.Gender; }));
y.domain([0, d3.max(data, function(d) { return d.count; })]);
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(data) { return x(data.Gender); })
.attr("width", x.bandwidth())
.attr("y", function(d) { return y(data.count); })
.attr("height", function(d) { return height - y(data.count); });
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
svg.append("g")
.call(d3.axisLeft(y));
<style> /* set the CSS */
.bar { fill: steelblue; }
</style>
<!DOCTYPE html>
<body><script src="//d3js.org/d3.v4.min.js"></script></body>
I want a graph to plot male and female count. count in y-axis and gender in x-axis
my d3.js index.html
where data_json is the this data
[ {"Gender":"Male","count":5}, {"Gender":"Female","count":2}];
im getting graph for only 1 set i.e {"Gender":"Male","count":5} if i set data_json to the same else only axis is displayed.
but not together in same graph. Im new to d3.js and unable to figure out the solution. please help.
Your error is stems from this:
var temp = [ {"Gender":"Male","count":5}, {"Gender":"Female","count":2}];
var data=[]
data.push(temp);
The d3 .data method takes an array. Combined with an enter selection, one element can be appended per item in the array. temp is already an array, by pushing it to data you are making an array like the follows:
[[ {"Gender":"Male","count":5}, {"Gender":"Female","count":2}]]
This array has only one item, a sub-array. The sub-array is really what you want though. This is also creates problems when using the scales:
x.domain(data.map(function(d) { return d.Gender; }));
As each datum (and there is only one) comprises of an array, d.Gender will be undefined, d[1] will be defined.
Instead, use your temp array as your dataset without pushing it into a new array. Then modify the y, x, and height values of each rect to access d.count or d.Gender rather than data.Gender or data.count (as data.count is undefined, and also not datum specific, while d.count is the count associated with the datum bound to each rect).
Take a look at the snippet below which makes these changes:
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 500 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var x = d3.scaleBand()
.range([0, width])
.padding(0.1);
var y = d3.scaleLinear()
.range([height, 0]);
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 + ")");
var data = [ {"Gender":"Male","count":5}, {"Gender":"Female","count":2}];
x.domain(data.map(function(d) { return d.Gender; }));
y.domain([0, d3.max(data, function(d) { return d.count; })]);
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(data) { return x(data.Gender); })
.attr("width", x.bandwidth())
.attr("y", function(d) { return y(d.count); })
.attr("height", function(d) { return height - y(d.count); });
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
svg.append("g")
.call(d3.axisLeft(y));
<style> /* set the CSS */
.bar { fill: steelblue; }
</style>
<!DOCTYPE html>
<body><script src="//d3js.org/d3.v4.min.js"></script></body>

D3 line and chart graphs on the same page breaking

I am trying to render 2 graphs on the same page: chart graph and a linear graph
in html i have two divs which bind to the 2 different graphs respectively like so:
<div id="svg-container">
<div id="svg-container-avg">
The first graph that i import in html is this linear 'average' graph that just doesn't display the path or the x axis.
It works fine if i delete the chart graph which is imported straight after. I would automatically assume that there are some sort of dependencies between the two graphs, but i cannot find anything...
my first import, linear graph: scns-avg.js file contains this
// Set the dimensions of the canvas / graph
var margin = {
top: 30,
right: 20,
bottom: 70,
left: 50
},
width = 800 - margin.left - margin.right,
height = 350 - margin.top - margin.bottom;
var x = d3.scale.linear().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
var valueline = d3.svg.line()
.x(function(d) {
return x(d.scnsID);
})
.y(function(d) {
return y(d.average);
});
var svg2 = d3.select("#svg-container-avg")
.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 + ")");
// Get the data
d3.json("scns-avg-data-retrieval.php", function(error, data) {
data.forEach(function(d) {
d.scnsID = d.scnsID;
d.average = +d.average;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) {
return d.scnsID;
}));
y.domain([0, d3.max(data, function(d) {
return d.average;
})]);
svg2.append("path") // Add the valueline path.
.style("stroke", "rgba(13, 183, 196, 0.9)")
.attr("d", valueline(data));
svg2.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 3.5)
.attr("cx", function(d) {
return x(d.scnsID);
})
.attr("cy", function(d) {
return y(d.average);
})
svg2.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg2.append("g") // Add the Y Axis
.attr("class", "y axis")
.call(yAxis);
});
My Data structor in JSON:
[{"date":"11-Mar-16","average":"3.18","scnsID":"2"},{"date":"12-Mar-16","average":"3.09","scnsID":"3"},{"date":"15-Mar-16","average":"3.16","scnsID":"4"},{"date":"17-Mar-16","average":"3.20","scnsID":"5"}]
When trying to run the html page with both graphs being imported the scns-avg.js throws up in the console Error: Invalid value for attribute d="M43,2.8124999999999902LNaN,15.46875000000002LNaN,5.6249999999999805L471,0"
which points to valueline(data) in this part of the code
svg2.append("path")
.style("stroke", "rgba(13, 183, 196, 0.9)")
.attr("d", valueline(data));
That is dependent on this piece of code, so the problem must be lying here, but for 6 hours now i can't find the solution to this..
var valueline = d3.svg.line()
.x(function(d) { return x(d.scnsID); })
.y(function(d) { return y(d.average); });
My chart graph that gets imported next and if disabled the linear graph renders properly is as follows below:
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .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")
.ticks(5);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>Frequency:</strong> <span style='color:rgba(13, 183, 196, 0.9)'>" + d.average + "</span>";
});
var svg = d3.select("#svg-container").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 + ")");
svg.call(tip);
// Get the data
d3.json("scns-data-retrieval.php", function(error, data) {
if (error) throw error;
data.forEach(function(d) {
d.question = d.question;
d.average = +d.average;
});
x.domain(data.map(function(d) { return d.question; }));
y.domain([0, 5]);
legendSpace = width/5;
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x", 0 - (height / 3))
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Level of need for help:");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.question); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.average); })
.attr("height", function(d) { return height - y(d.average); })
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
});
function type(d) {
d.average = +d.average;
return d;
};
data structure in JSON:
[{"question":1,"average":3.3333333333333},{"question":2,"average":2.5},{"question":3,"average":4},{"question":4,"average":2.75},{"question":5,"average":2.75},{"question":6,"average":2.75},{"question":7,"average":3},{"question":8,"average":3},{"question":9,"average":2.75},{"question":10,"average":3.25},{"question":11,"average":3.25},{"question":12,"average":3.5},{"question":13,"average":3},{"question":14,"average":3.25},{"question":15,"average":3.5},{"question":16,"average":3.5},{"question":17,"average":3.25},{"question":18,"average":3.75},{"question":19,"average":3.5},{"question":20,"average":3},{"question":21,"average":3},{"question":22,"average":3.5},{"question":23,"average":3.25},{"question":24,"average":3.75},{"question":25,"average":3.75},{"question":26,"average":3.75},{"question":27,"average":3.5},{"question":28,"average":2.75},{"question":29,"average":2.25},{"question":30,"average":3.5},{"question":31,"average":3},{"question":32,"average":3}]
I can't do this anymore and it's driving me crazy so i am now turning to the power of stackoverflow!
After some mad research and trial and error i have fixed this problem.
When you are trying to render more than 1 graph, if they are of different type and not lets say - multiple linear graphs, but something like a linear graph with a bar chart the variable scope is getting mixed up and it's not restricted to the javascript file once it's imported into html.
To fix this problem every graph js file has to wrapped in (function(){ All your graph code goes here })();
This restricts the variable scope to just the function.

d3 javascript change range of the y axis

I'm a beginner with d3 javascript and I don't know how to change the y axis on this grouped bar chart:
http://bl.ocks.org/mbostock/3887051 Data + code can be found here
This is the code of the site & the data of the grouped bar chart:
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.x.axis path {
display: none;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis() //creating a generic axis function//
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
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 + ")");
d3.csv("gender_ratio.csv", function(error, data) {
var ageNames = d3.keys(data[0]).filter(function(key) { return key !== "Perioden"; });
data.forEach(function(d) {
d.ages = ageNames.map(function(name) { return {name: name, value: +d[name]*1000}; });
});
x0.domain(data.map(function(d) { return d.Perioden; }));
x1.domain(ageNames).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(d) { return d3.max(d.ages, function(d) { return d.value; }); })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Population");
var Perioden = svg.selectAll(".Perioden")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x0(d.Perioden) + ",0)"; });
Perioden.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.name); });
var legend = svg.selectAll(".legend")
.data(ageNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
});
</script>
Data:
State,Under 5 Years,5 to 13 Years,14 to 17 Years,18 to 24 Years,25 to 44 Years,45 to 64 Years,65 Years and Over
CA,2704659,4499890,2159981,3853788,10604510,8819342,4114496
TX,2027307,3277946,1420518,2454721,7017731,5656528,2472223
NY,1208495,2141490,1058031,1999120,5355235,5120254,2607672
FL,1140516,1938695,925060,1607297,4782119,4746856,3187797
IL,894368,1558919,725973,1311479,3596343,3239173,1575308
PA,737462,1345341,679201,1203944,3157759,3414001,1910571
If your x-axis is regular old numeric data, you should be using a linear scale not an ordinal. Ordinal is meant for discrete values (think a, b, c or x, y, z or tom, dick, harry) while linear is meant for continuous data (think 1,2,3 or 50, 100, 150):
var x = d3.scale.linear()
.range([0, width])
.domain([1650, 1700]);
In d3 speak, range is the pixel span of your data (from min to max), while domain is the user-space span of your data (the min and max of your data values). The scale that's returned then maps your user space data to it's pixel space position.
Below is a heavily commented example of a simple d3 bar graph:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.x.axis path {
display: none;
}
</style>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// sample data with x and y values
// d3 likes arrays of objects
var data = [
{
x: 1660,
y: 1
},{
x: 1670,
y: 2
},{
x: 1680,
y: 3
},{
x: 1690,
y: 4
}
];
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0,width]) // our pixel span
.domain([1650, 1700]); // our user space data span
var y = d3.scale.linear()
.range([height, 0]) // same thing as x, pixel span
.domain([0,5]); // user space space
// marry the scale to the axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
// set up our svg tag
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 + ")");
// draw x axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// draw y axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// get a nice bar width
// it is the width of our axis divided by the number of ticks
var barWidth = (width / xAxis.ticks()[0]);
// draw the bars
var state = svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class","bar")
.attr("width", barWidth)
.attr("x", function(d) { return x(d.x) - (barWidth / 2); }) // center it on tick
.attr("y", function(d) { return y(d.y); }) // y is the top of the bar
.attr("height", function(d) { return height - y(d.y); }); // and height goes to axis
</script>
</body>
</html>

Categories