I am just starting to learn D3, and have been following a tutorial to create this piece of code.
I created a couple of bars and intend to create an x axis for my graph.
The problem is when I add the ".call(xAxis)" to my canvas the browsers won't show me anything and I get the following error in the console:
Uncaught TypeError: Cannot call method 'copy' of undefined d3.min.js:5
(anonymous function) d3.min.js:5
(anonymous function) d3.min.js:3
R d3.min.js:1
da.each d3.min.js:3
n d3.min.js:5
da.call d3.min.js:3
(anonymous function)
Can anyone please help me with what's wrong? I really can't understand what's missing or what I'm doing wrong!
<!doctype html>
<html>
<head>
<title>Intro to D3</title>
<script src="d3.min.js"></script>
<head>
<body>
<script>
var width = 1024;
var height = 768;
var dataArray = [20, 40, 60, 120];
var xAxis = d3.svg.axis()
.scale(widthScale);
var widthScale = d3.scale.linear()
.domain([0, 120])
.range([0, width]);
var color = d3.scale.linear()
.domain([0, 120])
.range(["red", "blue"]);
var canvas = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(20, 0)")
.call(xAxis);
var bars = canvas.selectAll("rect")
.data(dataArray)
.enter()
.append("rect")
.attr("width", function(d){ return widthScale(d); })
.attr("height", 20)
.attr("fill", function(d){ return color(d); })
.attr("y", function(d, i){ return i*30; });
</script>
</body>
</html>
The problem is that you're assigning the scale to the axis before defining it. Doing it in this order works fine:
var widthScale = d3.scale.linear()
.domain([0, 120])
.range([0, width]);
var xAxis = d3.svg.axis()
.scale(widthScale);
You probably also want to append the bars to the SVG itself, not the g element that contains the axis. To do that, simply split the definition of canvas and the appending of the axis:
var canvas = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
canvas.append("g")
.attr("transform", "translate(20, 0)")
.call(xAxis);
Complete demo here.
Related
I need to display micromoles per liter (µmol/L) in my chart's tickFormat, but when I pass in "µmol/L", it shows the characters "µ" instead of the symbol for mu. How do I get it to render the symbol?
In that case, you shouldn't use an HTML entity. Once you're dealing with an SVG , use this:
\u00B5
Check this snippet:
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 200);
var scale = d3.scaleLinear()
.range([40, 460])
.domain([0, 100]);
var axis = d3.axisBottom(scale)
.tickFormat(function(d){ return d + "\u00B5mol/L"})
.ticks(5);
svg.append("g")
.attr("transform", "translate(0,100)")
.call(axis);
text { font-size: 14px;}
<script src="https://d3js.org/d3.v4.min.js"></script>
I need to display micromoles per liter (µmol/L) in my chart's tickFormat, but when I pass in "µmol/L", it shows the characters "µ" instead of the symbol for mu. How do I get it to render the symbol?
In that case, you shouldn't use an HTML entity. Once you're dealing with an SVG , use this:
\u00B5
Check this snippet:
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 200);
var scale = d3.scaleLinear()
.range([40, 460])
.domain([0, 100]);
var axis = d3.axisBottom(scale)
.tickFormat(function(d){ return d + "\u00B5mol/L"})
.ticks(5);
svg.append("g")
.attr("transform", "translate(0,100)")
.call(axis);
text { font-size: 14px;}
<script src="https://d3js.org/d3.v4.min.js"></script>
I'm trying to learn how to use the d3.js hexbin plugin.
I started with the example: http://bl.ocks.org/mbostock/4248145 , and I'm adapting it.
I have a data set of points between [0,0] and [600,600]. I want to output them to a 300,300 graph.
My graph doesn't look right. It looks like the data isn't being scaled properly and the graph is only showing 1/4 of the data. Can someone tell me what's wrong? I've read a book about using d3, but I don't have very much experience using it.
Jsfiddle of my hexbin
var graph_width = 300;
var graph_height = 300;
var data_width = 600;
var data_height = 600;
var randomX = d3.random.normal(data_width / 2, 80),
randomY = d3.random.normal(data_height / 2, 80),
points = d3.range(2000).map(function() { return [randomX(), randomY()]; });
var color = d3.scale.linear()
.domain([0, 20])
.range(["white", "steelblue"])
.interpolate(d3.interpolateLab);
var hexbin = d3.hexbin()
.size([graph_width, graph_height])
.radius(20);
var x = d3.scale.linear()
.domain([0, data_width])
.range([0, graph_width]);
var y = d3.scale.linear()
.domain([0, data_height])
.range([0, graph_height]);
var svg = d3.select("body").append("svg")
.attr("width", graph_width)
.attr("height", graph_height)
.append("g");
svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("class", "mesh")
.attr("width", graph_width)
.attr("height", graph_height);
svg.append("g")
.attr("clip-path", "url(#clip)")
.selectAll(".hexagon")
.data(hexbin(points))
.enter().append("path")
.attr("class", "hexagon")
.attr("d", hexbin.hexagon())
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.style("fill", function(d) { return color(d.length); });
I think I understand. You have data values in in the range of 0 to 600 but want those mapped to x/y positions in the range of 0 to 300.
If that's it then scale the points:
var x = d3.scale.linear()
.domain([0, data_width])
.range([0, graph_width]);
var y = d3.scale.linear()
.domain([0, data_height])
.range([0, graph_height]);
var randomX = d3.random.normal(data_width / 2, 80),
randomY = d3.random.normal(data_height / 2, 80),
points = d3.range(2000).map(function() { return [x(randomX()), y(randomY())]; });
Updated fiddle.
Hope someone can help, I have a slight problem in that the horizontal axis label 100 gets cut off the end of the stacked horizontal barchart. I can't seem to figure out what is wrong in the code. Thanks in advance for your help. Please see code below.
<!DOCTYPE html>
-->
<html>
<head>
<title>Horizontal stacked bar</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<script type="text/javascript" src="d3/d3.js"> </script>
<style>
.axis{
font-size: 14px;
}
#h{
}
</style>
</head>
<body>
<script>
var margin = {
top: 12,
left: 15,
right: 15,
bottom: 14
};
var w = 500 - margin.left - margin.right;
var h = 300 - margin.top - margin.bottom;
var dataset = [
[
{x:0,y:20}
],
[
{x:0,y:30}
],
[
{x:0,y:50}
]
];
//Set up stack method
var stack = d3.layout.stack();
//Data, stacked
stack(dataset);
//Set up scales
var xScale = d3.scale.linear()
.domain([0,d3.max(dataset, function(d) {return d3.max(d, function(d)
{return d.y0 + d.y;}); }) ])
// note use of margin + right to get axis to scale width
.range([0, w + margin.right]);
var yScale = d3.scale.ordinal()
.domain(d3.range(dataset[0].length))
.rangeRoundBands([0,w ], 0.05);
//Easy colors accessible via a 10-step ordinal scale
var colors = d3.scale.category10();
//or make your own colour palet
var color = d3.scale.ordinal()
.range(["#1459D9", "#148DD9", "#87ceeb", "#daa520"]);
// good site for colour codes http://www.colorpicker.com/113EF2
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
;
// Add a group for each row of data
var groups = svg.selectAll("g")
.data(dataset)
.enter()
.append("g")
.style("fill", function(d,i){return color(i);})
;
// Add a rect for each data value
var rects = groups.selectAll("rect")
.data(function(d) { return d; })
.enter()
.append("rect")
.attr("x", function(d) {return xScale(d.y0) ;}) //+99 will move axis right
.attr("y", 180)
.attr("height", 90)
.attr("width", yScale.rangeBand());
//Add an axis
var xAxis = d3.svg.axis()
.scale(xScale);
svg.append("g")
.call(xAxis)
;
</script>
</body>
</html>
You are really better off using the xScale for both dimensions, x and y. After all, your y is really a width. Here is what I mean:
...
//Set up scales
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function (d) {
return d3.max(d, function (d) {
return d.y0 + d.y;
});
})])
.range([0, w]); // no need to tamper with margins since w already accounts for that
...
// Add a rect for each data value
var rects = groups.selectAll("rect")
.data(function (d) {return d;})
.enter()
.append("rect")
.attr("x", function (d) {
return xScale(d.y0); // use x scale
})
.attr("y", 50)
.attr("height", 50)
.attr("width", function (d) {
return xScale(d.y); // use x scale
})
...
And here is the updated FIDDLE. You can go ahead and make changes to the right margin value and any of your data y values (I placed comments in the code to that effect) and you can see that this solution scales well.
I have currently have a quick test for a graph I'm about to create for website and I have made the most basic functionality. I have a graph, a 4 elements and an x and a y axis and a zoom functionality.
My problem lies in the fact that when I zoom on the graph, the elements are able to reach the axis and overlap it. I've pasted my source code below
//Setting generic width and height values for our SVG.
var margin = {top: 60, right: 0, bottom: 60, left: 40},
width = 1024 - 70 - margin.left - margin.right,
height = 668 - margin.top - margin.bottom;
//Other variable declarations.
//Creating scales used to scale everything to the size of the SVG.
var xScale = d3.scale.linear()
.domain([0, 1024])
.range([0, width]);
var yScale = d3.scale.linear()
.domain([1, 768])
.range([height, 0]);
//Creates an xAxis variable that can be used in our SVG.
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
//Zoom command ...
var zoom = d3.behavior.zoom()
.x(xScale)
.y(yScale)
.scaleExtent([1, 10])
.on("zoom", zoomTargets);
// The mark '#' indicates an ID. IF '#' isn't included argument expected is a tag such as "svg" or "p" etc..
var SVG = d3.select("#mainSVG")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
//Create background. The mouse must be over an object on the graph for the zoom to work. The rectangle will cover the entire graph.
var rect = SVG.append("rect")
.attr("width", width)
.attr("height", height);
//Showing the axis that we created earlier in the script for both X and Y.
var xAxisGroup = SVG.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
var yAxisGroup = SVG.append("g")
.attr("class", "y axis")
.call(yAxis);
//This selects 4 circles (non-existent, there requires data-binding) and appends them all below enter.
//The amount of numbers in data is the amount of circles to be appended in the enter() section.
var circle = SVG
.selectAll("circle")
.data([40,100,400,1900])
.enter()
.append("circle")
.attr("cx",function(d){return xScale(d)})
.attr("cy",function(d){return yScale(d)})
.attr("r",20);
//Resets zoom when click on circle object. Zoom work now, should be changed to a button instead of click on circle though.
SVG.selectAll("circle").on("click", function() {
zoom.scale(1);
zoom.translate([0,0]);
zoomTargets();
});
function zoomTargets() {
SVG.select(".x.axis").call(xAxis);
SVG.select(".y.axis").call(yAxis);
SVG.selectAll("circle").attr("cx",function(d){return xScale(d)}).attr("cy",function(d){return yScale(d)});
}
function resetZoom() {
zoom.scale(1);
zoom.translate([0,0]);
zoomTargets();
}
I've tried using "append("g2") before creating a circle to I can make g2 smaller than the entire svg, but that doesn't seem to work. As far as I have understood, you can just append a new element inside your existing one. I'm guessing I'm wrong since it hasn't worked for me.
Thank you in advance for your help.
Leave a small gap between the most extreme data point and the axis. In particular, you may want the range of your domain to take the margins into account:
var xScale = d3.scale.linear()
.domain([0, 1024])
.range([0, width-margin.right]);
var yScale = d3.scale.linear()
.domain([1, 768])
.range([height, margin.bottom]);