Creating axis from array of strings in D3? - javascript

I've created a basic bar chart in d3 from an array of integers.
let maxWidth = $('body').width();
let chartWidth = maxWidth * 0.75; // also arbitrary
let chartHeight = chartWidth / _GoldenRatio;
let barWidth = (chartWidth / dataArray.length) - horizontalSpacing;
let barHeightUnit = chartHeight / d3.max(dataArray);
let mainChart = d3.select(containerSelector)
.append('svg')
.attr('width', chartWidth)
.attr('height', chartHeight);
mainChart.selectAll("rect")
.data(dataArray)
.enter()
.append("rect")
.attr("class", "bar")
.attr("height", function(d, i) { return (d * barHeightUnit) })
.attr("width",barWidth)
.attr("x", function(d, i) { return i * (barWidth + horizontalSpacing) + horizontalSpacing })
.attr("y", function(d, i) { return chartHeight - (d * barHeightUnit) });
I do this a few times with a few different arrays. Each list of integers corresponds with a list of strings that I'd like to display along the x-axis. Each string will lie below one bar to label it.
I can't figure out a way to do this easily with d3 or d3-axis. What is an easy way to construct an axis from this list of strings?

Hope this snippet using d3.v4 helps.
var margin = {
top: 100,
right: 100,
bottom: 100,
left: 100
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scaleBand()
.range([0, width])
.domain(["apple", "orange", "banana", "grapefruit"]);
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 + ")");
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
<script src="https://d3js.org/d3.v4.min.js"></script>

Related

Legend and label not rendering on svg

I am creating a visualisation using d3.js and svg images in PowerBi (this uses version 3 of d3).
I have got my visual working, however my legend is not rendering. I tested this in a browser, and the legend items appear in the elements of the page, but just aren't showing up.
My code for the legend items are
var pbi = {
width:1108,
height:636,
colors:[
"#A70240",
"#4A2366",
"#009A44",
"#A0D081",
"#01B5BB",
"#137B88",
"#5D6771",
"#CDC8C1"
]
var margin = {top: 20, right: 30, bottom: 30, left: 140},
width = pbi.width - margin.left - margin.right,
height = pbi.height - margin.top - margin.bottom,
legendleft = pbi.width - margin.right;
var ly = d3.scale.ordinal() // For legend
.rangeRoundBands([0, height], barPad, barOuterPad);
var svg = d3.select("#chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
----
ly.domain(rData.map(function(d) { return d.milestone; })); // Legend
var milestoneMap = {}; // Maps years to colours
var legendArray = []; // For legend
rData.forEach(function (d) {
var entry = d.year;
var rowEntry = {
entry: entry, // Axis label
milestone: d.milestone, // For colour lookup
date: d.date, // For X position of points
y: y(entry)
}
if (!(d.milestone in milestoneMap)) {
// First occurrence of each year saved to legend
legendArray.push({milestone: d.milestone});
rowArray.push(rowEntry);
var legend = svg.append("g").attr("id", "legend")
.attr("transform", "translate(" + legendleft + "," + margin.top + ")").selectAll(null)
.data(legendArray)
.enter();
// Legend agency labels
legend.append("text")
.attr("class", "milestoneLabel")
.attr("x", 25)
.attr("y", function(d) { return ly(d.milestone)+5; })
.style("stroke", "black")
.style("stroke-width", .3)
.text(function(d) { return d.milestone; });
legend.append("circle")
.attr("class", "legend")
.attr("cx", 12)
.attr("cy", function(d) { return ly(d.milestone); })
.attr("r", 8)
.style("stroke", "black")
.style("stroke-width", 1)
.style("fill", function(d, i) { return milestoneMap[d.milestone]; });
This is omitting code that calculates all other elements.
Why is it that the legend circle and label is appearing as an element on the page but isn't rendering anything?
Thanks
Turns out, I just needed to add my legend left to my svg.
From
var svg = d3.select("#chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
to
var svg = d3.select("#chart")
.attr("width", width + margin.left + margin.right + legendleft)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

D3 - display x value data ticks as percentages

Have a line graph shown as here: https://imgur.com/1kadiJF
Would love the x-axis to display a & sign after each tick, 10%, 20%, etc. How does one accomplish this?
I've read up on a format method one can use, as well as saw people using Math and actually doing a conversion, but figure there must be a quick way to add a string % after each tick, surely!
Using V4 of D3 here
<script>
// set the dimensions and margins of the graph
const formatPercent = d3.format(".0%")
const margin = {top: 20, right: 30, bottom: 60, left: 250},
width = 1000 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// append the svg object to the body of the page
const svg = d3.select(".line")
.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.json("./data/linedata.json", function(data) {
// Add X axis
const x = d3.scaleLinear()
.domain([0, 100])
.range([ 0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll("text").attr('class', 'xaxis')
.attr("transform", "translate(0,0 )")
.style("text-anchor", "end");
// Y axis
const y = d3.scaleBand()
.range([ 0, height ])
.domain(data.map(function(d) { return d.desc; }))
.padding(.1)
svg.append("g").attr('class', 'xaxis')
.call(d3.axisLeft(y))
//Bars
svg.selectAll("myRect")
.data(data)
.enter()
.append("rect")
.attr("x", x(0) )
.attr("y", function(d) { return y(d.desc); })
.attr("width", function(d) { return x(d.total); })
.attr("height", y.bandwidth() )
.attr("fill", "#008080")
})
</script>
Have you tried using the tickFormat function?
Something like this should work:
.tickFormat(d => d + "%")

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>

Rects won't show up on d3 histogram

I'm using Polymer to render some d3 charts. When the Polymer is initially rendered I only draw a graph with axes and no data, since the data comes later once the API calls succeed. However, when I get around to adding the 'rect' elements in the svg, despite them showing up in the Chrome devtools element inspector, they don't show up in the chart itself.
dataChanged: function() {
var data = this.data;
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = this.width - margin.left - margin.right,
height = this.height - margin.top - margin.bottom;
// format the data
data.forEach(function(d) {
d.date = d3.isoParse(d.date);
});
// set the ranges
var x = d3.scaleTime()
.domain(d3.extent(data, function(d) { return d.date; }))
.rangeRound([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var svg = d3.select(this.$.chart);
var histogram = d3.histogram()
.value(function(d) { return d.date; })
.domain(x.domain())
.thresholds(x.ticks(d3.timeMonth));
var bins = histogram(data);
y.domain([0, d3.max(bins, function(d) { return d.length; })]);
var t = d3.transition()
.duration(750)
.ease(d3.easeLinear);
svg.selectAll("rect")
.data(bins)
.enter().append("rect")
.attr("class", "bar")
.attr("x", 1)
.attr("transform", function(d) {
return "translate(" + x(d.x0) + "," + y(d.length) + ")";
})
.attr("width", function(d) { return x(d.x1) - x(d.x0) -1 ; })
.attr("height", function(d) { return height - y(d.length); });
svg.select(".xAxis")
.transition(t)
.call(d3.axisBottom(x));
svg.select(".yAxis")
.transition(t)
.call(d3.axisLeft(y));
},
ready: function() {
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = this.width - margin.left - margin.right,
height = this.height - margin.top - margin.bottom;
// set the ranges
var x = d3.scaleTime()
.domain([new Date(2010, 6, 3), new Date(2012, 0, 1)])
.rangeRound([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
// Add the SVG to my 'chart' div.
var svg = d3.select(this.$.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 + ")");
// Add the X Axis
svg.append("g")
.attr("class","xAxis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.attr("class","yAxis")
.call(d3.axisLeft(y));
}
ready() gets called upon rendering, dataChanged() when the parent component passes a chunk of data down.
The axes get rendered correctly, with the right transitions and the right dimensions, but the rects don't. They show up in the chrome element inspector with a 0x17 size, even though this is what they look like: <rect class="bar" x="1" transform="translate(0,24.06417112299465)" width="101" height="275.93582887700535"></rect>
In your ready function, you are grabbing your div creating an svg element adding a g element and then appending your axis to that g.
In your dataChanged function, you are grabbing your div and appending rects to it.
See the disconnect? You can't parent svg to HTML.
In ready do this:
var svg = d3.select(this.$.chart).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("id", "canvas")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
In dataChanged:
var svg = d3.select("#canvas");
This will allow you to "find" the appropriate g to append your rects to.

How to zoom with d3

I'm drawing a simple bar chart with d3, and I have an x axis setup and have the brush setup so that I can brush to "select" a group of the bars. I would like to then drill down and scale the chart to contain only these bars. The following code works and the colors of the bars inside and touched by the brush turn the right color, but I can't make the thing zoom.
I have looked at this: http://bl.ocks.org/mbostock/1667367 and a bunch of other stuff and just cannot figure it out.
Here is a fiddle, can someone show me how to simply zoom the darn thing?
var brush;
function go3()
{
var dataset = [];
var m = 40;
var count = 500;
dataset.push(m);
for (var i = 0; i < 150; i++) { //Loop 25 times
var newNumber = Math.random() * m; //New random number (0-30)
dataset.push(newNumber); //Add new number to array
}
margin = {top: 10, right: 10, bottom: 30, left: 10},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom
w = width;
h = height;
yScale = d3.scale.linear().domain([0, d3.max(dataset)]).range([0, h * .95]);
xScale = d3.scale.linear()
.range([0, w])
.domain([0, w]);
var xAxis = d3.svg.axis().scale(xScale).orient("bottom");
console.log("Max: " + d3.max(dataset));
svg = d3.select("body")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
;
svg.append("rect")
.attr("stroke", "grey")
.attr("stroke-width", 1)
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.attr("fill", "#FFFFFF")
.classed("main-container", true);
svg.append("rect")
.attr("width", w)
.attr("height", h)
.attr("stroke", "grey")
.attr("stroke-width", 0)
.attr("fill", "#EEFFEE")
.attr("x", margin.left)
.attr("y", margin.top)
.classed("brushable-container", true)
;
xAxisGroup = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + (h + margin.top) + ")")
.call(xAxis)
padding=2;
rects = svg.append("g").selectAll(".brushable")
.data(dataset)
.enter()
.append("rect")
.classed("brushable", true);
brush = d3.svg.brush()
.x(xScale)
.on("brush", brushmove)
.on("brushend", brushend);
context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("class", "brush")
.call(brush)
.selectAll('rect')
.attr('height', h);
barWidth = w / dataset.length;
console.log("Width: " + barWidth);
rects
.attr("width", 4)
.attr("height", function(d, i){ return yScale(d)})
.attr("stroke", "yellow")
.attr("stroke-width", .3)
.attr("x", function(d, i){ return (i * barWidth + margin.left) })
.attr("y", function(d, i){ return h - yScale(d) + margin.top} )
;
}
function brushend(){
var extent = brush.extent();
var min = extent[0] >= extent[1] ? extent[1] : extent[0];
var max = extent[0] >= extent[1] ? extent[0] : extent[1];
var lolobb = d3.selectAll("rect.brushable");
var lob = lolobb[0];
console.log(min + " - " + max);
var i = 0;
while( i < lob.length ){
var bbb = lob[i];
try {
var p = parseFloat(bbb.attributes.x.value);
if(min <= p && max >= p) {
d3.select(bbb).attr("fill", "#00FF00");
} else {
d3.select(bbb).attr("fill", "#000000");
}
i++;
} catch(r) {
console.log("BBB");
console.log(bbb);
console.log("Error with " + i);
console.log(typeof(bbb));
console.log(r);
}
}
console.log(min + " - " + max);
console.log(lolobb);
}
function brushmove() {
var extent = brush.extent();
}
You can do this by adding calling a zoom function on "body", you can do so by doing:
svg = d3.select("body")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.call(d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoom))
;
The zooming part is:
.append("g")
.call(d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoom))
Then add the zoom() function:
function zoom() {
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
Here is a Fiddle Example. Use the scroll wheel to zoom.

Categories