Adding a line on an svg with image in background in d3 - d3.js

Hi Everyone,
I need some suggestions on a task i have been trying to do while learning d3. I have an svg with an image in the background. I am trying to draw a path on this svg post attaching the image.
While drawing a line on svg is not giving me trouble but once i add image and then try to draw perhaps the lines drawn go behind the image and aren't visible.
below is what i am trying:
var lineData = [{"Y": 778, "X": 256}, {"Y": 736, "X": 635}];
var linePathGenerator = d3.svg.line()
.x(function(d) { return d.X; })
.y(function(d) { return d.Y; })
.interpolate("linear");
//check to see if SVG Path Mini-Language Instructions are generated
linePathGenerator(lineData);
var svgContainer = d3.select("body")
.append("svg")
.attr("width", "800")
.attr("height", "800").append("image")
.attr("xlink:href", "https://dl.dropbox.com/s/qn8dzj5057urskc/blank.jpg");
var svgPath = svgContainer
.append("path")
.attr("stroke", "blue")
.attr("stroke-width", "4px")
.attr("fill", "none");
svgPath.attr("d", linePathGenerator(lineData));
Thanks in advance!

The reason why the line was not displayed is, it was rendering inside the image tag.
var svgContainer = d3.select("body")
.append("svg")
.attr("width", "800")
.attr("height", "800").append("image")
.attr("xlink:href", "https://dl.dropbox.com/s/qn8dzj5057urskc/blank.jpg");
Here, the svgContainer's reference is to the image tag rather than the svg.
To fix this use
var lineData = [{"Y": 10, "X": 10}, {"Y": 100, "X": 100}];
var linePathGenerator = d3.svg.line()
.x(function(d) { return d.X; })
.y(function(d) { return d.Y; })
.interpolate("linear");
//check to see if SVG Path Mini-Language Instructions are generated
linePathGenerator(lineData);
var svgContainer = d3.select("body")
.append("svg")
.attr("width", "800")
.attr("height", "800");
svgContainer.append("image")
.attr("xlink:href", "https://dl.dropbox.com/s/qn8dzj5057urskc/blank.jpg");
var svgPath = svgContainer
.append("path")
.attr("stroke", "blue")
.attr("stroke-width", "4px")
.attr("fill", "none");
svgPath.attr("d", linePathGenerator(lineData));
Working fiddle: https://jsfiddle.net/GunnerSS/7fvtcLrw/

Related

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

Unable to get scaleLinear() to populate values into SVG Rectangle's width

Please look at this index.html with simple d3.js scaling:
<body>
<script>
var canvasWidth = 750;
var canvasHeight = 600;
// Setup scales
d3.json("tt.json",
function(data)
{
var widthScale = d3.scaleLinear()
.domain([0, 3])
.range(0, canvasWidth);
var colorScale = d3.scaleLinear()
.domain([0, 10])
.range(["red", "blue"]);
var canvas = d3.select("body")
.append("svg")
.attr("width", canvasWidth)
.attr("height", canvasHeight)
canvas.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("width", function(d) { return widthScale(d.t_count); })
.attr("height", 30)
.attr("y", function(d, i) { return i * 50; })
.attr("fill", "blue")
canvas.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("fill", "black")
.attr("y", function(d, i) { return (i * 50 + 22); })
.text(function(d) { return d.ttext; })
}) // of function(data)
</script>
</body>
The .json file on the server looks like this:
[{"ttext":"Architect","t_count":"1"},
{"ttext":"Entertainment","t_count":"2"},
{"ttext":"Food","t_count":"2"},
{"ttext":"Gujarati","t_count":"1"},
{"ttext":"Laundry","t_count":"1"},
{"ttext":"Milk","t_count":"2"},
{"ttext":"Rajasthani","t_count":"1"}]
The output is just this:
(nothing but the ttext attribute from the json file.)
A hardcoded factor i.e., return (d.t_count * 30); does plot the rectangle. But when the scaling is introduced, the text is all that is output.
Here's the SVG DOM that is generated (strangely missing width in the rect):
Struggling with this for a day almost. Can anyone review and please help?
In a scale, both domain and range have to be arrays.
So, change to this:
var widthScale = d3.scaleLinear()
.domain([0, 3])
.range([0, canvasWidth]);//range as an array

D3: How to dynamically refresh a graph by changing the data file source?

How do I update the data on demand by changing the file d3 accesses? With a click, for example, it would read data from a new data file and add more nodes to the graph like AJAX.
I use d3.tsv to read in data.tsv, one of many files of the same format.
I made a simple graph to illustrate my question. Thanks in advance.
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 400,
height = 200;
var x = d3.scale.linear().range([0, width]),
y = d3.scale.linear().range([height, 0]);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
d3.tsv("data.tsv", function(error, data) {
if (error) console.warn(error);
x.domain(d3.extent(data, function(q) {return q.xCoord;}));
y.domain(d3.extent(data, function(q) {return q.yCoord;}));
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("r", 10)
.attr("cx", function(d) { return x(d.xCoord); })
.attr("cy", function(d) { return y(d.yCoord); })
});
</script>
update the graph
Try this.
var width = 400,
height = 200;
var x = d3.scale.linear().range([0, width]),
y = d3.scale.linear().range([height, 0]);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var dataSource = 'data.tsv',
dataSource2 = 'data2.tsv';
function updateChart(sourcefile) {
d3.tsv(sourcefile, function(error, data) {
if (error) console.warn(error);
x.domain(d3.extent(data, function(q) {return q.xCoord;}));
y.domain(d3.extent(data, function(q) {return q.yCoord;}));
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("r", 10)
.attr("cx", function(d) { return x(d.xCoord); })
.attr("cy", function(d) { return y(d.yCoord); })
});
}
updateChart(dataSource);
//here is where you change the data..
d3.select(#button).on("click", function() {
updateChart(dataSource2)
})

Creating a border around your D3 graph

So I've just started my D3 journey, and wanted to ask about how one would create a small 1px border around the chart.
I created the variables "border" and "bordercolor" and then I added .attr("border",border) to the var svg = d3.select("body") portion of my code. It doesn't crash, but I get no border either.
I guess the question is how do i add this border, and if someone could explain why what i did is wrong.
<script type="text/javascript">
//Width and height
var w = 800;
var h = 400;
var padding = 20;
var border=1;
var bordercolor='black';
var dataset = [
[5, 20], [480, 90], [250, 50], [100, 33], [330, 95],[-50,-100],[50,-45],
[410, 12], [475, 44], [25, 67], [85, 21], [220, 88],[-480, 90], [3,-90]
];
// create scale functions
var xScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[0]; }), d3.max(dataset, function(d) { return d[0]; })])
.range([padding, w - padding * 2]);
var yScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[0]; }), d3.max(dataset, function(d) { return d[1]; })])
.range([h - padding, padding]);
var rScale = d3.scale.linear()
.domain( [-100, d3.max(dataset, function(d) { return d[1]; })] )
.range([2,5]);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
.attr("border",border)
;
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", 3);
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d[0] + "," + d[1];
})
.attr("x", function(d) {
return xScale(d[0]);
})
.attr("y", function(d) {
return yScale(d[1]);
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "red");
</script>
Use the style attribute to place an outline around the svg:
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("style", "outline: thin solid red;") //This will do the job
.attr("width", w)
.attr("height", h);
The svg var is just a container. You need to add a path or element to the container and then give it the stroke color and width you want for your border. There is more than one way to do this. In this gist I did it by adding a rect with the following values:
var borderPath = svg.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("height", h)
.attr("width", w)
.style("stroke", bordercolor)
.style("fill", "none")
.style("stroke-width", border);
IMHO it's better to keep separated shape from style instructions:
.append("rect")
.attr("x", 5)
.attr("y", 5)
.attr("height", 40)
.attr("width", 50)
.attr("class","foo")
...
CSS:
svg rect.foo {
fill: white;
stroke-width: 0.5;
stroke: grey;
}
Simply use css:
svg {
border:1px solid black;
}
If X and Y Axis are used, other option is to use tickSizeOuter()
Example:
var yAxis = d3.axisLeft(y).ticks(5).tickSizeOuter(-width);
var xAxis = d3.axisBottom(x).ticks(5).tickSizeOuter(-height);

generate circle for each line of CSV D3.js

I'm trying to read in a CSV and generate a circle for each line using d3.js.
[I'd prefer not to use a callback function but that's a different question.]
I'm able to create a table for each line of data following: http://christopheviau.com/d3_tutorial/
but I can't seem to generate a circle for each line:
d3.text("test.csv", function(datasetText) {
var parsedCSV = d3.csv.parseRows(datasetText);
var sampleSVG2 = d3.select("#viz")
.append("svg")
.attr("width", 100)
.attr("height", 100);
sampleSVG2.selectall("circle")
.data(parsedCSV)
.enter().append("circle")
.style("stroke", "gray")
.style("fill", "blue")
This is probably not "correct" but it works. By calling a new svg area for each row and each circle it worked.
var sampleSVG = d3.select("#potato")
.data(parsedCSV)
.enter().append("circle")
.append("svg")
.attr("width", 100)
.attr("height", 100);
sampleSVG.append("circle")
.style("stroke", "gray")
.style("fill", "blue")
.attr("r", 40)
.attr("cx", 50)
.attr("cy", 50)
.on("mouseover", function(){d3.select(this).style("fill", "red");})
.on("mouseout", function(){d3.select(this).style("fill", "steelblue");});

Resources