I am learning d3 js. I found a great book about the library. But I needed to translate the code examples from version 3 to 4.
Here is my code on version 4
<body>
<script type="text/javascript">
var width = 800;
var height = 500;
var dataset = [];
dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];
var xScale = d3.scaleBand()
.domain(d3.range(dataset.length))
.rangeRound([0, width], 0.5);
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset)])
.range([0, height]);
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i){return xScale(i);})
.attr("y", function(d){return height- yScale(d);})
.attr("width", xScale.bandwidth())
.attr("height", function(d){return yScale(d);})
.attr("fill", function(d){return "rgb(0,0,"+d*10+")";});
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d){return d;})
.attr("text-anchor", "middle")
.attr("x", function(d, i){return xScale(i) + xScale.bandwidth() / 2;})
.attr("y", function(d){return height - yScale(d) + 14;})
.attr("fill", "white");
</script>
</body>
Here is the outcome
As I understand, this
.rangeRound([0, width], 0.5);
should bring some gaps between the bars. But, I don’t' know why - there are no gaps!
What am I doing wrong?
I believe the new pattern in d3 v4 would be -
var xScale = d3.scaleBand()
.domain(d3.range(dataset.length))
.rangeRound([0, width])
.paddingInner(0.05);
Related
My question is based on a little modification in the code shown below. I code in Python, and HTML/CSS are new to me. I have the following code.
In the code below , modify the code such that on mouse out the values of the bars remain there, but they are white in color.
How can I accomplish the same. Many thanks. I am learning this.
//Width and height
var w = 600;
var h = 250;
var dataset = [5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
11, 12, 15, 20, 18, 17, 16, 18, 23, 25
];
var xScale = d3.scaleBand()
.domain(d3.range(dataset.length))
.rangeRound([0, w])
.paddingInner(0.05);
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset)])
.range([0, h]);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create bars
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => xScale(i))
.attr("y", d => h - yScale(d))
.attr("width", xScale.bandwidth())
.attr("height", d => yScale(d))
.attr("fill", d => "rgb(100, 0, " + (d * 10) + ")")
.on("mouseover", function(d) {
//Get this bar's x/y values, then augment for the tooltip
var xPosition = parseFloat(d3.select(this).attr("x")) + xScale.bandwidth() / 2;
var yPosition = parseFloat(d3.select(this).attr("y")) + 14;
//Create the tooltip label
svg.append("text")
.attr("id", "tooltip")
.attr("x", xPosition)
.attr("y", yPosition)
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("font-weight", "bold")
.attr("fill", "black")
.text(d);
})
.on("mouseout", () => d3.select("#tooltip").remove());
//Remove the tooltip
rect:hover {
fill: orange;
}
<script src="//d3js.org/d3.v5.min.js" charset="utf-8"></script>
How to make a clickable transition bar graph in d3 v4?
Current code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3: A bar chart that transitions to new data!</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<style type="text/css">
/* No style rules here yet */
</style>
</head>
<body>
<p>Click on this text to update the chart with new data values (once).</p>
<script type="text/javascript">
//Width and height
var w = 600;
var h = 250;
var dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];
var xScale = d3.scaleBand()
.rangeRound([0, w])
.padding(0.05);
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset)])
.range([0, h]);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create bars
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d);
})
.attr("width", xScale.bandwidth())
.attr("height", function(d) {
return yScale(d);
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
});
//Create labels
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d;
})
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return xScale(i) + xScale.bandwidth() / 2;
})
.attr("y", function(d) {
return h - yScale(d) + 14;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white");
//On click, update with new data
d3.select("p")
.on("click", function() {
//New values for dataset
dataset = [ 11, 12, 15, 20, 18, 17, 16, 18, 23, 25,
5, 10, 13, 19, 21, 25, 22, 18, 15, 13 ];
//Update all rects
svg.selectAll("rect")
.data(dataset)
.transition() // <-- This makes it a smooth transition!
.attr("y", function(d) {
return h - yScale(d);
})
.attr("height", function(d) {
return yScale(d);
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
});
//Update all labels
svg.selectAll("text")
.data(dataset)
.text(function(d) {
return d;
})
.attr("x", function(d, i) {
return xScale(i) + xScale.band() / 2;
})
.attr("y", function(d) {
return h - yScale(d) + 14;
});
});
</script>
</body>
</html>
Code above from:
https://github.com/alignedleft/d3-book/blob/master/chapter_09/05_transition.html
http://examples.oreilly.com/0636920026938/chapter_09/05_transition.html
I am getting errors after switching the code to v4. I fixed the errors I knew about but now I am getting these one errors in the JavaScript console:
Error: attribute x: Expected length, "NaN".
You are not setting the domain of the x scale:
var xScale = d3.scaleBand()
.domain(d3.range(dataset.lengh))
.rangeRound([0, w])
.padding(0.1);
Also, pay attention to xScale.band(), which doesn't exist: it should be xScale.bandwidth() instead.
Here is a working code, with the domain:
var w = 600;
var h = 250;
var dataset = [5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
11, 12, 15, 20, 18, 17, 16, 18, 23, 25
];
var xScale = d3.scaleBand()
.domain(d3.range(dataset.length))
.rangeRound([0, w])
.padding(0.1);
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset)])
.range([0, h]);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create bars
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d);
})
.attr("width", xScale.bandwidth())
.attr("height", function(d) {
return yScale(d);
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
});
//Create labels
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d;
})
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return xScale(i) + xScale.bandwidth() / 2;
})
.attr("y", function(d) {
return h - yScale(d) + 14;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white");
//On click, update with new data
d3.select("p")
.on("click", function() {
//New values for dataset
dataset = [11, 12, 15, 20, 18, 17, 16, 18, 23, 25,
5, 10, 13, 19, 21, 25, 22, 18, 15, 13
];
//Update all rects
svg.selectAll("rect")
.data(dataset)
.transition() // <-- This makes it a smooth transition!
.attr("y", function(d) {
return h - yScale(d);
})
.attr("height", function(d) {
return yScale(d);
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
});
//Update all labels
svg.selectAll("text")
.data(dataset)
.text(function(d) {
return d;
})
.attr("y", function(d) {
return h - yScale(d) + 14;
});
});
<script src="https://d3js.org/d3.v4.min.js"></script>
<p>Click on this text to update the chart with new data values (once).</p>
I'm building a bar chart in d3.js with an ordinal x-axis whose ticks should label the chart with text. Could anyone explain how the ordinal scale "maps" x ticks to the corresponding bar positions? Specifically, if I want to designate the x tick labels with an array of text values to the corresponding bars in a bar chart.
Currently I'm setting the domain as the following:
var labels = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t"];
var xScale = d3.scale.ordinal()
.domain(labels)
However, values of 1-19 are showing after the text labels.
As seen in this fiddle:
http://jsfiddle.net/chartguy/FbqjD/
Associated Fiddle Source Code:
//Width and height
var margin = {top: 20, right: 20, bottom: 30, left: 40};
var width = 600 - margin.left - margin.right;
var height= 500-margin.top -margin.bottom;
var w = width;
var h = height;
var dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];
var labels = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t"];
var xScale = d3.scale.ordinal()
.domain(labels)
.rangeRoundBands([margin.left, width], 0.05);
var xAxis = d3.svg.axis().scale(xScale).orient("bottom");
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset)])
.range([h,0]);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create bars
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return yScale(d);
})
.attr("width", xScale.rangeBand())
.attr("height", function(d) {
return h - yScale(d);
})
.attr("fill", function(d) {
return "rgb(0, 0, 0)";
});
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + 0 + ")")
.call(xAxis);
//Create labels
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d;
})
.attr("x", function(d, i) {
return xScale(i) + xScale.rangeBand() / 2;
})
.attr("y", function(d) {
return h - yScale(d) + 14;
});
You can set the tick values of an ordinal axis explicitly using d3.svg.axis().tickValues(*array*).
But this is an odd way to do it because it dangerously separates your keys and values, meaning you have to take care to manually align the scales and make sure that your data corresponds correctly. It helps to group the keys and values in a single object and then use the format:
axis.domain(array.map(function (d) { return d.value; }))
to map your axis domains.
I have reworked your data and fiddle to do it in what I see as the more d3 way. (Also note that I made some other changes just for fun, namely improved the margins and cleaned up the axis alignment, etc.)
I can't figure out how to properly create a histogram where there are both positive and negative values in the data array.
I've used the histogram example here http://bl.ocks.org/mbostock/3048450 as a base, and while the x axis values and ticks are correct, the bars are out to lunch.
Data
var values = [-15, -20, -22, -18, 2, 6, -26, -18, -15, -20, -22, -18, 2, 6, -26, -18];
X Scale
var x0 = Math.max(-d3.min(values), d3.max(values));
var x = d3.scale.linear()
.domain([-x0, x0])
.range([0, width])
.nice();
Check the jfiddle here: http://jsfiddle.net/tNdJj/2/
I assume it's something missing from the "rect" creations but I am not seeing it.
Using the example of histogram from the following question: Bar chart with negative values
I inversed x and y and adapted the display. Now you have a nice basis.
Here is the corresponding jsFiddle: http://jsfiddle.net/chrisJamesC/tNdJj/4/
Here is the relevant code:
var data = [-15, -20, -22, -18, 2, 6, -26, -18];
var margin = {top: 30, right: 10, bottom: 10, left: 30},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var y0 = Math.max(Math.abs(d3.min(data)), Math.abs(d3.max(data)));
var y = d3.scale.linear()
.domain([-y0, y0])
.range([height,0])
.nice();
var x = d3.scale.ordinal()
.domain(d3.range(data.length))
.rangeRoundBands([0, width], .2);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
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.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", function(d) { return d < 0 ? "bar negative" : "bar positive"; })
.attr("y", function(d) { return y(Math.max(0, d)); })
.attr("x", function(d, i) { return x(i); })
.attr("height", function(d) { return Math.abs(y(d) - y(0)); })
.attr("width", x.rangeBand());
svg.append("g")
.attr("class", "x axis")
.call(yAxis);
svg.append("g")
.attr("class", "y axis")
.append("line")
.attr("y1", y(0))
.attr("y2", y(0))
.attr("x1", 0)
.attr("x2", width);
Note: For simple visualizations like this, I would recommand using nvd3.js
The trick is that the demo code is overly optimistic, assuming that its input is positive:
bar.append("rect")
.attr("x", 1)
// .attr("width", x(data[0].dx) - 1) // Does the wrong thing for negative buckets.
.attr("width", x(data[0].x + data[0].dx) - 1)
.attr("height", function(d) { return height - y(d.y); });
http://jsfiddle.net/tNdJj/46/
I'm trying to display a dataset as a graph, but while the x and y axes are displaying correctly with respect to the x and y scales that I've set, the graph bars are not being drawn on the SVG canvas appropriately.
This is the graph that is displayed:
This is what I have for the dataset and svg components as written in CoffeeScript:
dataset = [[1992, 1], [1994, 3], [1995, 3], [1996, 1], [1997, 3], [1998, 6], [1999, 11], [2000, 36], [2001, 37],
[2002, 46], [2003, 171], [2004, 442], [2005, 514], [2006, 753], [2007, 756],
[2008, 660], [2009, 829], [2010, 903], [2011, 4425], [2012, 1635], [2013, 8]]
w = 800
h = 500
padding = 40
xScale = d3.scale.linear()
.domain([d3.min(dataset, (d)-> d[0]) - 1, d3.max(dataset, (d)-> d[0]) + 1])
.range([padding, w - padding * 2])
yScale = d3.scale.linear()
.domain([0, d3.max(dataset, (d)-> d[1])])
.range([h - padding, padding])
xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.tickFormat(d3.format("0"))
yAxis = d3.svg.axis()
.scale(yScale)
.orient('left')
svg = d3.select('body')
.append('svg')
.attr('width', w)
.attr('height', h)
svg.selectAll('.bar')
.data(dataset)
.enter()
.append('rect')
.attr('class', 'bar')
.attr('x', (d)-> xScale(d[0]))
.attr('y', (d)-> yScale(d[0]))
.attr('width', (d, i)-> w / dataset.length * i)
.attr('height', (d)-> d * 4)
svg.selectAll('text')
.data(dataset)
.enter()
.append('text')
.attr('x', (d)-> xScale(d[0]))
.attr('y', (d)-> yScale(d[1]))
svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(0,' + (h - padding) + ')')
.call(xAxis)
svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(' + padding + ',0)')
.call(yAxis)
From your code:
.attr('width', (d, i)-> w / dataset.length * i)
.attr('height', (d)-> d * 4)
Both have problems.
For width, you shouldn't multiply by i, or even have to depend on data. Need something more like:
.attr('width', widthPerYear) // don't need a function here
widthPerYear is something you need to decide on. Technically, it can be w/dataset.length, but that assumes dataset is the same length as the entire range.
For height, you need
.attr('y', (d)-> h - yScale(d[1]))