I'm totally newbie with D3.js and I've got a problem which returns me the message: "scale.domain is not a function".
I have made a component to build a Scatter Plot Graphic. In it's code there is no any call to the instrucction "scale.domain".
Here is the code:
import React, {Component} from "react";
import {Determinator, MultiLang} from "react-multi-language";
import * as d3 from "d3";
class ScatterPlotGraphics extends Component{
/**
* Some of the props received are mandatory and others not. Between mandatory properties we've got:
* idContainer: Name of the id container. This number must be unique for the web page where we draw the graphic
* lang: language of presentation of text
* data: Array of json objects with the information needed to build the graphic
* total_points: Total points of a team which are needed to make some calculus
*
* #param {*} props
*/
constructor(props){
super();
this.props = props;
this.state = {
loaded: true,
margin: {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width: 560,
height: 360
};
}
componentDidMount(){
let canvas = this.setCanvas();
let axis = this.setAxes();
this.setAxesToCanvas(canvas, axis);
this.setPointsToCanvas(canvas, this.state.data);
}
setAxes(){
let xRange = [0, this.state.width];
let yRange = [this.state.height, 0];
let xAxis = d3.axisBottom(xRange).tickFormat(function(d) { return d.p2_p; });
let yAxis = d3.axisLeft(yRange).tickFormat(function (d) { return d.p3_p; });
return {"xAxis" : xAxis, "yAxis" : yAxis};
}
setAxesToCanvas(canvas, axis){
//Add x-axis to the canvas
canvas.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0, " + this.state.height + ")") //move axis to the bottom of canvas
.call(axis.xAxis)
.append("text")
.append("class", "label")
.attr("x", this.state.width)
.attr("y", -6)
.style("text-anchor", "end") //right-justify text
.text("%2P");
//Add y-axis to the canvas
canvas.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("tranform", "rotate(-90)") //Although axis is rotated, text is not
.attr("y", 15)
.style("text-anchor", "end")
.text("%3P");
}
setCanvas(){
let margin = this.state.margin;
// Add the visualization svg canvas to the container <div>
let svg = d3.select("#scatterplot").style("background-color", "#091E36");
svg.attr("height", this.state.height);
svg.attr("width", this.state.width);
svg.style("color", "#FFFFFF");
svg.append("p").text("Hola, cómo estamos?");
return svg;
//return canvas;
}
setPointsToCanvas(canvas, data){
canvas.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 5.5) //Radius size, could map to another dimension
.attr("cx", function(d) { return xScale(d.p2_p); }) //x position
.attr("cy", function(d) { return yScale(d.p3_p); }) //y position
.style("fill", "green");
}
render(){
return(
<div>
{
(this.state.loaded) ?
<div id="scatterplot"></div>
:
<h5>
<Determinator>
{{
es: "Cargando datos ...",
en: "Loading data ..."
}}
</Determinator>
</h5>
}
<MultiLang lang = {this.props.lang} />
</div>
);
};
};
module.exports.ScatterPlotGraphics = ScatterPlotGraphics;
When I try to access to the webpage which contains this component I've got this error in the console:
If I deploy the error line "TypeError: scale.domain is not a function", we can see that there is a reference in line 51 of the code.
If we check the code, we can see that in 51 line there is no any reference to "scale.domain"
How is that possible? What am I doing wrong?
Edit I:
I have modified the function setAxes, being like this:
setAxes(data){
let xRange = [0, this.state.width];
let yRange = [this.state.height, 0];
let xScale = d3.scaleLinear()
.domain([ d3.min(data, function(d) { return d.p2_p; }) - 1,
d3.max(data, function(d) { return d.p2_p; }) + 1 ])
.range(xRange);
let yScale = d3.scaleLinear()
.domain([ d3.min(data, function(d) { return d.p3_p; }) - 1,
d3.max(data, function(d) { return d.p3_p; }) + 1 ])
.range(yRange); // flip order because y-axis origin is upper LEFT
let xAxis = d3.axisBottom(xScale).tickFormat(function(d) { return d.p2_p; });
let yAxis = d3.axisLeft(yScale).tickFormat(function (d) { return d.p3_p; });
return {"xAxis" : xAxis, "yAxis" : yAxis, "xScale" : xScale, "yScale" : yScale};
}
But now, the problem is that I've got the error "values is undefined" and I don't know why that happend :(
My requirement is to draw a category-grouped bar chart in which each category has a different number of groups, using pure d3. I have no idea how to take domain and range to meet my requirement.
I tried in the way given in the answer to d3 nested grouped bar chart, but it did not work in my case.
Here my graph structure is like:
The issue with the plunker of the answer that you mention is that it will just work for values that have the same children. In order to handle the dynamic children values I took this approach
Lets create the color mapping for our groups:
var color = {
Mechanical: '#4A7B9D',
Electrical: '#54577C',
Hydraulic: '#ED6A5A'
};
We also need a structure with nested values that will be the inner groups:
// Simulated data structure
var data = [{
key: 'Mechanical',
values: [{
key: 'Gear',
value: 11
}, {
key: 'Bearing',
value: 8
}, {
key: 'Motor',
value: 3
}]
}];
I created a barPadding value which will dictate the separation between bars:
var barPadding = 120;
We are going to need a dummy scale to get the rangeBand of the bars, lets do that:
// dummy array
var rangeBands = [];
// cummulative value to position our bars
var cummulative = 0;
data.forEach(function(val, i) {
val.cummulative = cummulative;
cummulative += val.values.length;
val.values.forEach(function(values) {
rangeBands.push(i);
})
});
// set scale to cover whole svg
var x_category = d3.scale.linear()
.range([0, width]);
// create dummy scale to get rangeBands (width/childrenValues)
var x_defect = d3.scale.ordinal().domain(rangeBands)
.rangeRoundBands([0, width], .1);
var x_category_domain = x_defect.rangeBand() * rangeBands.length;
x_category.domain([0, x_category_domain]);
Then lets add all our category groups g elements:
var category_g = svg.selectAll(".category")
.data(data)
.enter().append("g")
.attr("class", function(d) {
return 'category category-' + d.key;
})
.attr("transform", function(d) { // offset by inner group size
var x_group = x_category((d.cummulative * x_defect.rangeBand()));
return "translate(" + x_group + ",0)";
})
.attr("fill", function(d) { // make child elements of group "inherit" this fill
return color[d.key];
});
Adding our inner groups g elements:
var defect_g = category_g.selectAll(".defect")
.data(function(d) {
return d.values;
})
.enter().append("g")
.attr("class", function(d) {
return 'defect defect-' + d.key;
})
.attr("transform", function(d, i) { // offset by index
return "translate(" + x_category((i * x_defect.rangeBand())) + ",0)";
});
Having our g elements lets add the labels:
var category_label = category_g.selectAll(".category-label")
.data(function(d) {
return [d];
})
.enter().append("text")
.attr("class", function(d) {
console.log(d)
return 'category-label category-label-' + d.key;
})
.attr("transform", function(d) {
var x_label = x_category((d.values.length * x_defect.rangeBand() + barPadding) / 2);
var y_label = height + 30;
return "translate(" + x_label + "," + y_label + ")";
})
.text(function(d) {
return d.key;
})
.attr('text-anchor', 'middle');
var defect_label = defect_g.selectAll(".defect-label")
.data(function(d) {
return [d];
})
.enter().append("text")
.attr("class", function(d) {
console.log(d)
return 'defect-label defect-label-' + d.key;
})
.attr("transform", function(d) {
var x_label = x_category((x_defect.rangeBand() + barPadding) / 2);
var y_label = height + 10;
return "translate(" + x_label + "," + y_label + ")";
})
.text(function(d) {
return d.key;
})
.attr('text-anchor', 'middle');
And finally our rects:
var rects = defect_g.selectAll('.rect')
.data(function(d) {
return [d];
})
.enter().append("rect")
.attr("class", "rect")
.attr("width", x_category(x_defect.rangeBand() - barPadding))
.attr("x", function(d) {
return x_category(barPadding);
})
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return height - y(d.value);
});
Here's the above code in plnkr: https://plnkr.co/edit/L0eQwtEMQ413CpoS5nvo?p=preview
I'm trying to create a circle pack graph using nest() and .rollup. I'm getting the following errors:
Error: Invalid value for <g> attribute transform="translate(undefined,undefined)"
Error: Invalid value for <circle> attribute r="NaN"
I want the circles to be sized according to the number of companies in each country. I'm attempting to adapt Mike Bostock's Flare circle-pack example.
If anyone could point me in the direction of any information, I'd be very grateful.
JS code:
var diameter = 960,
format = d3.format(",d");
var pack = d3.layout.pack()
.size([diameter - 4, diameter - 4])
.value(function(d) { return d.size; });
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(2,2)");
//Get data
d3.json("data/countriesNested.php", function(error, data){
var submissionsByCountry = d3.nest()
.key(function(d) { return d.Country; })
.key(function(d) { return d.Organisation; })
.rollup(function(leaves) { return leaves.length; })
.entries(data);
var node = svg.datum(data).selectAll(".node")
.data(pack.nodes)
.enter().append("g")
.attr("class", function(d) { return d.children ? "node" : "leaf node"; })
.attr("transform", function(d) { return "translate(" + d.cx + "," + d.cy + ")"; });
node.append("title")
.text(function(d) { return d.name + (d.children ? "" : ": " + format(d.size)); });
node.append("circle")
.attr("r", function(d) { return d.r; });
});
d3.select(self.frameElement).style("height", diameter + "px");
</script>
Data file (from MySQL using PHP script):
[
{
"Country":"USA",
"ID":4,
"Organisation":"Company 1"
},
{
"Country":"USA",
"ID":5,
"Organisation":"Company 2"
},
{
"Country":"USA",
"ID":6,
"Organisation":"Company 3"
},
{
"Country":"FRANCE",
"ID":19,
"Organisation":"Company 4"
},
{
"Country":"FRANCE",
"ID":24,
"Organisation":"Company 5"
},
{
"Country":"GERMANY",
"ID":10,
"Organisation":"Company 6"
},
{
"Country":"ITALY",
"ID":7,
"Organisation":"Company 7"
},
.....
Thanks for reading.
There are a few errors in your code that need to be fixed:
You need to set the accessor functions for children and values on your pack layout:
var pack = d3.layout.pack()
.size([diameter - 4, diameter - 4])
.children(function(d) {
return d.values; // accessor for children
})
.value(function(d) {
return d.values; // accessor for values
});
Your d3.nest() returns an array but d3.pack() requires you to supply a root object containing the hierarchy. You have to create a root object and put your nested array inside:
var countryRoot = {
key: "root",
values: submissionsByCountry
};
In your code you nest your data into submissionsByCountry but you are not using this variable anywhere else. So you obviously have to refer to it when binding data to your svg. This is accomplished by the above mentioned root object which is later on bound to the svg.
var node = svg.datum(countryRoot).selectAll(".node")
The attributes the pack layout is adding to your data nodes include values x and y, whereas you refered to them as cx and cy which are attributes to <svg:circle> but are not present in your data. Hence, you got your transform="translate(undefined,undefined)" error messages. You should use these attributes as such:
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
I put together a working plunk.
So, you want a parent circle (let's call it the world), with child circles representing each country sized with a count of entries in your JSON array?
d3.json("data/countriesNested.php", function(error, data) {
var submissionsByCountry = d3.nest()
.key(function(d) {
return d.Country;
})
.rollup(function(leaves) {
return leaves.length;
})
.entries(data);
var root = {
"key": "world",
"children": submissionsByCountry
};
...
This will give you something closely resembling the flare.json.
Next, you need to give d3 the right accessor for your circle size.
var pack = d3.layout.pack()
.size([diameter - 4, diameter - 4])
.value(function(d) {
return d.values; //<-- this comes from your roll-up and is the count.
});
Finally it looks like you changed the example code to access non-exist cx and cy attributes in the resulting nodes data:
var node = svg.datum(root).selectAll(".node")
.data(pack.nodes)
.enter().append("g")
.attr("class", function(d) {
return d.children ? "node" : "leaf node";
})
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; //<-- this is .x, .y
});
Here's an example.
I'm trying to learn to build a choropleth map using an example provided by d3.js.
I keep getting an error: "TypeError: congress.forEach is not a function" when I switch from the .tsv file in the example to a .json file in my version. Here is the code I'm using:
<script type="text/javascript">
var width = 960,
height = 500;
var path = d3.geo.path();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.append("g")
.call(d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoom));
d3.json("us-10m.json", function (error, us) {
d3.json("https://www.govtrack.us/api/v2/role?current=true", function (error, congress) {
var memberId = {};
congress.forEach(function (d) { //TypeError:congress.forEach is not a function
memberId[d.id] = +d.id;
});
g.append("g")
.attr("class", "states")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path)
.style("fill", function (d) {
return color(memberId[d.id]); // <-C
});
g.append("path")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("class", "states")
.attr("d", path);
});
});
function zoom() {
g.attr("transform", "translate("
+ d3.event.translate
+ ")scale(" + d3.event.scale + ")");
}
Any help is appreciated. Sorry I didn't put this on jsfiddle but I couldn't connect to .us-10m.json because of a cross domain issue.
Thank you,
Thom
congress is not an Array — it's an Object:
{
meta: {
limit: 100,
offset: 0,
total_count: 540
},
objects: [ {...}, {...}, {...}, {...} ]
}
Need to iterate over congress.objects:
congress.objects.forEach(function() {...})
I am trying to replicate the health & wealth nations chart.
http://bost.ocks.org/mike/nations/:
When i click start the animation of the chart works which works perfectly and if i click stop the animation stops. However if i click the start next time, it is starting from the beginning instead from where i stopped? how do i animate from the place where i left?
Following is the code:
<h1>The Wealth & Health of Nations</h1>
<p id="chart"></p>
<input type="submit" value="Start" onclick=start();>
<input type="submit" value="Stop" onclick=stop();>
<script src="http://d3js.org/d3.v2.js?2.8.1"></script>
<script>
// Various accessors that specify the four dimensions of data to visualize.
function x(d) { return d.income; }
function y(d) { return d.lifeExpectancy; }
function radius(d) { return d.population; }
function color(d) { return d.region; }
function key(d) { return d.name; }
// Chart dimensions.
var margin = {top: 29.5, right: 29.5, bottom: 29.5, left: 59.5},
width = 960 - margin.right,
height = 500 - margin.top - margin.bottom;
// Various scales. These domains make assumptions of data, naturally.
var xScale = d3.scale.log().domain([300, 1e5]).range([0, width]),
yScale = d3.scale.linear().domain([10, 85]).range([height, 0]),
radiusScale = d3.scale.sqrt().domain([0, 5e8]).range([0, 40]),
colorScale = d3.scale.category10();
// The x & y axes.
var xAxis = d3.svg.axis().orient("bottom").scale(xScale).ticks(12, d3.format(",d")),
yAxis = d3.svg.axis().scale(yScale).orient("left");
// Create the SVG container and set the origin.
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 + ")");
// Add the x-axis.
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the y-axis.
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// Add an x-axis label.
svg.append("text")
.attr("class", "x label")
.attr("text-anchor", "end")
.attr("x", width)
.attr("y", height - 6)
.text("income per capita, inflation-adjusted (dollars)");
// Add a y-axis label.
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("y", 6)
.attr("dy", ".75em")
.attr("transform", "rotate(-90)")
.text("life expectancy (years)");
// Add the year label; the value is set on transition.
var label = svg.append("text")
.attr("class", "year label")
.attr("text-anchor", "end")
.attr("y", height - 24)
.attr("x", width)
.text(2000);
function start()
{
//alert("Start Clicked");
// Load the data.
d3.json("nations_new.json", function(nations) {
// A bisector since many nation's data is sparsely-defined.
var bisect = d3.bisector(function(d) { return d[0]; });
// Add a dot per nation. Initialize the data at 2000, and set the colors.
var dot = svg.append("g")
.attr("class", "dots")
.selectAll(".dot")
.data(interpolateData(2000))
.enter().append("circle")
.attr("class", "dot")
.style("fill", function(d) { return colorScale(color(d)); })
.call(position)
.sort(order);
// Add a title.
dot.append("title")
.text(function(d) { return d.name; });
// Add an overlay for the year label.
var box = label.node().getBBox();
var overlay = svg.append("rect")
.attr("class", "overlay")
.attr("x", box.x)
.attr("y", box.y)
.attr("width", box.width)
.attr("height", box.height);
//.on("mouseover", enableInteraction);
// Start a transition that interpolates the data based on year.
svg.transition()
.duration(30000)
.ease("linear")
.tween("year", tweenYear)
.each("end", enableInteraction);
// Positions the dots based on data.
function position(dot) {
dot .attr("cx", function(d) { return xScale(x(d)); })
.attr("cy", function(d) { return yScale(y(d)); })
.attr("r", function(d) { return radiusScale(radius(d)); });
}
// Defines a sort order so that the smallest dots are drawn on top.
function order(a, b) {
return radius(b) - radius(a);
}
// After the transition finishes, you can mouseover to change the year.
function enableInteraction() {
var yearScale = d3.scale.linear()
.domain([2000, 2009])
.range([box.x + 10, box.x + box.width - 10])
.clamp(true);
// Cancel the current transition, if any.
svg.transition().duration(0);
overlay
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("mousemove", mousemove)
.on("touchmove", mousemove);
function mouseover() {
label.classed("active", true);
}
function mouseout() {
label.classed("active", false);
}
function mousemove() {
displayYear(yearScale.invert(d3.mouse(this)[0]));
}
}
// Tweens the entire chart by first tweening the year, and then the data.
// For the interpolated data, the dots and label are redrawn.
function tweenYear() {
var year = d3.interpolateNumber(2000, 2009);
return function(t) { displayYear(year(t)); };
}
// Updates the display to show the specified year.
function displayYear(year) {
dot.data(interpolateData(year), key).call(position).sort(order);
label.text(Math.round(year));
}
// Interpolates the dataset for the given (fractional) year.
function interpolateData(year) {
return nations.map(function(d) {
return {
name: d.name,
region: d.region,
income: interpolateValues(d.income, year),
population: interpolateValues(d.population, year),
lifeExpectancy: interpolateValues(d.lifeExpectancy, year)
};
});
}
// Finds (and possibly interpolates) the value for the specified year.
function interpolateValues(values, year) {
var i = bisect.left(values, year, 0, values.length - 1),
a = values[i];
if (i > 0) {
var b = values[i - 1],
t = (year - a[0]) / (b[0] - a[0]);
return a[1] * (1 - t) + b[1] * t;
}
return a[1];
}
});
}
function stop()
{
//alert("stop Clicked");
svg.transition().duration(0);
}
the json file is :
[
{
"name":"Angola",
"region":"Sub-Saharan Africa",
"income":[[2000,2446.65],[2001,2479.69],[2002,2773.29],[2003,2785.39],[2004,3007.11],[2005,3533],[2006,4069.56],[2007,4755.46],[2008,5228.74],[2009,5055.59]],
"population":[[2000,10442812],[2001,10623424],[2002,10866106],[2003,11186202],[2004,11521432],[2005,11827315],[2006,12127071],[2007,12420476],[2008,12707546]],
"lifeExpectancy":[[2000,43.56],[2001,43.86],[2002,44.22],[2003,44.61],[2004,45.05],[2005,45.52],[2006,46.02],[2007,46.54],[2008,47.06],[2009,47.58]]
},
{
"name":"china",
"region":"East Asia & Pacific",
"income":[[2000,12446.65],[2001,12479.69],[2002,12773.29],[2003,12785.39],[2004,12007.11],[2005,12533],[2006,12069.56],[2007,12755.46],[2008,12228.74],[2009,12055.59]],
"population":[[2000,31542812],[2001,31623424],[2002,31866106],[2003,32186202],[2004,31521432],[2005,31827315],[2006,32127071],[2007,32420476],[2008,32707546]],
"lifeExpectancy":[[2000,53.56],[2001,63.86],[2002,64.22],[2003,64.61],[2004,76.05],[2005,66.52],[2006,86.02],[2007,87.54],[2008,89.06],[2009,68.58]]
},
{
"name":"India",
"region":"South Asia",
"income":[[2000,22446.65],[2001,22479.69],[2002,22773.29],[2003,22785.39],[2004,22007.11],[2005,22533],[2006,22069.56],[2007,22755.46],[2008,22228.74],[2009,22055.59]],
"population":[[2000,41542812],[2001,41623424],[2002,41866106],[2003,42186202],[2004,41521432],[2005,41827315],[2006,42127071],[2007,42420476],[2008,42707546],[2009,42707546]],
"lifeExpectancy":[[2000,43.56],[2001,43.86],[2002,44.22],[2003,64.61],[2004,56.05],[2005,56.52],[2006,66.02],[2007,68.54],[2008,67.06],[2009,73.58]]
}
]
In the start function, you would need to keep track of what year you're currently showing, e.g. with a global variable:
var thisYear = 2000;
// lots of code...
function displayYear(year) {
thisYear = year;
dot.data(interpolateData(year), key).call(position).sort(order);
label.text(Math.round(year));
}
Then you would need to modify the year it starts with depending on the value of that variable:
function tweenYear() {
var year = d3.interpolateNumber(thisYear, 2009);
return function(t) { displayYear(year(t)); };
}
I was working on something very similar and the suggestions by Lars got me very far. However, I kept getting my dots duplicated each time I hit start. I have since discovered the following:
If you would like to make it so your dots are not duplicated each time you select the start button, you would need to add the following code as a part of your start() function, and ideally just before the dots are initially added:
svg.selectAll(".dot").remove()
This removes the previous .dot elements; new .dot elements are subsequently created assuming you've setup the thisYear global variable mentioned by Lars.