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
Related
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);
I have a map and a matching legend on my website. As the user selects different values from a select list, the map is updated and in the same function, the legend should be updated with new values. As the map actualization works properly, the values of the legend stay the same even in the console are logged the right values if I log the variables.
This is the function that draws the legend:
color_domain = [wert1, wert2, wert3, wert4, wert5];
ext_color_domain = [0, wert1, wert2, wert3, wert4, wert5];
console.log(ext_color_domain);
legend_labels = ["< "+wert1, ""+wert1, ""+wert2, ""+wert3, ""+wert4, "> "+wert5];
color = d3.scale.threshold()
.domain(color_domain)
.range(["#85db46", "#ffe800", "#ffba00", "#ff7d73", "#ff4e40", "#ff1300"]);
var legend = svg.selectAll("g.legend")
.data(ext_color_domain)
.enter().append("g")
.attr("class", "legend");
var ls_w = 20, ls_h = 20;
legend.append("rect")
.attr("x", 20)
.attr("y", function(d, i){ return height - (i*ls_h) - 2*ls_h;})
.attr("width", ls_w)
.attr("height", ls_h)
.style("fill", function(d, i) { return color(d); })
.style("opacity", 0.7);
legend.append("text")
.attr("x", 50)
.attr("y", function(d, i){ return height - (i*ls_h) - ls_h - 4;})
.text(function(d, i){ return legend_labels[i]; });
console.log(legend_labels); //gives the right legend_labels but doesn't display them correctly
};
Sadly even the map is updated with new colors they're colored with the old thresholds. This is the way the map is colored:
svg.append("g")
.attr("class", "id")
.selectAll("path")
.data(topojson.feature(map, map.objects.immoscout).features)
.enter().append("path")
.attr("d", path)
.style("fill", function(d) {
return color(rateById[d.id]);
})
This is tough to answer without a complete, working code sample but...
You are not handling the enter, update, exit pattern correctly. You never really update existing elements, you are only re-binding data and entering new ones.
Say you've called your legend function once already, now you have new data and you do:
var legend = svg.selectAll("g.legend")
.data(ext_color_domain)
.enter().append("g")
.attr("class", "legend");
This re-binds the data and computes an enter selection. It says, hey d3, what data elements are new? For those new ones, you then append a g. Further:
legend.append("rect")
.attr("x", 20)
.attr("y", function(d, i){ return height - (i*ls_h) - 2*ls_h;})
.attr("width", ls_w)
.attr("height", ls_h)
.style("fill", function(d, i) { return color(d); })
.style("opacity", 0.7);
Again, this is operating on those newly entered elements only. The ones that already existed on the page aren't touched at all.
Untested code, but hopefully it points you in the right direction:
// selection of all enter, update, exit
var legend = svg.selectAll("g.legend")
.data(ext_color_domain); //<-- a key function would be awesome here
legend.exit().remove(); //<-- did the data go away? remove the g bound to it
// ok, what data is coming in? create new elements;
var legendEnter = legend.enter().append("g")
.attr("class", "legend");
legendEnter.append("rect");
legendEnter.append("text");
// ok, now handle our updates...
legend.selectAll("rect")
.attr("x", 20)
.attr("y", function(d, i){ return height - (i*ls_h) - 2*ls_h;})
.attr("width", ls_w)
.attr("height", ls_h)
.style("fill", function(d, i) { return color(d); })
.style("opacity", 0.7);
legend.selectall("text")
...
There's some really great tutorials on this; and it's confusing as hell, but it's the foundation of d3.
An example that helps you get started with updating d3 (d3, v4):
const line = svg.selectAll('line').data(d3Data); // binds things
line.exit().remove(); // removes old data
line.enter()
.append('line') // add new lines for new items on enter
.merge(line) // <--- this will make the updates to the lines
.attr('fill', 'none')
.attr('stroke', 'red');
I am trying to set up a d3 force visualization with nodes and links. I have my nodes displaying properly but am having some trouble with links. Could someone take a look at my json file and then my code and guide me through the process of getting the links to display?
Here's the json data (sources and targets for links are at the bottom):
https://api.myjson.com/bins/4t8na
And here's the code for the visualization:
<script type= "text/javascript">
var w = 1000,
h = 650;
var svg = d3.select("body").append("svg")
.attr("height", 0)
.attr("width", 0)
.style("border", "1px solid black");
var data; // a global
var force = d3.layout.force()
.size([w, h])
.linkDistance([150])
.charge([-1050])
.gravity(0.5)
.on("tick", tick);
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
var circles = svg.selectAll(".node");
d3.json("https://api.myjson.com/bins/1rnhq", function(error, json) {
if (error) return console.warn(error);
data = json;
var nodes = data;
console.log(data);
force.nodes(data)//.links()
.start();
// Update nodes.
circles = circles.data(data);
circles.exit().remove();
var nodeEnter = circles.enter().append("g")
.attr("class", "node")
.style("fill", "#000")
.style("opacity", 0.75)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", click)
.call(force.drag);
nodeEnter.append("circle")
.attr("r", function(d) { return d.sector == "Academia" ? 1:5 });
nodeEnter.attr("cursor", "pointer");
//Update links
var links = svg.selectAll(".link")
.data(data.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", "1px");
links.exit().remove();
function mouseover() {
d3.select(this).select("circle").transition()
.duration(250)
.attr('r', 10);
}
function mouseout() {
d3.select(this).select("circle").transition()
.duration(250)
.attr('r', 5);
}
nodeEnter.append("text")
.attr("text-anchor", "middle")
.style("font-size", ".75em")
.attr("dy", "-0.85em").text(function (d) { return d.name });
var tooltip = svg.append("rect")
.attr("x", 1000)
.attr("y", 0)
.attr("width", 900)
.attr("height", 700)
.attr("opacity", 0.85);
function click() {
d3.select(tooltip).transition()
.duration(450)
.attr("x", 650)
};
});
function tick() {
links.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
circles.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
};
// create svg nodes for each json object: "sector"
// create svg nodes for each json object: "name"
// load links.json
// create svg links from links json file
// style links
// sort json objects by projects
// get google map: coastal virginia
// sort json objects: "name" by geography
// get googe map U.S.
</script>
The main problem is inside your JSON links
You have values like this:
{"source":52,"target":28},{"source":52,"target":29},{"source":52,"target":30},{"source":52,"target":31}
But there is no Node with index 52 thus everything was breaking on load.
However you code has lot of other errors like
circles.exit().remove();//this is incorrect coz circles in your case is not a selection
And many more :)
Working code here
Hope this helps!
I believe the problem is that data.links does not exist, what you have is data[#].links. So when you do .data(data.links) on your var links = ..., you are passing an undefined attribute there.
Try this:
var links = svg.selectAll(".link")
.data(data)
// ...
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.
I'm trying to create a basic d3 pie chart with a legend. I'm following the examples in two different tutorials and somehow code from one example isn't playing well with the other. What I'm trying to do is set an ordinal scale's domain so I can use that to create a legend.
On the following line, I set the domain. If I step through the code, I can see that immediately after I get ["HEURISTIC", "ADWARE", "COMPANY_BLACK_LIST", "PUP", "SUSPECTED_MALWARE", "KNOWN_MALWARE"]. This is exactly what I want.
color.domain(labels)
However, if I keep stepping through, once I reach the following line, the domain changes to ["HEURISTIC", "ADWARE", "COMPANY_BLACK_LIST", "PUP", "SUSPECTED_MALWARE", "KNOWN_MALWARE", 0, 1, 2, 3, 4, 5]
arcs.append("svg:path")
.attr("fill", function(d, i) { return color(i); } )
.attr("d", arc);
QUESTION: What is causing those six extra items to be inserted into the domain?
Code (http://jsfiddle.net/tonicboy/2urZY/5/):
var w = 150,
h = 100,
r = 50,
color = d3.scale.category20c(),
dataset = [{"name":"HEURISTIC","value":65},{"name":"ADWARE","value":75},{"name":"COMPANY_BLACK_LIST","value":9},{"name":"PUP","value":34},{"name":"SUSPECTED_MALWARE","value":14},{"name":"KNOWN_MALWARE","value":156}],
labels = _.pluck(dataset, "name");
color.domain(labels);
var chart = d3.select("#pie_chart")
.append("svg:svg")
.data([dataset])
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", "0 0 " + w + " " + h)
.attr("preserveAspectRatio", "xMinYMin meet");
var vis = chart.append("g")
.attr("transform", "translate(" + (w - r) + "," + r + ")");
var arc = d3.svg.arc()
.outerRadius(r);
var pie = d3.layout.pie()
.value(function(d) { return d.value; });
var arcs = vis.selectAll("g.slice")
.data(pie)
.enter()
.append("svg:g")
.attr("class", "slice");
arcs.append("svg:path")
.attr("fill", function(d, i) { return color(i); } )
.attr("d", arc);
var legend = chart.append("g")
.attr("class", "pie-legend")
.selectAll("g")
.data(color.domain())
.enter()
.append("g")
.attr("transform", function(d, i) { return "translate(0," + i * 7 + ")"; });
legend.append("rect")
.attr("width", 5)
.attr("height",5)
.style("fill", color);
legend.append("text")
.attr("x", 8)
.attr("y", 9)
.text(function(d) { return d; });
Here is what the chart looks like so far:
You're setting your ordinal scale domain with strings, but then calling it with index numbers. If you ask an ordinal scale for a value that isn't currently in its domain, it will add it to the domain and assign it the next value in the range (or recycle the range values if it runs out).
Original code:
arcs.append("svg:path")
.attr("fill", function(d, i) { return color(i); } )
.attr("d", arc);
Should be
arcs.append("svg:path")
.attr("fill", function(d, i) {return color( d.data.name); } )
.attr("d", arc);
The d value is the object created by the pie chart function; it stores the original data object as d.data. The name from that data is one of the values used in the color scale domain.
Updated fiddle: http://jsfiddle.net/2urZY/6/