I am very new to coding and am trying to learn D3. I have map of France which I am able to make appear in the browser. However, I am trying to display circles on the map based on a csv file. I am not sure what I am doing wrong with the function... Any help/direction would be great.
Here is a Cloud9 of the code and files... https://ide.c9.io/santiallende/d3-map-bubbles-france
I won't sugarcoat, your code's a mess.
You define and append 4 different svg elements to the body and you create 3 different projections. All of it is unnecessary. Just go through and remove all the redundancies.
//Width and height
var w = 800;
var h = 350;
var canvas = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h)
d3.json("france.json", function(data) {
var group = canvas.selectAll("g")
.data(data.features)
.enter()
.append("g")
//Define map projection
var projection = d3.geo.mercator()
.translate([400, 1200])
.scale([1100]);
//Define path generator
var path = d3.geo.path()
.projection(projection);
var areas = group.append("path")
.attr("d", path)
.attr("class", "area")
.attr("fill", "steelblue");
//Load in cities data
d3.csv("wineregions.csv", function(data) {
canvas.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", 5)
.style("fill", "yellow")
.style("opacity", 0.75);
});
});
Fixed code here.
Related
This is my first time using d3.js, so please bear with me. I am implementing this inside of a vue.js file as pure javascript.
I am trying to make a scatter plot with zooming capabilities. So far I have everything nearly working, but when I zoom I notice that the x-axis isn't scaling properly, but the y-axis is working properly. For instance, when looking at the original plot, a point may be at around 625 on the x-axis, but after zooming in the same point will be less than 600. This is not happening with the y-axis - those points scale properly. I am assuming that something is wrong with the scaling of the x-axis in my zoom function, but I just can't figure it out. Please take a look, and let me know if you can see where I went wrong.
Edit: I should mention that this is using d3.js version 7.4.4
<template>
<div id="reg_plot"></div>
</template>
<script>
import * as d3 from 'd3';
export default {
name: 'regCamGraph',
components: {
d3
},
methods: {
createSvg() {
// dimensions
var margin = {top: 20, right: 20, bottom: 30, left: 40},
svg_dx = 1400,
svg_dy =1000,
chart_dx = svg_dx - margin.right - margin.left,
chart_dy = svg_dy - margin.top - margin.bottom;
// data
var y = d3.randomNormal(400, 100);
var x_jitter = d3.randomUniform(-100, 1400);
var d = d3.range(1000)
.map(function() {
return [x_jitter(), y()];
});
// fill
var colorScale = d3.scaleLinear()
.domain(d3.extent(d, function(d) { return d[1]; }))
.range([0, 1]);
// y position
var yScale = d3.scaleLinear()
.domain(d3.extent(d, function(d) { return d[1]; }))
.range([chart_dy, margin.top]);
// x position
var xScale = d3.scaleLinear()
.domain(d3.extent(d, function(d) { return d[0]; }))
.range([margin.right, chart_dx]);
console.log("chart_dy: " + chart_dy);
console.log("margin.top: " + margin.top);
console.log("chart_dx: " + chart_dx);
console.log("margin.right: " + margin.right);
// y-axis
var yAxis = d3.axisLeft(yScale);
// x-axis
var xAxis = d3.axisBottom(xScale);
// zoom
var svg = d3.select("#reg_plot")
.append("svg")
.attr("width", svg_dx)
.attr("height", svg_dy);
svg.call(d3.zoom().on("zoom", zoom)); // ref [1]
// plot data
var circles = svg.append("g")
.attr("id", "circles")
.attr("transform", "translate(200, 0)")
.selectAll("circle")
.data(d)
.enter()
.append("circle")
.attr("r", 4)
.attr("cx", function(d) { return xScale(d[0]); })
.attr("cy", function(d) { return yScale(d[1]); })
.style("fill", function(d) {
var norm_color = colorScale(d[1]);
return d3.interpolateInferno(norm_color)
});
// add y-axis
var y_axis = svg.append("g")
.attr("id", "y_axis")
.attr("transform", "translate(75,0)")
.call(yAxis).style("font-size", "20px")
// add x-axis
var x_axis = svg.append("g")
.attr("id", "x_axis")
.attr("transform", `translate(${margin.left}, ${svg_dy - margin.bottom})`)
.call(xAxis).style("font-size", "20px")
function zoom(e) {
// re-scale y axis during zoom
y_axis.transition()
.duration(50)
.call(yAxis.scale(e.transform.rescaleY(yScale)));
// re-scale x axis during zoom
x_axis.transition()
.duration(50)
.call(xAxis.scale(e.transform.rescaleX(xScale)));
// re-draw circles using new y-axis scale
var new_xScale = e.transform.rescaleX(xScale);
var new_yScale = e.transform.rescaleY(yScale);
console.log(d);
x_axis.call(xAxis.scale(new_xScale));
y_axis.call(yAxis.scale(new_yScale));
circles.data(d)
.attr('cx', function(d) {return new_xScale(d[0])})
.attr('cy', function(d) {return new_yScale(d[1])});
}
}
},
mounted() {
this.createSvg();
}
}
</script>
Interestingly enough, after I set the clip region to prevent showing points outside of the axes the problem seemed to resolve itself. This is how I created the clip path:
// clip path
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("x", "0")
.attr("y", "0")
.attr('width', chart_dx)
.attr('height', chart_dy);
And I then added that attribute to the svg when plotting the data like this:
svg.append("g").attr("clip-path", "url(#clip)")
Updated clip path with plot data section:
// clip path
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("x", "0")
.attr("y", "0")
.attr('width', chart_dx)
.attr('height', chart_dy);
// plot data
var circles = svg.append("g")
.attr("id", "circles")
.attr("transform", "translate(75, 0)")
.attr("clip-path", "url(#clip)") //added here
.selectAll("circle")
.data(d)
.enter()
.append("circle")
.attr("r", 4)
.attr("cx", function(d) { return xScale(d[0]); })
.attr("cy", function(d) { return yScale(d[1]); })
.style("fill", function(d) {
var norm_color = colorScale(d[1]);
return d3.interpolateInferno(norm_color)
});
I ended up resolving this issue. I have updated the original post to show what worked for me.
Basically, after adding the clip region things started to work properly.
// clip path (this is the new clip region that I added. It prevents dots from being drawn outside of the axes.
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("x", "0")
.attr("y", "0")
.attr('width', chart_dx)
.attr('height', chart_dy);
// plot data
var circles = svg.append("g")
.attr("id", "circles")
.attr("transform", "translate(75, 0)")
.attr("clip-path", "url(#clip)") //added clip region to svg here
.selectAll("circle")
.data(d)
.enter()
.append("circle")
.attr("r", 4)
.attr("cx", function(d) { return xScale(d[0]); })
.attr("cy", function(d) { return yScale(d[1]); })
.style("fill", function(d) {
var norm_color = colorScale(d[1]);
return d3.interpolateInferno(norm_color)
});
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 made a map of a state that takes 3 different data sets(2 csv and 1 json) and pumps out a map of the state, with population data per country, and a circle on each major city.
My issue is when I run the code, 2 separate svg elements are created.
If I define the var svg=d3.select() outside the first d3.csv() function, the first svg element on the DOM is blank, and the second SVG elemnt gets the correct map.
If I place the svg=d3.select() inside the first d3.csv() function, both SVG elemnts get the map.
I cannot figure out why or where the second SVG is coming from, or why the code is running twice
The below code has the var svg=d3... inside the d3.csv... Everything on the map works, I removed a lot of filtering to make it easier to read, but I can add the code if you think I need to
var w = 960;
var h = 500;
//define the projection
var projection=d3.geoAlbers()
.translate([w/2, h/2])
.scale([1000]);
//Define path generator, using the Albers USA projection
var path = d3.geoPath()
.projection(projection);
//Create SVG element
//Load in GeoJson Data
var color=d3.scaleQuantize()
.range(['rgb(66,146,198)','rgb(33,113,181)','rgb(8,81,156)','rgb(8,48,107)'])
//load the migration data, which will fill the states
d3.csv("http://127.0.0.1:8000/whyleave/migrations.csv").then(function(data){
color.domain([
d3.min(data, function(d) {return d.exemptions;}),
d3.max(data, function(d) {return d.exemptions;})
]);
data=data.filter(function(d){
return d.State==stateab;})
d3.json("http://127.0.0.1:8000/whyleave/data.json").then(function(json){
var ga=json.features.filter(function(feature){
if (feature.properties.STATE == statenum)
return feature.properties.STATE
})
var state = {
"type": "FeatureCollection",
"features": ga
}
projection.scale(1).translate([0,0])
var b = path.bounds(state),
s = .95 / Math.max((b[1][0] - b[0][0]) / w, (b[1][1] - b[0][1]) / h),
t = [(w - s * (b[1][0] + b[0][0])) / 2, (h - s * (b[1][1] + b[0][1])) / 2];
projection
.scale(s)
.translate(t);
var svg = d3.select("#map")
.append("svg")
.attr("width", w)
.attr("height", h);
//Bind data and create one path per GeoJSON feature
svg.selectAll("path")
.data(state.features)
.enter()
.append('path')
.attr("class", "nation")
.attr("d", path)
.style("stroke", "#fff")
.style("stroke-width", "1")
.style("fill", function(d){
//get data value
var value=d.properties.value;
if (value){ return color(value);}
else{return "rgb(198,219,239)";}
});
d3.csv("http://127.0.0.1:8000/whyleave/cities.csv").then(function(city){
city=city.filter(function(d){
return d.state_id==stateab & d.population > 250000;})
svg.selectAll("circle")
.data(city)
.enter()
.append("circle")
.attr("cx", function(d){
return projection([d.lng, d.lat])[0];
})
.attr("cy", function(d){
return projection([d.lng, d.lat])[1];
})
.attr("r", "5")
.style("fill", "yellow")
.style("stroke", "gray")
.style("stroke-width", 0.25)
.style("opacity", 0.75);
svg.selectAll("text")
.data(city)
.enter()
.append("text")
.attr('class', 'label')
.attr("x", function(d){
return projection([d.lng, d.lat])[0];
})
.attr("y", function(d){
return projection([d.lng, d.lat])[1];})
.text(function(d){
return d.city;
})
.attr("fill", "red");
});
});});
I put the script lines on the html after the body, when I loaded them in the body everything worked fine
Please help me fix code. I use D3 v5.7.0 and i try draw doughnut.
Live demo is here.
My code is here:
const dataset = [
[ 5, 3 ]
];
const svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 500);
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("class", "outer-circle")
.attr("cx", 110)
.attr("cy", 110)
.attr("r", 10);
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("class", "inner-circle")
.attr("cx", 110)
.attr("cy", 110)
.attr("r", 5);
But browser display single black circle. Its problem
I'm not 100% clear on what you are trying to do. You're attaching the dataset, but you don't seem to be using it.
I'm assuming you want to, so here's an example using the data for the radius. Notice the css: it uses fill for the color not background.
You can also use fill and stroke instead of two circles.
const dataset = [ 5, 3 ];
const svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 500);
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", 110)
.attr("cy", 110)
.attr("r", function(d) { return d * 5})
.attr("class", function(d, i) {
return i ? "inner-circle": "outer-circle"
})
svg {
background: cyan;
}
.inner-circle {
fill: white;
}
.outer-circle {
fill: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Instead of background in your css you should use fill like this :
fill: rgb(255, 0, 0);
And what is that dataset doing there ? here is a simple doughnut by some edits to your code :
const svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 500);
svg.append("circle")
.attr("class", "outer-circle")
.attr("cx", 110)
.attr("cy", 110)
.attr("r", 10)
svg.append("circle")
.attr("class", "inner-circle")
.attr("cx", 110)
.attr("cy", 110)
.attr("r", 5)
svg {
background: cyan;
}
.inner-circle {
fill: rgb(255,255,255);
}
.outer-circle {
fill: rgb(255,0,0);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
D3 Donut Chart is a better/flexible/standard approach to draw a donut chart in d3 i.e. using d3 shape -arcs and d3 shapes - pies.
Here's a snippet using the above arc and pie generators and the dataset that you provide and drawing a donut out of it: (I'm using d3.schemeCategory10 to add colors to the arcs.
var dataset = [5, 3];
var height = 300,
width = 800,
radius = Math.min(width, height)/2;
var colorSchema = d3.schemeCategory10;
var arc = d3.arc()
.outerRadius(radius-10)
.innerRadius(radius-40);
var pie = d3.pie()
.sort(null)
.value(function (d) { return d; });
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height);
var g = svg.append('g').attr('transform', 'translate('+(width/2)+','+(height/2)+')');
var path = g.selectAll('.arc')
.data(pie(dataset))
.enter().append('g').classed('arc', true);
path.append('path').attr('d', arc).attr('fill', function(d, i) { return colorSchema[i];});
<script src="https://d3js.org/d3.v5.min.js"></script>
And adding some more values to the dataset, here's another snippet:
var dataset = [5, 3, 10, 20, 8];
var height = 300,
width = 800,
radius = Math.min(width, height)/2;
var colorSchema = d3.schemeCategory10;
var arc = d3.arc()
.outerRadius(radius-10)
.innerRadius(radius-40);
var pie = d3.pie()
.sort(null)
.value(function (d) { return d; });
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height);
var g = svg.append('g').attr('transform', 'translate('+(width/2)+','+(height/2)+')');
var path = g.selectAll('.arc')
.data(pie(dataset))
.enter().append('g').classed('arc', true);
path.append('path').attr('d', arc).attr('fill', function(d, i) { return colorSchema[i];});
<script src="https://d3js.org/d3.v5.min.js"></script>
I see that you've accepted an answer but I wanted to let you know about this approach as well which gives a lot of flexibility. Hope this helps too.
I am new to Javascript and i am trying to draw 4 radar charts. Each chart has different title. I create TitleOptions var and call it below. but it shows everything on every chart. Can I filter the title by using ChartID? I attached my code below. and could anyone help me with this?
<script>
var w = 200;
var h = 200;
var colorscale = d3.scale.category10();
//Legend, titles
var LegendOptions = ['Try Count','Succcess Count', 'Success Rate'];
var TitleOptions=['Try/Success Count Per Skill', 'Try/Success Rate Per Skill', 'Point Get Per Skill', 'Point Lose Per Skill']
////////////////////////////////////////////
/////////// Initiate legend ////////////////
////////////////////////////////////////////
var svg = d3.select('#body')
// .selectAll('svg')
.append('svg')
.attr("width", w+300)
.attr("height", h)
//Create the title for the legend
var text = svg.append('g').append("text")
.attr("class", "title")
.attr('transform', 'translate(90,0)')
.attr("x", 30)
.attr("y", 10)
.attr("font-size", "12px")
.attr("fill", "#404040")
// .text("What % of owners use a specific service in a week");
.text(TitleOptions);
//Initiate Legend
var legend = svg.append("g")
.attr("class", "legend")
.attr("height", 100)
.attr("width", 200)
.attr('transform', 'translate(90,20)')
;
//Create colour squares
legend.selectAll('rect')
.data(LegendOptions)
.enter()
.append("rect")
.attr("x", w - 65)
.attr("y", function(d, i){ return i * 20;})
.attr("width", 10)
.attr("height", 10)
.style("fill", function(d, i){ return colorscale(i);})
;
//Create text next to squares
legend.selectAll('text')
.data(LegendOptions)
.enter()
.append("text")
.attr("x", w - 52)
.attr("y", function(d, i){ return i * 20 + 9;})
.attr("font-size", "11px")
.attr("fill", "#737373")
.text(function(d) { return d; })
;
//Options for the Radar chart, other than default
var mycfg = {
w: w,
h: h,
maxValue: 0.6,
levels: 6,
ExtraWidthX: 300
}
//Load the data and Call function to draw the Radar chart
// dynamic data creation
d3.json("<c:url value='/chart/radarChartData.do?ChartID=${ChartID}&PlayerKey=${PlayerKey}'/>", function(error, data){
RadarChart.draw("#chart", JSONconverter(data.list), mycfg);
});
</script>
Encapsulate the drawing part into a function and call it four times. Something like:
function draw(title) {
const svg = ..
..
.text(title);
}
draw('title1');
draw('title2');
// or better:
['title1', 'title2'].forEach(draw);