I'm trying to recreate the map shown here using D3 but I'm having trouble binning the values from my data and mapping them to colors. Currently I'm using scale.quantile that I believe sets the number of bins according to the range which should be 8 since I have 8 colors. So it would bin up the fertility values into 8 different bins and map each bin to each color but the colors are incorrect. In my data Sweden has a fertility rate of 1.89 and Russia 1.7 but they are the same color on my map which looks like this with my current code. What am I doing wrong?
var width5 = 700;
var height5 = 500;
buckets = 8,
colors = ['#ff1a1a','#ff471a','#ffd633','#ffff1a','#ccff33',' #66ff66','#00ff00',' #009900'];
var projection = d3.geo.mercator()
.center([ 13, 52 ])
.translate([ width5/2.5, height5/1.7 ])
.scale([ width5/1.5 ]);
var path = d3.geo.path()
.projection(projection);
var mapchart = d3.select("#mapchartarea")
.append("svg")
.attr("width", width5)
.attr("height", height5);
d3.json("data/europe_geo_fertility.json", function(error,collection) {
if(error) throw error;
console.log(d3.min(collection.features, function(d) { return d.properties.fertility;}))
// Sets color for each country according to fertility rate
var colorScale = d3.scale.quantile()
.domain([0, buckets -1, d3.max(collection.features, function(d) { return d.properties.fertility;})])
.range(colors);
// Draws Map
mapchart.selectAll("path")
.data(collection.features)
.enter()
.append("path")
.attr("d", path)
.attr("stroke", "rgba(8, 81, 156, 0.2)")
.attr("fill", "rgba(8, 81, 156, 0.6)")
.style("fill", function(d) { return colorScale(d.properties.fertility); });
// Adds name of each country
mapchart.selectAll("text")
.data(collection.features)
.enter()
.append("svg:text")
.text(function(d){
return d.properties.name;
})
.attr("x", function(d){
return path.centroid(d)[0];
})
.attr("y", function(d){
return path.centroid(d)[1];
})
.attr("text-anchor","middle")
.attr('font-size','6pt');
});
Related
I want to change the symbol type from circle to triangle, square, other symbols.
svg.selectAll().
data(data).enter()
.append("circle")
.attr("class", "dot")
.attr("cx", function(d, i) { return timeScale(d.year); })
.attr("cy", function(d, i) { return yScale(d.sale) })
.style("fill", "#FFC300")
.attr("r", function(d) {return est_size(d.est)})
If I change .append("circle") to .append("triangle"), the chart does not show the symbol. How can I show a triangle instead of a circle?
SVG doesn't have an element type for a triangle - the most basic shapes are rect and circle (there are also paths, polygons, ellipses, etc, but no triangle). However, we have a few options open to us, we can use a d3-symbol (available symbols listed here), or we can create our own symbol and use that.
For using d3-symbol we can do the following:
var width = 500;
var height = 300;
var data = d3.range(10)
.map(function(d) { return { x: Math.random()*width, y: Math.random()*height }; })
var svg = d3.select("svg")
.attr("width",width)
.attr("height",height);
svg.selectAll(".symbol")
.data(data)
.enter()
.append("path")
.attr("d", d3.symbol().type(d3.symbolTriangle).size(50))
.attr("transform",function(d) { return "translate("+[d.x,d.y]+")" })
.attr("class","symbol");
// For demonstrating that the triangles are centered:
svg.selectAll(null)
.data(data)
.enter()
.append("circle")
.attr("r", 3)
.attr("fill","orange")
.attr("transform",function(d) { return "translate("+[d.x,d.y]+")" });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>
symbol.size corresponds to shape area, not an edge length
Alternatively, we can create a function that returns a basic triangle polygon ourselves, and use it with selection.append():
var width = 500;
var height = 300;
var data = d3.range(10)
.map(function(d) { return { x: Math.random()*width, y: Math.random()*height }; })
var svg = d3.select("svg")
.attr("width",width)
.attr("height",height);
var symbol = function() {
// Hand drawn triangle:
return d3.create('svg:path').attr("d","M0,8L-5,-3L5,-3Z").node()
}
svg.selectAll(".symbol")
.data(data)
.enter()
.append(symbol) // append can accept a function.
.attr("transform",function(d) { return "translate("+[d.x,d.y]+")" })
.attr("class","symbol");
// For demonstrating that the triangles are centered:
svg.selectAll(null)
.data(data)
.enter()
.append("circle")
.attr("r", 3)
.attr("fill","orange")
.attr("transform",function(d) { return "translate("+[d.x,d.y]+")" });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>
We could also take a few other approaches, such as using svg symbol elements, but the above two methods should be sufficient.
I am trying to change the x-scale of already plotted path in d3.js
Also, i am able to change the x-scale of circles using following code.
var s = d3.event.selection || xTimeScale.range();
xTimeScale.domain(s.map(xDateScale.invert, xDateScale));
d3.selectAll(".dot").transition().duration(10).attr("cx",function(d){ return xTimeScale(d.date); })
As you can see i have changed the domain of xTimeScale with new values. and then select all the circles with class(.dot) and replot the x and y with changed xTimeScale.
Now i also want to change the path with this new scale.
Here is the code when my path first plotted.
var lineFunction = d3.line().x(function(d) { return x(d.date); }).y(function(d) { return y(d.value); })
var lineGraph = svg.append("path").attr("d",lineFunction(data)).attr("stroke", "blue")
.attr("stroke-width", 2).attr("fill", "none").attr("class","dotsLine");
Since you're just changing the scale used by the line generator you don't need to rebind the data (by the way, I see that you are not binding any data), you just need to pass the new scale to the line generator and, then, update the d attribute of the path.
Look at this demo. There is a x scale, named xScale1, used by the line generator:
var line = d3.line()
.x(function(d) {
return xScale1(d)
})
When you click the button, the line generator uses another scale...
line.x(function(d) {
return xScale2(d)
})
... and the path d attribute is updated:
path.transition()
.duration(1000)
.attr("d", line(data));
Here is the demo:
var svg = d3.select("svg");
var data = [12, 130, 45, 60, 110, 21];
var yScale = d3.scaleLinear()
.domain(d3.extent(data))
.range([140, 10]);
var xScale1 = d3.scalePoint()
.domain(data)
.range([10, 290]);
var xScale2 = d3.scalePoint()
.domain(data.concat(d3.range(6)))
.range([10, 290]);
var line = d3.line()
.x(function(d) {
return xScale1(d)
})
.y(function(d) {
return yScale(d)
})
.curve(d3.curveBasis);
var path = svg.append("path")
.attr("d", line(data))
.attr("fill", "none")
.attr("stroke", "black");
d3.select("button").on("click", function() {
line.x(function(d) {
return xScale2(d)
})
path.transition()
.duration(1000)
.attr("d", line(data));
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<button>Click me</button>
<br>
<svg></svg>
I'm very new to data vizualisation and JavaScript and I'm trying to build a bar chart histogram using d3 v4.
I was working first working on d3 v3 and everything was going so well but I've got informed that I needed to work on v4.
Here is a piece of my code :
...
// create function for x-axis mapping.
var x = d3.scaleBand().rangeRound([0, hGDim.w]).padding(0.1)
.domain(fD.map(function(d) { return d[0]; }));
var xAxis = d3.axisBottom()
.scale(x);
// Create function for y-axis map.
var y = d3.scaleBand().rangeRound([0, hGDim.h])
.domain([0, d3.max(fD, function(d) { return d[1]; })]);
var yAxis = d3.axisBottom()
.scale(y);
// Create bars for histogram to contain rectangles and freq labels.
var bars = hGsvg.selectAll(".bar").data(fD).enter()
.append("g").attr("class", "bar");
//create the rectangles.
bars.append("rect")
.attr("x", function(d) { return x(d[0]); })
.attr("y", function(d) { return y(d[1]); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return hGDim.h - y(d[1]); })
.attr('fill',barColor)
.on("mouseover",mouseover)// mouseover is defined below.
.on("mouseout",mouseout);// mouseout is defined below.
//Create the frequency labels above the rectangles.
bars.append("text").text(function(d){ return d3.format(",")(d[1])})
.style("font-family", "sans-serif")
.attr("x", function(d) { return x(d[0])+x.bandwidth()/2; })
.attr("y", function(d) { return y(d[1])-5; })
.attr("text-anchor", "middle");
...
When trying to run this, I have this 2 errors :
Error: attribute height: Expected length, "NaN".
And it tells me that it's on this line :
.attr("height", function(d) { return hGDim.h - y(d[1]); })
hGDim.h being a number
I also have this error :
Error: attribute y: Expected length, "NaN".
And it tells me that it's on this line :
.attr("y", function(d) { return y(d[1])-5; })
I didn't put all my code (271 lines), I'm not sure it's needed here.
Do you have any idea from where could these errors come from ?
I feel that I'm trying to add 2 variables of different types... However, it was working well on v3.
You are treating your y scale like a continuous scale, but it needs to be ordinal like your x scale (scaleBand() is ordinal). Try this:
var y = d3.scaleBand()
.rangeRound([0, hGDim.h])
.domain(fD.map(function(d) {return d[1];}));
Then, you must modify the create rectangle code as follows:
bars.append("rect")
.attr("x", function(d) { return x(d[0]); })
.attr("y", function(d) { return y(d[1]); })
.attr("width", x.bandwidth())
.attr("height", y.bandwidth())
.attr('fill', barColor)
.on("mouseover", mouseover)
.on("mouseout", mouseout);
Here is a minimal example, assuming d[0] and d[1] are coordinates on the heatmap:
var data = [[1, 1, "red"], [1, 2, "blue"], [1, 3, "green"],
[2, 1, "navy"], [2, 2, "yellow"], [2, 3, "orange"],
[3, 1, "red"], [3, 2, "blue"], [3, 3, "red"]],
svg = d3.select("svg"),
w = 500,
h = 500,
x = d3.scaleBand()
.rangeRound([0, w])
.padding(0.1)
.domain(data.map(function(d) {return d[0];})),
y = d3.scaleBand()
.rangeRound([0, h])
.padding(0.1)
.domain(data.map(function(d) {return d[1]})),
xAxis = d3.axisBottom()
.scale(x),
yAxis = d3.axisBottom()
.scale(y),
bars = svg.selectAll(".bar")
.data(data).enter()
.append("g")
.attr("class", "bar");
bars.append("rect")
.attr("x", function(d) { return x(d[0]); })
.attr("y", function(d) { return y(d[1]); })
.attr("width", x.bandwidth())
.attr("height", y.bandwidth())
.style("fill", function(d) { return d[2]; });
<script src="https://d3js.org/d3.v4.js"></script>
<svg height="500px", width="500px"></svg>
I have a series of paired xy coordinates that create 58 lines. I want to plot them on a Cartesian graph, values are between -5 and 5 on both axis, essentially making a scatter plot of lines. I have made something similar in matplotlib using the quiver function, but I want to be able to do this in D3. I would also like to be able to label each line, or each line that meets a length threshold. The code I have come up with below. Thanks.
var lisa = [["Eloy",0.0169808,-0.695317,-0.0510301,-0.6995938],
["Florence",-0.3465685,-0.6790588,-0.5869514,-0.6762134],
["Phoenix",0.677068,-0.5754814,-0.6052215,-0.6158059],
["Tucson",-0.663848,0.4111043,-0.6722116,0.011639]]
var w = 200;
var h = 200;
//create the svg element and set the height and width parameters
var svg = d3.select("div").select("div")
.append("svg")
.attr("height",h)
.attr("width", w)
.style("border", "1px solid black");
//Create the scale for the scatter plot
var xScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[0];}),d3.max(dataset, function(d) { return d[0];})])
.range([-1,1]);
var yScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[1];}),d3.max(dataset, function(d) { return d[1];})])
.range([-1,1]);
//This is the function that creates the SVG lines
var line = svg.selectAll("line")
.data(lisa)
.enter()
.append("line");
//This gets the cooresponding x,y cordinates from the dataset
line.attr("x1", function(d) {
return xScale(d[0]);
})
.attr("y1", function(d) {
return yScale(d[1]);
})
.attr("x2", function(d) {
return xScale(d[2]);
})
.attr("y2", function(d) {
return yScale(d[3]);
})
.attr("stroke", "black");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Your code has some problems:
First, your range right now ([-1, 1]) makes no sense. This should be the domain instead (I changed the ranges to [0, w] and [0, h]).
In your real code, the domain should be [-5, 5] and the range should be the limits of the plot, something like [leftLimit, rightLimit] and [topLimit, bottomLimit] (have in mind that, in an SVG, the 0 position for the y axis is the top, not the bottom).
Second, given this array:
["Tucson",-0.663848,0.4111043,-0.6722116,0.011639]
your x and y positions should be the indices 1,2,3 and 4, not 0, 1, 2 and 3.
Besides that changes, I added the labels:
var text = svg.selectAll(".text")
.data(dataset)
.enter()
.append("text");
text.attr("font-size", 10)
.attr("x", function(d) {
return xScale(d[1]);
})
.attr("y", function(d) {
return yScale(d[2]);
})
.text(d => d[0]);
Here is the demo with the corrections:
var dataset = [["Eloy",0.0169808,-0.695317,-0.0510301,-0.6995938],
["Florence",-0.3465685,-0.6790588,-0.5869514,-0.6762134],
["Phoenix",0.677068,-0.5754814,-0.6052215,-0.6158059],
["Tucson",-0.663848,0.4111043,-0.6722116,0.011639]];
var color = d3.scale.category10();
var w = 400;
var h = 300;
//create the svg element and set the height and width parameters
var svg = d3.select("body")
.append("svg")
.attr("height",h)
.attr("width", w)
.style("border", "1px solid black");
//Create the scale for the scatter plot
var xScale = d3.scale.linear()
.domain([-1,1])
.range([0,w]);
var yScale = d3.scale.linear()
.domain([-1,1])
.range([0,h]);
//This is the function that creates the SVG lines
var line = svg.selectAll("line")
.data(dataset)
.enter()
.append("line");
//This gets the cooresponding x,y cordinates from the dataset
line.attr("x1", function(d) {
return xScale(d[1]);
})
.attr("y1", function(d) {
return yScale(d[2]);
})
.attr("x2", function(d) {
return xScale(d[3]);
})
.attr("y2", function(d) {
return yScale(d[4]);
})
.attr("stroke-width", 2)
.attr("stroke", (d,i)=>color(i));
var text = svg.selectAll(".text")
.data(dataset)
.enter()
.append("text");
text.attr("font-size", 10)
.attr("x", function(d) {
return xScale(d[1])+2;
})
.attr("y", function(d) {
return yScale(d[2]) + 4;
})
.text(d=>d[0]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I am plotting points on a UK map using D3 off a live data stream. When the data points exceed 10,000 the browser becomes sluggish and the animation is no longer smooth. So I modify the dataPoints array to keep only the last 5000 points.
However when I modify the dataPoints the first time using splice() D3 stops rendering any new points. The old points gradually disappear (due to a transition) but there are no new points. I am not sure what I am doing wrong here.
I have simulated the problem by loading data of a CSV as well storing it in memory and plotting them at a rate of 1 point every 100ms. Once the number of dots goes above 10 I splice to retain the last 5 points. I see the same behaviour. Can someone review the code and let me know what I am doing wrong?
Setup and the plotting function:
var width = 960,
height = 1160;
var dataPoints = []
var svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height);
var projection = d3.geo.albers()
.center([0, 55.4])
.rotate([4.4, 0])
.parallels([40, 70])
.scale(5000)
.translate([width / 2, height / 2]);
function renderPoints() {
var points = svg.selectAll("circle")
.data(dataPoints)
points.enter()
.append("circle")
.attr("cx", function (d) {
prj = projection([d.longitude, d.latitude])
return prj[0];
})
.attr("cy", function (d) {
prj = projection([d.longitude, d.latitude])
return prj[1];
})
.attr("r", "4px")
.attr("fill", "blue")
.attr("fill-opacity", ".4")
.transition()
.delay(5000)
.attr("r", "0px")
}
/* JavaScript goes here. */
d3.json("uk.json", function(error, uk) {
if (error) return console.error(error);
console.log(uk);
var subunits = topojson.feature(uk, uk.objects.subunits);
var path = d3.geo.path()
.projection(projection);
svg.selectAll(".subunit")
.data(subunits.features)
.enter().append("path")
.attr("class", function(d) { return "subunit " + d.id })
.attr("d", path);
svg.append("path")
.datum(topojson.mesh(uk, uk.objects.subunits, function(a,b) {return a!== b && a.id !== 'IRL';}))
.attr("d", path)
.attr("class", "subunit-boundary")
svg.append("path")
.datum(topojson.mesh(uk, uk.objects.subunits, function(a,b) {return a=== b && a.id === 'IRL';}))
.attr("d", path)
.attr("class", "subunit-boundary IRL")
svg.selectAll(".place-label")
.attr("x", function(d) { return d.geometry.coordinates[0] > -1 ? 6 : -6; })
.style("text-anchor", function(d) { return d.geometry.coordinates[0] > -1 ? "start": "end"; });
svg.selectAll(".subunit-label")
.data(topojson.feature(uk, uk.objects.subunits).features)
.enter().append("text")
.attr("class", function(d) { return "subunit-label " + d.id })
.attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; })
.attr("dy", ".35em")
.text(function(d) { return d.properties.name; })
// function applyProjection(d) {
// console.log(d);
// prj = projection(d)
// console.log(prj);
// return prj;
// }
lon = -4.6
lat = 55.45
dataPoints.push([lon,lat])
renderPoints()
});
Function to cleanup old points
var cleanupDataPoints = function() {
num_of_elements = dataPoints.length
console.log("Pre:" + num_of_elements)
if(num_of_elements > 10) {
dataPoints = dataPoints.splice(-5, 5)
}
console.log("Post:" + dataPoints.length)
}
Loading data from CSV and plotting at a throttled rate
var bufferedData = null
var ptr = 0
var renderNext = function() {
d = bufferedData[ptr]
console.log(d)
dataPoints.push(d)
ptr++;
renderPoints()
cleanupDataPoints()
if(ptr < bufferedData.length)
setTimeout(renderNext, 100)
}
d3.csv('test.csv', function (error, data) {
bufferedData = data
console.log(data)
setTimeout(renderNext, 100)
})
In the lines
points = svg.selectAll("circle")
.data(dataPoints)
points.enter() (...)
d3 maps each element in dataPoints (indexed from 0 to 5000) to the circle elements (of which there should be 5000 eventually). So from its point of view, there is no enter'ing data: there are enough circles to hold all your points.
To make sure that the same data point is mapped to the same html element after it changed index in its array, you need to use an id field of some sort attached to each of your data point, and tell d3 to use this id to map the data to elements, instead of their index.
points = svg.selectAll("circle")
.data(dataPoints, function(d){return d.id})
If the coordinates are a good identifier for your point, you can directly use:
points = svg.selectAll("circle")
.data(dataPoints, function(d){return d.longitude+" "+d.latitude})
See https://github.com/mbostock/d3/wiki/Selections#data for more details.