Circles not appending to coordinates - d3.js

Very new to D3, so thanks for being patient! I'm building a map where I want to append circles to certain cities on a US map. The lat and lon coordinates are in a .csv I'm hosting on GitHub.
Right now, the two circles I'm trying to append just appear in the far left corner of the map:
The Map
These are the errors I'm getting:
Console Errors
Here's the code:
<head>
<script>
function draw(geo_data) {
'use strict';
var margin = 75,
width = 1920 - margin,
height = 1080 - margin;
var svg = d3.select('body')
.append('svg')
.attr('width', width + margin)
.attr('height', height + margin)
.append('g')
.attr('class', 'map');
var projection = d3.geoAlbersUsa();
var path = d3.geoPath().projection(projection);
var map = svg.selectAll('path')
.data(geo_data.features)
.enter()
.append('path')
.attr('d', path)
.style('fill', 'rgba(253, 227, 167, 0.8)')
.style('stroke', 'black')
.style('stroke-width', 0.4);
d3.csv('top100cities.csv', function(error, data) {
svg.append('g')
.attr('class', 'bubble')
.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx', function(d) {
return projection([d.lon, d.lat]);
})
.attr('cy', function(d) {
return projection([d.lon, d.lat]);
})
.attr('r', 20)
.style('fill', 'rgba(103, 65, 114, 0.5)');
});
};
</script>
</head>
<body>
<script>
d3.json('us_states.json', draw);
</script>
</body>

You are setting the same value for both cx and cy:
.attr('cx', function(d) {
return projection([d.lon, d.lat]);
})
.attr('cy', function(d) {
return projection([d.lon, d.lat]);
})
That value is not either cx or cy, it is both: projection([d.lon,d.lat]) will return a two element array containing x and y, so you should be using:
.attr('cx', function(d) {
return projection([d.lon, d.lat])[0]; // x coordinate
})
.attr('cy', function(d) {
return projection([d.lon, d.lat])[1]; // y coordinate
})
The error occurs as you pass an array and not a number.

Related

How to give distance between the circles in d3

I was able to create circles with d3 but I'm stuck trying to figure out how to add space between them?
var myCircle= [20, 10, 50, 80, 40];
var height = 500
var width = 500
var mychart = d3.select('#chartD').append('svg')
.attr('width', width)
.attr('height', height)
.selectAll('circle')
.data(myCircle)
.enter()
.append('circle')
.attr("cx", function (d) { return d; })
.attr("cy", function (d) { return d; })
.attr("r", function (d) {
return d;
})
.style("fill", '#f4f4f4')
}

Using Math.sqrt() instead of d3.scaleSqrt() for sizing circles on map?

I am building a map where circles appended to cities on a US map are sized based upon a value in the CSV (guns column or d.guns in the JavaScript).
I was able to get the circles to resize using Math.sqrt() while appending the circles, but I do not think this is the right way to do it (or is it fine?):
.attr('r', function(d) {
return Math.sqrt(d.guns * 0.0010);
})
I attempted to use the d3.scaleSqrt() and d3.extent to resize the circles based upon the values, but I was thrown these errors in the console:
Here is the code when I attempted using d3.scaleSqrt:
<head>
<script>
function draw(geo_data) {
'use strict';
var margin = 75,
width = 1920 - margin,
height = 1080 - margin;
var svg = d3.select('body')
.append('svg')
.attr('width', width + margin)
.attr('height', height + margin)
.append('g')
.attr('class', 'map');
var projection = d3.geoAlbersUsa();
var path = d3.geoPath().projection(projection);
var map = svg.selectAll('path')
.data(geo_data.features)
.enter()
.append('path')
.attr('d', path)
.style('fill', 'rgba(253, 227, 167, 0.8)')
.style('stroke', 'black')
.style('stroke-width', 0.4);
d3.csv('top100cities.csv', function(error, data) {
// Converts strings to integers.
data.forEach(function(d) {
return d.guns = +d.guns;
})
var guns_extent = d3.extent(function(d) {
return d.guns;
});
var radius = d3.scaleSqrt()
.domain(guns_extent)
.range([0, 12]);
svg.append('g')
.attr('class', 'bubble')
.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx', function(d) {
return projection([d.lon, d.lat])[0];
})
.attr('cy', function(d) {
return projection([d.lon, d.lat])[1];
})
.attr('r', function(d) {
return radius(d.guns);
})
.style('fill', 'rgba(103, 65, 114, 0.5)');
});
};
</script>
<body>
<script>
d3.json('us_states.json', draw);
</script>
</body>
Although Xavier Guihot's answer is technically correct and proposes a working solution it slightly deviates from the D3 track. The error in your code was caused by not providing all parameters to d3.extent(); you simply forgot to pass in the array, namely data, from which to determine the extent (emphasis mine):
# d3.extent(array[, accessor]) <>
Returns the minimum and maximum value in the given array using natural order. Providing both, the array as well as the accessor, your code would look like this:
var guns_extent = d3.extent(data, function(d) { // Pass in data as first parameter
return d.guns;
});
Below is the working demo:
function draw(geo_data) {
'use strict';
var margin = 75,
width = 1920 - margin,
height = 1080 - margin;
var svg = d3.select('body')
.append('svg')
.attr('width', width + margin)
.attr('height', height + margin)
.append('g')
.attr('class', 'map');
var projection = d3.geoAlbersUsa();
var path = d3.geoPath().projection(projection);
var map = svg.selectAll('path')
.data(geo_data.features)
.enter()
.append('path')
.attr('d', path)
.style('fill', 'rgba(253, 227, 167, 0.8)')
.style('stroke', 'black')
.style('stroke-width', 0.4);
d3.csv("https://raw.githubusercontent.com/dieterholger/US-Gun-Manufacturing-Interactive/master/top100cities.csv", function(error, data) {
// Converts strings to integers.
data.forEach(function(d) {
return d.guns = +d.guns;
})
var guns_extent = d3.extent(data, function(d) { // Pass in data
return d.guns;
});
var radius = d3.scaleSqrt()
.domain(guns_extent)
.range([0, 12]);
svg.append('g')
.attr('class', 'bubble')
.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx', function(d) {
return projection([d.lon, d.lat])[0];
})
.attr('cy', function(d) {
return projection([d.lon, d.lat])[1];
})
.attr('r', function(d) {
return radius(d.guns);
})
.style('fill', 'rgba(103, 65, 114, 0.5)');
});
};
d3.json('https://raw.githubusercontent.com/dieterholger/US-Gun-Manufacturing-Interactive/master/us_states.json', draw);
<script src="https://d3js.org/d3.v4.js"></script>
The error, I think, was in the retrieval of the max number of guns.
Here is the diff:
let guns = data.map( function(d) { return d.guns });
console.log(Math.max(...guns));
var radius = d3.scaleSqrt().domain([0, Math.max(...guns)]).range([0, 25]);
Here is the modified demo:
<head>
<script>
function draw(geo_data) {
'use strict';
var margin = 75,
width = 1920 - margin,
height = 1080 - margin;
var svg = d3.select('body')
.append('svg')
.attr('width', width + margin)
.attr('height', height + margin)
.append('g')
.attr('class', 'map');
var projection = d3.geoAlbersUsa();
var path = d3.geoPath().projection(projection);
var map = svg.selectAll('path')
.data(geo_data.features)
.enter()
.append('path')
.attr('d', path)
.style('fill', 'rgba(253, 227, 167, 0.8)')
.style('stroke', 'black')
.style('stroke-width', 0.4);
d3.csv('https://raw.githubusercontent.com/dieterholger/US-Gun-Manufacturing-Interactive/master/top100cities.csv', function(error, data) {
let guns = data.map( function(d) { return d.guns });
console.log(Math.max(...guns));
var radius = d3.scaleSqrt().domain([0, Math.max(...guns)]).range([0, 25]);
svg.append('g')
.attr('class', 'bubble')
.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx', function(d) {
return projection([d.lon, d.lat])[0];
})
.attr('cy', function(d) {
return projection([d.lon, d.lat])[1];
})
.attr('r', function(d) {
return radius(d.guns);
})
.style('fill', 'rgba(103, 65, 114, 0.5)');
});
};
</script>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
d3.json('https://raw.githubusercontent.com/dieterholger/US-Gun-Manufacturing-Interactive/master/us_states.json', draw);
</script>
</body>
Setting up the formula for the area of a circle could work.
The formula is A=πr2, where A equals the area of the circle, r equals the radius.
if A = d.guns
We know Area and π
then:
r = √(d.guns/π)
.attr("r", function (d) {return Math.sqrt(d.guns/3.1416);})
https://www.wikihow.com/Calculate-the-Radius-of-a-Circle#/Image:Calculate-the-Radius-of-a-Circle-Step-10-Version-4.jpg

how to drag element with mouse move

After appending or creating a new circle on drag, I want to able to drag the circle around. I tried with the following code using .call(d3.behavior.drag()...) but don't know why it isn't working
Preview: http://jsbin.com/vabowofiqe/1/edit?html,output
code:
var svgContainer = d3.select("body").append("svg")
.attr("width", 800)
.attr("height", 803);
//Draw the Circle
var circle = svgContainer.append("circle")
.attr("cx", 35)
.attr("cy", 145)
.attr("r", 25)
.style("stroke-opacity", .9)
.style("stroke", "green")
.style("stroke-width", 2)
.style('cursor', 'move')
.style("fill", "white");
function move() {
d3.select(this)
.attr('x', d3.event.x)
.attr('y', d3.event.y);
};
var drag = d3.behavior.drag()
.origin(function ()
{
var t = d3.select(this);
return {x: t.attr("x"), y: t.attr("y")};
})
.on('dragend', function (d) {
var mouseCoordinates = d3.mouse(this);
if (mouseCoordinates[0] > 170) {
//Append new element
var newCircle = d3.select("svg").append("circle")
.classed("drg", true)
.attr("cx", 100)
.attr("cy", 100)
.attr("r", 20)
.attr("cx", mouseCoordinates[0])
.attr("cy", mouseCoordinates[1])
.style("fill", "white")
.style("stroke-width", 2)
.style("stroke", "#CDB483")
//Calling the drag behavior after clonning .call(
d3.behavior.drag()
.on('drag', move).origin(function () {
var t = d3.select(this);
return {x: t.attr("cx"), y: t.attr("cy")};
}));
;
}
});
circle.call(drag);
A SVG circle element doesn't have x and y properties, but cx and cy properties that can be used to position them. You should change your move function accordingly.
function move() {
d3.select(this)
.attr('cx', d3.event.x)
.attr('cy', d3.event.y);
};

Nodes not appearing for second line in d3.js

I have a chart with 2 lines using d3.js.Both the line comes from different dataset but y axis range is same for both the lines. I am displaying the nodes for both the line.Nodes for first line appears fine.But nodes for second line doesn't appear.Can anybody please tell me what is the problem?? Here is my code for nodes and line.
var vis = d3.select('#visualisation'),
WIDTH = 400,
HEIGHT = 400,
MARGINS = { top: 20, right: 20, bottom: 20, left: 50 },
xRange = d3.scale.ordinal().rangeBands([MARGINS.left, WIDTH - MARGINS.right], 0.4).domain(barData.map(function (d) { return d.x; })),
y1Range = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([0, d3.max(barData1, function (d) { return d.y1; })]),
xAxis = d3.svg.axis().scale(xRange).tickSize(5);
y1Axis = d3.svg.axis().scale(y1Range).tickSize(5).orient("right").tickSubdivide(true);
/*Draw X Axis values*/
vis.append('svg:g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + (HEIGHT-MARGINS.bottom) + ')')
.call(xAxis);
/*Draw Y1 Axis values*/
vis.append('svg:g')
.attr('class', 'y axis')
.attr('transform', 'translate(' + (HEIGHT - MARGINS.bottom) + ',0)')
.call(y1Axis);
/*Draw the First Line*/
var lineFunc = d3.svg.line()
.x(function (d) {
return (xRange(d.x))+MARGINS.right;
})
.y(function (d) {
return y1Range(d.y1);
})
.interpolate('linear');
/*Animate the line*/
var path1 = vis.append('svg:path')
.attr("d", lineFunc(barData1))
.attr("stroke", "#00CC66")
.attr("stroke-width", 2)
.attr("fill", "none");
var totalLength = path1.node().getTotalLength();
path1.attr("stroke-dasharray", totalLength + " " + totalLength)
.attr("stroke-dashoffset", totalLength)
.transition()
.duration(1000)
.ease("linear")
.attr("stroke-dashoffset", 0);
/*Draw the circles*/
var circles = vis.selectAll("circle").data(barData1);
circles.enter()
.insert("circle")
.attr("cx", function (d) { return (xRange(d.x))+MARGINS.right; })
.attr("cy", function (d) { return y1Range(d.y1); })
.attr("r", 3)
.style("fill", "#00CC66");
/*Draw the Second Line*/
var lineFunc1 = d3.svg.line()
.x(function (d) {
return (xRange(d.x))+MARGINS.right;
})
.y(function (d) {
return y1Range(d.y2);
})
.interpolate('linear');
var path2= vis.append('svg:path')
.attr("d", lineFunc1(barData2))
.attr("stroke", "#CB347D")
.attr("stroke-width", 2)
.attr("fill", "none")
.attr('class', 'line');
var totalLength = path1.node().getTotalLength();
path2.attr("stroke-dasharray", totalLength + " " +totalLength)
.attr("stroke-dashoffset", totalLength)
.transition()
.duration(1000)
.ease("linear")
.attr("stroke-dashoffset", 0);
/*Draw the circles for second line*/
var circles = vis.selectAll("circle").data(barData2);
circles.enter()
.insert("circle")
.attr("cx1", function (d) { return (xRange(d.x)) + MARGINS.right; })
.attr("cy2", function (d) { return y1Range(d.y2); })
.attr("r", 3)
.style("fill", "#CB347D");
The problem is that when adding the second set of circles, you're selecting the first set that has just been created:
vis.selectAll("circle").data(barData2)
This selection will contain all the circles you've just added. Then you're matching the data to it, which is fine, but the enter selection will be empty (all data items matched to existing circles). Therefore, the following code, which operates only on the enter selection, does nothing.
The easiest way to fix this is to add a distinguishing class to the second set of circles (and ideally the first one as well):
var circles = vis.selectAll("circle.second").data(barData2);
circles.enter()
.insert("circle")
.attr("class", "second")
// ...

D3 vertical line graph

I have a line (& area) graph which works ok, when horizontal. But I really need it to be vertical, to stand up. I have tried all kinds of changes to the existing code. Mostly with some strange results.
Here’s the code (modified it a bit). Is there a way to change it to make the graph vertical?:
var x = d3.scale.linear().domain([1, itemCount]).range([0, width]);
var y = d3.scale.linear().domain([0, maxValue]).rangeRound([height, 0]);
// Set up linar x and y axis.
var xAxis = d3.svg.axis().scale(x).ticks(10);
var yAxis = d3.svg.axis().scale(y).ticks(2).orient("left");
// Line graph.
line = d3.svg.line()
.interpolate("basis")
.x(function (d) {
return x(d.x);
})
.y(function (d) {
return y(d.y);
});
// Create SVG element.
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
// X-Axis, to bottom.
svg.append("svg:g")
.attr("class", "axis")
.attr("transform", "translate(1," + height + ")")
.call(xAxis);
//Y-Axis
svg.append("svg:g")
.attr("class", "axis")
.attr("transform", "translate(40, 1)")
.call(yAxis);
// Horizontal axis guide lines.
svg.selectAll("line.y")
.data(y.ticks(5))
.enter()
.append("line")
.attr("x1", 0)
.attr("x2", width)
.attr("y1", y)
.attr("y2", y)
.style("stroke", "#000000")
.style("stroke-opacity", 0.1);
// Vertical axis guide lines.
svg.selectAll("line.x")
.data(y.ticks(5))
.enter()
.append("line")
.attr("x1", x)
.attr("x2", x)
.attr("y1", 0)
.attr("y2", this.heightInside - pushDown)
.style("stroke", "#000000")
.style("stroke-opacity", 0.1);
// Set up domains. Nice ensures the domains ends on nice round values.
x.domain([dataValues[0].x, dataValues[dataValues.length - 1].x]).nice();
y.domain([d3.min(dataValues, function (d) { return (d.y); }),
d3.max(dataValues, function (d) { return (d.y); })])
.nice();
// Draw line on graph.
svg.append("svg:path")
.attr("class", "line")
.attr("d", line(dataValues))
.style("stroke", function(d) { return colors[i]; });
// Marks.
svg.selectAll("circle_" + i)
.data(dataValues)
.enter()
.append("circle")
.style("fill", function(d) { return _this.colors[i]; })
.attr("r", 4)
.attr('cx', function (d) { return x(d.x); })
.attr('cy', function (d) { return y(d.y); });

Resources