I'm mapping points on a map using the following code:
var width = 960, height = 500, centered, data;
var svg = d3.select("#chart").append("svg").attr("width", width).attr("height", height);
var projection = d3.geo.albersUsa().scale(width).translate([width / 2, height / 2]);
var path = d3.geo.path().projection(projection);
var states = svg.append("g")
.append("g")
.attr("id", "states")
d3.json("usstates.min.geojson", function(json) {
states.selectAll("path")
.data(json.features)
.enter().append("path")
.attr("d", path);
});
var points = svg.append("g")
.attr("id", "points");
// Bentonville, Arkansas is at 36.3728, 94.2086
bvCoords = projection([36.3724, 94.2216]);
console.log(bvCoords)
points.append("svg:circle")
.attr("cx", bvCoords[0]).attr("r", 100).attr("cy", bvCoords[1])
.style("fill", "#F00");
When I load that in Chrome, console reports [114.03316181053958, -15.872634684617708], which would be somewhere off the western coast of Canada. If I add .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")") to the points.append call, it's somewhere in southern Indiana.
What am I doing wrong with this projection?
Your coordinates are the wrong way around: D3 uses the more "mathematical" conventional ordering of [longitude, latitude] ↦ [x, y].
Geographic coordinates are commonly given as latitude, longitude (e.g. 36° 22′ 0″ N, 94° 12′ 48″ W for Bentonville, Arkansas), which would be [-94.2216, 36.3724]: also note the negative longitude because it's West of 0°.
Related
I've copied some code to make a basic scatterplot and I'm trying to color the dots based on one of the columns of the data.
I've tried modifying the dataset to have a column named "Color" with values between 0 and 1 but when I assign the color function (i.e. d3.interpolateRdGy(d[2]) ) the scatterplot has no dots on it.
I'm very, very new to d3 (I have experience only with ggplot2 in R).
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<script>
// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 60},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
//Read the data
d3.csv("https://raw.githubusercontent.com/holtzy/data_to_viz/master/Example_dataset/2_TwoNum.csv", function(data) {
// Add X axis
var x = d3.scaleLinear()
.domain([0, 4000])
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 500000])
.range([ height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Add dots
svg.append('g')
.selectAll("dot")
.data(data)
.enter()
.append("circle")
.attr("cx", function (d) { return x(d.GrLivArea); } )
.attr("cy", function (d) { return y(d.SalePrice); } )
.attr("r", 1.5)
.style("fill", d3.interpolateRdGy(d[1]))
})
</script>
Typically with D3, you would create a color scale. If you want to map a quantitative value to a color, then you could do
const color = d3.scaleSequential()
.domain(d3.extent(data, d => d.Color))
.interpolator(d3.interpolateBlues);
d3.extent(data, d => d.Color) returns an array that contains the min and max values of the "Color" column in your dataset. This approach will work even when the values are not between 0 and 1, unlike directly calling the interpolator.
You can find other color schemes in the d3-scale-chromatic docs. For diverging color schemes, you can use a diverging scale.
Then to use the color scale, you would do
.attr('fill', d => color(d.Color))
I am trying to create line graph from this data. Year should be in x-axis and value should be in y-axis
[{"year":2000,"value":1},{"year":2001,"value":2},
{"year":2002,"value":3},
{"year":2003,"value":4},{"year":2004,"value":5},
{"year":2005,"value":6}];
I don't understand why it is not working.
Code is in jsfiddle.
https://jsfiddle.net/u58x99vh/1/
Lots of issues here:
You are assigned "text" to a g element, it does not have such a thing.
Your x-axis is rotated back vertical, it shouldn't be. Also it's not placed at the bottom of the graph.
Finally, you are creating the path incorrectly. And you have no declaration of CSS class viiva.
Putting this all together:
let dataSorc = [{"vuosi":2000,"arvo":1},{"vuosi":2001,"arvo":2},{"vuosi":2002,"arvo":3},
{"vuosi":2003,"arvo":4},{"vuosi":2004,"arvo":5},{"vuosi":2005,"arvo":6}];
// 2. Use the margin convention practice
let margin = {top: 50, right: 50, bottom: 50, left: 50}
, width = window.innerWidth - margin.left - margin.right // Use the window's width
, height = window.innerHeight - margin.top - margin.bottom; // Use the window's height
// The number of datapoints
//let kokoelma = d3.values(dataSorc);
//console.log(kokoelma);
// 5. X scale will use the index of our data
let xScale = d3.scaleLinear()
.domain(d3.extent(dataSorc, d => d.vuosi)) // input
.range([0, width]); // output
// 6. Y
let yScale = d3.scaleLinear()
.domain(d3.extent(dataSorc, d => d.arvo))
// input
.range([height, 0]); // output
//console.log("yScale");
//console.log(yScale);
//console.log("xScale");
//console.log(xScale);
// 7. d3's line generator
let line = d3.line(dataSorc)
.x( d => xScale(d.vuosi)) // set the x values for the line generator
.y( d => yScale(d.arvo)); // set the y values for the line generator
console.log("line");
console.log(line);
let svg = d3.select("#taulu").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 + ")");
//let g = svg.append("g").
//console.log("svg");
//console.log(svg);
// 3. Call the x axis in a group tag
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale))
.attr("class", "x_axis")
//.text("x_axis");
// Create an axis component with d3.axisBottom
// 4. Call the y axis in a group tag
svg.append("g")
.call(d3.axisLeft(yScale))
.attr("class", "y_axis")
//.text("y_axis");
// Create an axis component with d3.axisLeft
// 12. Appends a line for each datapoint
svg.append("path")
.datum(dataSorc)
//.enter().append("path") // Uses the enter().append() method
.attr("class", "viiva")
.attr("d", line); // Assign a class for styling
//console.log("data tulostetaan");
//console.log(dataSorc);
//console.log("svg kaikki data on laitettu");
//console.log(svg);
//console.log("testaa path");
//console.log(d3.selectAll("path"));
.viiva {
stroke: steelblue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<p>
testi
</p>
<div id="taulu">
</div>
I reworked your fiddle, check it. There is a diff between your code and my.
You should parse your date this way:
const parseTime = d3.timeParse("%Y");
dataSorc.forEach(item => {
item.vuosi = parseTime(item.vuosi);
item.arvo = item.arvo;
});
And use scaleTime for x axis instead of scaleLinear:
let xScale = d3.scaleTime()
.domain(d3.extent(dataSorc, d => d.vuosi)) // input
.range([0, width]); // output
You have only one line, so you can draw it like this:
svg.append("path")
.attr("class", "viiva")
.attr("stroke", "#000")
.attr("d",item => line(dataSorc));
Let's say we draw our scatter plot not with SVG but with canvas.
So something like:
var scale = 1 + Math.floor(Math.random() * 10);
// Redraw axes
x.domain([0, scale]);
y.domain([0, scale]);
xg.call(xAxis);
yg.call(yAxis);
var points = randomPoints(scale),
colors = {};
// Update canvas
context.clearRect(0, 0, width, height);
picker.clearRect(0, 0, width, height);
points.forEach(function(p,i){
// Space out the colors a bit
var color = getColor(i * 1000 + 1);
colors[color] = p;
picker.fillStyle = "rgb(" + color + ")";
context.beginPath();
picker.beginPath();
context.arc(x(p[0]), y(p[1]), 10, 0, 2 * Math.PI);
picker.arc(x(p[0]), y(p[1]), 5, 0, 2 * Math.PI);
context.fill();
picker.fill();
});
Now how would we pass data into a mouseover event, say, to draw a tooltip?
The examples I've seen for tooltips all take for granted that you are handling an event with data bound to an element moused-over.
But what about for canvas?
I assume you'll need to use d3.bisector or something similar based on the x y coordinates of the mouse event.
One way is to iterate trough all the points and check if the x and y matches the vicinity of the click. This will definitely be slow when the scatter chart points are too many.{I think in your case you are making scatter chart in canvas just to over come that issue}
Other way is to make the use of quad tree.
First I am making some random 10000 points.
sampleData = d3.range(1000).map(function(d) {
var datapoint = {};
datapoint.id = "Sample Node " + d;
datapoint.x = Math.random() * 500;
datapoint.y = Math.random() * 500;
return datapoint;
})
Store all the points in the scatter chart in quad tree like this.
quadtree = d3.geom.quadtree()
.extent([[0,0], [500,500]]) //here 500 is the width and height of the canvas or the max x/y range of the points in scatter chart.
.x(function(d) {return d.x})
.y(function(d) {return d.y});
Pass all the points into quadtree:
quadData = quadtree(sampleData);
Now on click find the associated node data:
quadData = quadtree(sampleData);
d3.select("canvas").on("click", function(){
found = [];
//find in the vicinity of 10 pixel around the click.
search(quadData, d3.event.pageX -10, d3.event.pageY-10, d3.event.pageX +10, d3.event.pageY+10);
var message = "";
//iterate the found and make the message
found.forEach(function(d){
message += d.id + " ";
});
alert("selected Node" + message);
var data
})
Finally my search function to check for the nodes in the quadtree rectangle:
function search(quadtree, x0, y0, x3, y3) {
quadtree.visit(function(node, x1, y1, x2, y2) {
var p = node.point;
if (p) {
p.selected = (p.x >= x0) && (p.x < x3) && (p.y >= y0) && (p.y < y3);
if(p.selected){
found.push(p);
}
}
return x1 >= x3 || y1 >= y3 || x2 < x0 || y2 < y0;
});
}
Click on any of the circle and you will be alerted with the data it holds
working code here
I ended up using a solution suggested by Noah Veltman, as follows:
var margin = {top: 20, right: 10, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
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 factory = d3.geom.quadtree()
.extent([
[0, 0],
[width, height]
]);
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var xg = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")");
var yg = svg.append("g")
.attr("class", "y axis");
var chartArea = d3.select("body").append("div")
.style("left", margin.left + "px")
.style("top", margin.top + "px");
var canvas = chartArea.append("canvas")
.attr("width", width)
.attr("height", height);
var context = canvas.node().getContext("2d");
context.fillStyle = "#f0f";
// Layer on top of canvas, example of selection details
var highlight = chartArea.append("svg")
.attr("width", width)
.attr("height", height)
.append("circle")
.attr("r", 7)
.classed("hidden", true);
redraw();
function redraw() {
// Randomize the scale
var scale = 1 + Math.floor(Math.random() * 10);
// Redraw axes
x.domain([0, scale]);
y.domain([0, scale]);
xg.call(xAxis);
yg.call(yAxis);
var points = randomPoints(scale);
var tree = factory(points);
// Update canvas
context.clearRect(0, 0, width, height);
points.forEach(function(p,i){
context.beginPath();
context.arc(x(p[0]), y(p[1]), 5, 0, 2 * Math.PI);
context.fill();
});
canvas.on("mousemove",function(){
var mouse = d3.mouse(this),
closest = tree.find([x.invert(mouse[0]), y.invert(mouse[1])]);
highlight.attr("cx", x(closest[0]))
.attr("cy", y(closest[1]));
});
canvas.on("mouseover",function(){
highlight.classed("hidden", false);
});
canvas.on("mouseout",function(){
highlight.classed("hidden", true);
});
}
function randomPoints(scale) {
// Get points
return d3.range(1000).map(function(d){
return [
Math.random() * scale,
Math.random() * scale
];
});
}
The key is the use of
var factory = d3.geom.quadtree()
.extent([
[0, 0],
[width, height]
]);
Which has a getter setter function based on the x, y value pair of each point.
Long time lurker, enthusiastic about learning D3! Anywhoo, I'm working on following Mr. Bostock's paths examples.
Now, I have a fiddle that is working just wonderfully - it is smooth, updates quickly, scales the x-axis and y-axis quite nicely.
This is a time series chart I'm trying to tackle. Basically, I'm taking sensor integers that go into a JSON array. My target is to receive this data in realtime; Sensor sends a temp, and I get a smooth reflection of that data on the chart in near realtime. Here's the rub. As soon as I add in the JSON fetching code, everything goes from smooth as butter to choppy and crazy. Here's what I'm working with... Please be understand that I am very new to D3.
Here is the nicely randomized code which displays the graph in the way I want:
var n = 40,
duration = 750,
now = new Date(Date.now() - duration),
random = d3.random.normal(0, 0.2),
count = 0,
data = d3.range(n).map(random);
var margin = {
top: 20,
right: 20,
bottom: 20,
left: 40
},
width = 500 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var x = d3.time.scale()
.domain([now - (n - 2) * duration, now - duration])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, d3.max(data)])
.range([height, 0]);
var line = d3.svg.line()
.interpolate("basis")
.x(function (d, i) {
return x(now - (n - 1 - i) * duration);
})
.y(function (d, i) {
return y(d);
});
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("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var axis = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(x.axis = d3.svg.axis().scale(x).orient("bottom"));
var yAx = svg.append("g")
.attr("class", "y axis")
.call(y.axis = d3.svg.axis().scale(y).orient("left"));
var path = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.datum(data)
.attr("class", "line");
var transition = d3.select({}).transition()
.duration(750)
.ease("linear");
tick();
function tick() {
transition = transition.each(function () {
// update the domains
now = new Date();
x.domain([now - (n - 2) * duration, now - duration]);
y.domain([0, d3.max(data) + 10]);
// push a new data point onto the back
data.push(20 + Math.random() * 100);
// redraw the line, and slide it to the left
path.attr("d", line)
.attr("transform", null);
// slide the x-axis left, rescale the y-axis
axis.call(x.axis);
yAx.call(y.axis);
// slide the line left
path.transition()
.attr("transform", "translate(" + x(now - (n - 1) * duration) + ")");
// pop the old data point off the front
data.shift();
}).transition().each("start", tick);
}
svg {
font: 10px sans-serif;
}
.line {
fill: none;
stroke: orange;
stroke-width: 1.5px;
}
.axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Now, I realize that my poor understanding of transitions is probably getting me stuck. That being said, I need help! My trouble starts when I add the following:
First, I add var JSONdata = []; as a global, then right after function tick() {
transition = transition.each(function () { I add my JSON code:
d3.json("tempdata.php", function(error, data){
JSONdata = data;
data.forEach(function(d) {
JSONdata.temp = +d.temp;
})
I finish by wrapping up the end of the whole tick block with the final })
So it works... but it's slow and choppy! Methinks there be dragons in my transitions...? I'd like to have as near real time as possible, but even up to 5 seconds as a buffered delay would be acceptable.
Can anyone lend a hand or some enlightenment?
I'd like the graph to display a constant line with the last updated value, so even if the sensor goes offline, I'll see the last value. Granted, eventually I need to work out a round robin database type deal and actually face the issue of better timestamp handling. But, that can be for another day.
My JSFiddle with just the randomized data: http://jsfiddle.net/6z9qe46e/7/
I am quite new to d3.js. I am trying to make a scatter plot using the data present in a csv file. From the csv file, I am utilizing data from two columns.
d3.csv("test.csv",function (data) {
var margin = {top: 30, right: 10, bottom: 50, left: 60},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var xMax = d3.max(data, function(d) { return +d.Survivaltime; }),
xMin = 0,
yMax = d3.max(data, function(d) { return +d.Year; }),
yMin = 1950;
//Define scales
var x = d3.scale.linear()
.domain([xMin, xMax])
.range([0, width]);
var y = d3.scale.linear()
.domain([yMin, yMax])
.range([height, 0]);
});
// the chart object, includes all margins
var chart = d3.select('body')
.append('svg:svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'chart')
// the main object where the chart and axis will be drawn
var main = chart.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width)
.attr('height', height)
.attr('class', 'main')
// draw the x axis
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
.tickSize(-height)
.tickFormat(d3.format("s"));
main.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'main axis date')
.call(xAxis);
// draw the y axis
var yAxis = d3.svg.axis()
.scale(y)
.orient('left');
.ticks(5)
.tickSize(-width)
.tickFormat(d3.format("s"));
main.append('g')
.attr('transform', 'translate(0,0)')
.attr('class', 'main axis date')
.call(yAxis);
// draw the graph object
var svg = main.append("svg:g");
g.selectAll("scatter-dots")
.data(d.Year) // using the values in the ydata array
.enter().append("svg:circle") // create a new circle for each value
.attr("cy", function (d) { return y(d.Year); } ) // translate y value to a pixel
.attr("cx", function (d) { return x(d.Survivaltime); } ) // translate x value
.attr("r", 10) // radius of circle
.style("opacity", 0.6); // opacity of circle
here'sa link to the csv file:
http://bit.ly/14oLdml
Please help me.
Looks good for the most part. The only problems I could see (although there may be others that I'm missing) are near the end:
// draw the graph object
//var svg = main.append("svg:g");// <---- this is causing a bug. Should be:
var g = main.append("svg:g");
Then fix:
g.selectAll(".scatter-dots"); // <---- added a period before scatter-dots
The other thing, which is not breaking your code, but should be fixed:
After you .append("svg:circle"), you should call .attr("class", "scatter-dots") on it.