I have a D3 map that should be filling US counties by how they fall within a range. I'm not having D3 determine that range for me, the data is such that 0, .5,1, and 10 work as quantiles.
The problem I'm having is that counties are not being filled with the appropriate color by where they are on the range. I know this because I can see the values via a tooltip. It's almost as if there's another value that is being cut into quantiles.
There is a complication in that I have a dropdown to select an additional variable to fill the map with (the same quantiles are fine for these variables)
Below is where I think my problem is, the initial code, the updateMap function or within the legend itself. Thank you for any suggestions.
var width = 800,
height = 500;
var statById = d3.map();
var quantile = d3.scale.quantile()
.domain([0,.5,1,10])
.range(['white','blue','red', 'green']);
function updateMap(key){
quantile.domain(counties.map(function(d){return d[key];}));
countyShapes
.transition().duration(1000).ease(d3.ease('linear'))
.attr("fill", function(d) {
if (statById.get(d.id)){
if(statById.get(d.id)[key] == 0){
return 'white';
}
else{
return quantile(statById.get(d.id)[key]);
}
}
else{
errorArray.push(d.id);
return "white";
}});
}
var legend = svg.selectAll('g.legendEntry')
.data(quantile.range().reverse())
.enter()
.append('g').attr('class', 'legendEntry');
legend
.append('rect')
.attr("x", width - 780)
.attr("y", function(d, i) {
return i * 20;
})
.attr("width", 10)
.attr("height", 10)
.style("stroke", "black")
.style("stroke-width", 1)
.style("fill", function(quantile){return quantile;});
//the data objects are the fill colors
legend
.append('text')
.attr("x", width - 760) //leave 5 pixel space after the <rect>
.attr("y", function(d, i) {
return i * 20;
})
.attr("dy", "0.8em") //place text one line *below* the x,y point
.text(function(d,i) {
var extent = quantile.invertExtent(d);
//extent will be a two-element array, format it however you want:
var format = d3.format("0.2f");
return format(+extent[0]) + " - " + format(+extent[1]);
});
And here is the entire script rendering the map:
<script>
var width = 800,
height = 500;
var statById = d3.map();
var quantile = d3.scale.quantile()
.domain([0,.5,1,10])
.range(['white','blue','red', 'green']);
var path = d3.geo.path();
var svg = d3.select("#map")
.attr("width", width)
.attr("height", height)
.append('svg:g')
.call(d3.behavior.zoom().on("zoom", redraw))
.append('svg:g');
svg.attr("transform", "scale( " + .9 + ")");
function redraw() {
console.log("here", d3.event.translate, d3.event.scale);
svg.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
d3.select("#selectPer")
.on("change", function(){menuChange();});
d3.select("#selectType")
.on("change", function(){menuChange();});
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 1e-6)
.style("background", "rgba(250,250,250,.7)");
tooltip.append("span").attr("id", "countyName")
queue()
.defer(d3.json, "us.json")
.defer(d3.csv, "mvpfinal020715.csv")
.defer(d3.json, "countyPop.json")
.await(ready);
errorArray = [];
var counties;
var countyPop;
function ready(error, us, countiesJSON, countyPopJSON) {
counties = countiesJSON;
countyPop = countyPopJSON;
counties.forEach(function(d){
try{
d.lq_11 = +d['lq_11'];
d.lq_21 = +d['lq_21'];
d.lq_22 = +d['lq_22'];
d.lq_23 = +d['lq_23'];
d.lq_42 = +d['lq_42'];
d.lq_51 = +d['lq_51'];
d.lq_52 = +d['lq_52'];
d.lq_53 = +d['lq_53'];
d.lq_55 = +d['lq_55'];
d.lq_56 = +d['lq_56'];
d.lq_61 = +d['lq_61'];
d.lq_62 = +d['lq_62'];
d.lq_71 = +d['lq_71'];
d.lq_72 = +d['lq_72'];
d.lq_81 = +d['lq_81'];
d.lq_92 = +d['lq_92'];
statById.set(+d.fips, d);
if (isNaN('white')){
}
}
catch(e){
//remove double lines of csv
}
});
quantile.domain(counties.map(function(d){return d.subPerCap;}));
countyShapes = svg.append("g")
.attr("class", "counties")
.selectAll("path")
.data(topojson.feature(us, us.objects.counties).features)
.enter().append("path")
countyShapes
.attr("fill", "rgb(200,200,200)")
.attr("d", path)
.on("mouseover", function(d){
d3.select(this)
.attr("stroke", "red")
.attr("stroke-width", 1)
tooltip
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 5) + "px")
.transition().duration(300)
.style("opacity", 1)
.style("display", "block")
updateDetails(statById.get(d.id));
})
.on("mouseout", function(d){
d3.select(this)
.attr("stroke", "")
.attr("stroke-width", .2)
tooltip.transition().duration(700).style("opacity", 0);
});
svg.append("path")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("class", "states")
.attr("d", path);
menuChange();
}
var printDetails = [
{'var': 'fips', 'print': 'FIPS'},
{'var': 'lq_21', 'print': 'Mining'},
{'var': 'lq_23', 'print': 'Construction'},
{'var': 'lq_51', 'print': 'Information'},
{'var': 'lq_52', 'print': 'Finance'},
{'var': 'No Criminal Conviction Return', 'print': 'Paperwork Returns'},
{'var': 'none', 'print': ''},
{'var': 'population', 'print': 'Population'},
{'var': 'Activation Date', 'print': 'Start Date'}];
function updateDetails(county){
tooltip.selectAll("div").remove();
tooltip.selectAll("div").data(printDetails).enter()
.append("div")
.append('span')
.text(function(d){return (d.print.length > 0) ? d.print + ": " : " - ";})
.attr("class", "boldDetail")
.insert('span')
.text(function(d){
if (d.var != 'none'){
return (""+county[d.var]).indexOf('/') == -1 ? totalFormat(county[d.var]) : county[d.var];
}})
.attr("class", "normalDetail");
d3.select("#countyName").text(county.County);
}
var totalFormat = d3.format(",");
function menuChange(){
var selectPer = document.getElementById('selectPer');
selectPerValue = selectPer.options[selectPer.selectedIndex].value;
var selectType = document.getElementById('selectType');
selectTypeValue = selectType.options[selectType.selectedIndex].value;
var keyName = selectTypeValue + (selectPerValue == 'PerCap' ? 'PerCap' : '');
console.log(keyName);
updateMap(keyName);
console.log(d3.sum(counties, function(d){return d[selectTypeValue];}));
var num = d3.sum(counties, function(d){return d[selectTypeValue];});
d3.select("#magicNum")
.text(selectPerValue == 'PerCap' ? d3.round(num*10000/313000000, 3) + " per 100,000" : totalFormat(num));
}
function updateMap(key){
quantile.domain(counties.map(function(d){return d[key];}));
countyShapes
.transition().duration(1000).ease(d3.ease('linear'))
.attr("fill", function(d) {
if (statById.get(d.id)){
if(statById.get(d.id)[key] == 0){
return 'white';
}
else{
return quantile(statById.get(d.id)[key]);
}
}
else{
errorArray.push(d.id);
return "white";
}});
}
var legend = svg.selectAll('g.legendEntry')
.data(quantile.range().reverse())
.enter()
.append('g').attr('class', 'legendEntry');
legend
.append('rect')
.attr("x", width - 780)
.attr("y", function(d, i) {
return i * 20;
})
.attr("width", 10)
.attr("height", 10)
.style("stroke", "black")
.style("stroke-width", 1)
.style("fill", function(quantile){return quantile;});
//the data objects are the fill colors
legend
.append('text')
.attr("x", width - 760) //leave 5 pixel space after the <rect>
.attr("y", function(d, i) {
return i * 20;
})
.attr("dy", "0.8em") //place text one line *below* the x,y point
.text(function(d,i) {
var extent = quantile.invertExtent(d);
//extent will be a two-element array, format it however you want:
var format = d3.format("0.2f");
return format(+extent[0]) + " - " + format(+extent[1]);
});
</script>
Related
I have a vertical line graph in d3 and I'm trying to add a tooltip in the form of a line and circle that follows the cursor movement in the vertical. The latter works but the circle fails to follow the path - I've tried different variations but the circle never follows the path, it's currently just following the y-axis of the graph (see attached image for example). I've achieved the same effect for a horizontal plot but when I try to adapt the code for a vertical graph I just can't get the circle to work properly.
I've brought an example together with the code below, still very new to javascript so code is a bit of a mess.
Screenshot of graph with circle (red) failing to follow the path:
function test(test_data) {
// setup params
var margin_ = {top: 30, right: 60, bottom: 30, left: 20},
width_ = 300
height_ = 700
// Add svg
var line_graph = d3.select("#my_dataviz_test")
.append("svg")
.attr("width", width_ + 100)
.attr("height", height_)
.append("g")
.attr("transform",
"translate(" + margin_.left + "," + margin_.top + ")");
d3.csv(test_data,
function(d){
return { output_time_ref: d.output_time_ref = +d.output_time_ref,
output_time: d3.timeParse("%d/%m/%Y %H:%M")(d.output_time),
prediction: d.prediction = +d.prediction,
}
},
function(data) {
// Add x axis
var x_test = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return +d.prediction; })])
.range([ 0, width_ ]);
line_graph.append("g")
.attr("transform", "translate(" + 0 + "," + height_ + ")")
.call(d3.axisBottom(x_test).tickSizeOuter(0).tickSizeInner(0).ticks(2))
.select(".domain").remove();
// Add Y axis
var y_test = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return +d.output_time_ref; })])
.range([ height_, 0 ]);
line_graph.append("g")
.call(d3.axisLeft(y_test).tickSizeOuter(0).tickSizeInner(0).ticks(5))
.select(".domain").remove();
// Add the line
path_test = line_graph.append("path")
.datum(data)
.attr("fill", "none")
.attr("fill", "steelblue")
.attr("fill-opacity", 0.2)
.attr("stroke", "steelblue")
.attr("stroke-width", 1)
.attr("d", d3.line()
.curve(d3.curveBasis)
.x(function(d) { return x_test(d.prediction) })
.y(function(d) { return y_test(d.output_time_ref) })
)
var mouseG2 = line_graph
.append("g")
.attr("class", "mouse-over-effects");
mouseG2
.append("path")
.attr("class", "mouse-line2")
.style("stroke", "#393B45")
.style("stroke-width", "0.5px")
.style("opacity", 0.75)
mouseG2.append("text")
.attr("class", "mouse-text2")
var totalLength2 = path_test.node().getTotalLength();
var mousePerLine2 = mouseG2.selectAll('.mouse-per-line2')
.data(data)
.enter()
.append("g")
.attr("class", "mouse-per-line2");
mousePerLine2.append("circle")
.attr("r", 8)
.style("stroke", 'red')
.style("fill", "none")
.style("stroke-width", "2px")
.style("opacity", "0");
mouseG2
.append('svg:rect')
.attr('width', width_)
.attr('height', height_)
.attr('fill', 'none')
// .attr('opacity', 0.2)
.attr('pointer-events', 'all')
.on('mouseout', function() {
d3.select("#my_dataviz_test")
.selectAll(".mouse-per-line2 circle")
.style("opacity", "0"); })
var mouseover = function(d) {
d3.select("#my_dataviz_test")
.select(".mouse-line2")
.style("opacity", "1")
.select(".mouse-text2")
.style("opacity", "1")
.select(".mouse-per-line2 circle")
.style("opacity", "1");
///////////////////////////////////////////////////
d3.select("#my_dataviz_test")
var mouse2 = d3.mouse(this);
d3.select("#my_dataviz_test")
.select(".mouse-text2")
.attr("y", mouse2[1])
.attr("transform", "translate(" + (mouse2[1]+60) + "," + (mouse2[1]+5) + ") rotate(90)")
d3.select("#my_dataviz_test")
.select(".mouse-line2")
.attr("d", function() {
var d = "M" + width_ + "," + mouse2[1];
d += " " + 0 + "," + mouse2[1];
return d;
})
d3.select("#my_dataviz_test")
.selectAll(".mouse-per-line2")
.attr("transform", function(d, i) {
var beginning2 = 0,
end2 = totalLength2
target2 = null;
while (true){
target2 = Math.floor((beginning2 + end2) / 2);
var pos2 = path_test.node().getPointAtLength(target2);
if ((target2 === end2 || target2 === beginning2) && pos2.y !== mouse2[1]) {
break;
}
if (pos2.y > mouse2[1]) { end2 = target2; }
else if (pos2.y < mouse2[1]) { beginning2 = target2; }
else {break};
}
d3.select("#my_dataviz_test").select('circle')
.style("opacity", 1)
return "translate(" + (pos2.x) + "," + mouse2[1] +")";
});
///////////////////////////////////////////////////
}
var mouseleave = function(d) {
d3.select("#my_dataviz_test")
.select(".mouse-line2")
.style("opacity", "0")
d3.select("#my_dataviz_test")
.select(".circle")
.style("opacity", "0")
}
line_graph
.on("mouseover", mouseover)
.on("mouseleave", mouseleave)
})
}
test_data.csv:
output_time_ref,output_time,prediction
0,04/01/2013 00:00,0
1,04/01/2013 00:30,0
2,04/01/2013 01:00,0
3,04/01/2013 01:30,0
4,04/01/2013 02:00,0
5,04/01/2013 02:30,0
6,04/01/2013 00:00,0
7,04/01/2013 03:30,0
8,04/01/2013 04:00,0
9,04/01/2013 04:30,8.17E-05
10,04/01/2013 05:00,0.002014463
11,04/01/2013 05:30,0.01322314
12,04/01/2013 06:00,0.033264463
13,04/01/2013 06:30,0.059607438
14,04/01/2013 07:00,0.098553719
15,04/01/2013 07:30,0.145661157
16,04/01/2013 08:00,0.186983471
17,04/01/2013 08:30,0.225206612
18,04/01/2013 09:00,0.267561983
19,04/01/2013 09:30,0.314049587
20,04/01/2013 10:00,0.334710744
21,04/01/2013 10:30,0.350206612
22,04/01/2013 11:00,0.359504132
23,04/01/2013 11:30,0.375
24,04/01/2013 12:00,0.393595041
25,04/01/2013 12:30,0.396694215
26,04/01/2013 13:00,0.393595041
27,04/01/2013 13:30,0.385330579
28,04/01/2013 14:00,0.367768595
29,04/01/2013 14:30,0.344008264
30,04/01/2013 15:00,0.320247934
31,04/01/2013 15:30,0.297520661
32,04/01/2013 16:00,0.273760331
33,04/01/2013 16:30,0.254132231
34,04/01/2013 17:00,0.216942149
35,04/01/2013 17:30,0.167355372
36,04/01/2013 18:00,0.123966942
37,04/01/2013 18:30,0.080785124
38,04/01/2013 19:00,0.041115702
39,04/01/2013 19:30,0.015805785
40,04/01/2013 20:00,0.002489669
41,04/01/2013 20:30,2.67E-05
42,04/01/2013 21:00,1.24E-05
43,04/01/2013 21:30,0
44,04/01/2013 22:00,0
45,04/01/2013 22:30,0
46,04/01/2013 23:00,0
47,04/01/2013 23:30,0
You can precisely compute x by y using the input data:
const y = d3.event.layerY - margin_.top;
const curY = y_test.invert(y);
const minY = Math.floor(curY);
const maxY = Math.ceil(curY);
if (data[minY] && data[maxY]) {
const yDelta = curY - minY;
const minP = data[minY].prediction;
const maxP = data[maxY].prediction;
const curP = minP + (maxP - minP) * yDelta;
const xPos = x_test(curP)
...
}
See it's working in the snippet:
const csvData = `output_time_ref,output_time,prediction
0,04/01/2013 00:00,0
1,04/01/2013 00:30,0
2,04/01/2013 01:00,0
3,04/01/2013 01:30,0
4,04/01/2013 02:00,0
5,04/01/2013 02:30,0
6,04/01/2013 00:00,0
7,04/01/2013 03:30,0
8,04/01/2013 04:00,0
9,04/01/2013 04:30,8.17E-05
10,04/01/2013 05:00,0.002014463
11,04/01/2013 05:30,0.01322314
12,04/01/2013 06:00,0.033264463
13,04/01/2013 06:30,0.059607438
14,04/01/2013 07:00,0.098553719
15,04/01/2013 07:30,0.145661157
16,04/01/2013 08:00,0.186983471
17,04/01/2013 08:30,0.225206612
18,04/01/2013 09:00,0.267561983
19,04/01/2013 09:30,0.314049587
20,04/01/2013 10:00,0.334710744
21,04/01/2013 10:30,0.350206612
22,04/01/2013 11:00,0.359504132
23,04/01/2013 11:30,0.375
24,04/01/2013 12:00,0.393595041
25,04/01/2013 12:30,0.396694215
26,04/01/2013 13:00,0.393595041
27,04/01/2013 13:30,0.385330579
28,04/01/2013 14:00,0.367768595
29,04/01/2013 14:30,0.344008264
30,04/01/2013 15:00,0.320247934
31,04/01/2013 15:30,0.297520661
32,04/01/2013 16:00,0.273760331
33,04/01/2013 16:30,0.254132231
34,04/01/2013 17:00,0.216942149
35,04/01/2013 17:30,0.167355372
36,04/01/2013 18:00,0.123966942
37,04/01/2013 18:30,0.080785124
38,04/01/2013 19:00,0.041115702
39,04/01/2013 19:30,0.015805785
40,04/01/2013 20:00,0.002489669
41,04/01/2013 20:30,2.67E-05
42,04/01/2013 21:00,1.24E-05
43,04/01/2013 21:30,0
44,04/01/2013 22:00,0
45,04/01/2013 22:30,0
46,04/01/2013 23:00,0
47,04/01/2013 23:30,0`;
var margin_ = {top: 30, right: 60, bottom: 30, left: 20},
width_ = 300
height_ = 700
// Add svg
var line_graph = d3.select("#my_dataviz_test")
.append("svg")
.attr("width", width_ + 100)
.attr("height", height_)
.append("g")
.attr("transform",
"translate(" + margin_.left + "," + margin_.top + ")");
const point = line_graph.append('circle')
.attr('r', 5)
.style('fill', 'red');
const data = d3.csvParse(csvData).map(d => ({
output_time_ref: +d.output_time_ref,
output_time: d3.timeParse("%d/%m/%Y %H:%M")(d.output_time),
prediction: +d.prediction,
}));
console.log(data);
// Add x axis
var x_test = d3.scaleLinear()
.domain([0, d3.max(data, d => d.prediction)])
.range([ 0, width_ ]);
line_graph.append("g")
.attr("transform", "translate(" + 0 + "," + height_ + ")")
.call(d3.axisBottom(x_test).tickSizeOuter(0).tickSizeInner(0).ticks(2))
.select(".domain").remove();
// Add Y axis
var y_test = d3.scaleLinear()
.domain([0, d3.max(data, d => +d.output_time_ref)])
.range([ height_, 0 ]);
line_graph.append("g")
.call(d3.axisLeft(y_test).tickSizeOuter(0).tickSizeInner(0).ticks(5))
.select(".domain").remove();
// Add the line
path_test = line_graph.append("path")
.datum(data)
.attr("fill", "none")
.attr("fill", "steelblue")
.attr("fill-opacity", 0.2)
.attr("stroke", "steelblue")
.attr("stroke-width", 1)
.attr("d", d3.line()
.curve(d3.curveBasis)
.x(function(d) { return x_test(d.prediction) })
.y(function(d) { return y_test(d.output_time_ref) })
)
line_graph.on('mousemove', () => {
const y = d3.event.layerY - margin_.top;
const curY = y_test.invert(y);
const minY = Math.floor(curY);
const maxY = Math.ceil(curY);
if (data[minY] && data[maxY]) {
const yDelta = curY - minY;
const minP = data[minY].prediction;
const maxP = data[maxY].prediction;
const curP = minP + (maxP - minP) * yDelta;
const xPos = x_test(curP)
// console.log(xPos);
point
.attr('cx', xPos)
.attr('cy', y)
}
// line_graph
// y_test
})
/*
var mouseG2 = line_graph
.append("g")
.attr("class", "mouse-over-effects");
mouseG2
.append("path")
.attr("class", "mouse-line2")
.style("stroke", "#393B45")
.style("stroke-width", "0.5px")
.style("opacity", 0.75)
mouseG2.append("text")
.attr("class", "mouse-text2")
var totalLength2 = path_test.node().getTotalLength();
var mousePerLine2 = mouseG2.selectAll('.mouse-per-line2')
.data(data)
.enter()
.append("g")
.attr("class", "mouse-per-line2");
mousePerLine2.append("circle")
.attr("r", 8)
.style("stroke", 'red')
.style("fill", "none")
.style("stroke-width", "2px")
.style("opacity", "0");
mouseG2
.append('svg:rect')
.attr('width', width_)
.attr('height', height_)
.attr('fill', 'none')
// .attr('opacity', 0.2)
.attr('pointer-events', 'all')
.on('mouseout', function() {
d3.select("#my_dataviz_test")
.selectAll(".mouse-per-line2 circle")
.style("opacity", "0"); })
var mouseover = function(d) {
d3.select("#my_dataviz_test")
.select(".mouse-line2")
.style("opacity", "1")
.select(".mouse-text2")
.style("opacity", "1")
.select(".mouse-per-line2 circle")
.style("opacity", "1");
d3.select("#my_dataviz_test")
var mouse2 = d3.mouse(this);
d3.select("#my_dataviz_test")
.select(".mouse-text2")
.attr("y", mouse2[1])
.attr("transform", "translate(" + (mouse2[1]+60) + "," + (mouse2[1]+5) + ") rotate(90)")
d3.select("#my_dataviz_test")
.select(".mouse-line2")
.attr("d", function() {
var d = "M" + width_ + "," + mouse2[1];
d += " " + 0 + "," + mouse2[1];
return d;
})
d3.select("#my_dataviz_test")
.selectAll(".mouse-per-line2")
.attr("transform", function(d, i) {
var beginning2 = 0,
end2 = totalLength2
target2 = null;
while (true){
target2 = Math.floor((beginning2 + end2) / 2);
var pos2 = path_test.node().getPointAtLength(target2);
if ((target2 === end2 || target2 === beginning2) && pos2.y !== mouse2[1]) {
break;
}
if (pos2.y > mouse2[1]) { end2 = target2; }
else if (pos2.y < mouse2[1]) { beginning2 = target2; }
else {break};
}
d3.select("#my_dataviz_test").select('circle')
.style("opacity", 1)
return "translate(" + (pos2.x) + "," + mouse2[1] +")";
});
}
var mouseleave = function(d) {
d3.select("#my_dataviz_test")
.select(".mouse-line2")
.style("opacity", "0")
d3.select("#my_dataviz_test")
.select(".circle")
.style("opacity", "0")
}
line_graph
.on("mouseover", mouseover)
.on("mouseleave", mouseleave);
*/
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div id="my_dataviz_test" />
I have a double nested data set in d3. I want to create a scatter plot to be updated for each value of the first key ("time" variable), but the data of each point is bound to the values of he second key ("space" variable). So, to be clear, each point should be translated to new coordinates and its radius must be updated too.
Here is a data sample (in file "prosperLoanData.csv")
BorrowerState,LoanOriginationDate,LoanOriginalAmount,LenderYield
AK,2007-01-01 00:00:01,1000,0.1
AK,2007-01-01 00:00:01,2000,0.11
AK,2007-01-01 00:00:01,1500,0.09
AK,2007-01-01 00:00:01,500,0.1
AK,2008-01-01 00:00:01,2500,0.07
AK,2008-01-01 00:00:01,3000,0.06
AK,2008-01-01 00:00:01,3500,0.0652
OK,2007-01-01 00:00:01,4000,0.08
OK,2007-01-01 00:00:01,4100,0.081
OK,2007-01-01 00:00:01,4500,0.0812
OK,2007-01-01 00:00:01,4600,0.0799
OK,2007-01-01 00:00:01,3900,0.08
OK,2008-01-01 00:00:01,5000,0.05
And here is my code. I though that deleting the YearGroups1.exit().remove() was enough, but it is not. I can not properly select the circles and rebind them to the new dataset (at the second nested level). Can you help me?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v5.min.js"></script>
<style>
div.tooltip {
position: absolute;
text-align: center;
width: 180px;
height: 45px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
</style>
<script type="text/javascript">
</script>
</head>
<body>
<script type="text/javascript">
/*
Use D3 to load the loan data
*/
var parseTime = d3.timeParse("%Y-%m-%d %H:%M:%S")
// load data
d3.csv("prosperLoanData.csv").then(function(data) {
// Setting global parameters
"use strict";
// ----> Margin & size
var margin = 75,
width = 1400 - margin,
height = 600 - margin,
op = 0.5,
years = [2007, 2008];
// ----> Format for strings
var formatDec = d3.format(".0f"),
formatper = d3.format(".2%"),
formatM = d3.format("\$.2s");
// change string (from CSV) into number format
data.forEach( function(d) {
d["Year"] = parseTime(d.LoanOriginationDate).getYear() + 1900;
d["LoanOriginalAmount"] = +d["LoanOriginalAmount"];
d["LenderYield"] = +d["LenderYield"];
// debugger
return d; });
// Function definition
// function update() {};
function key_func(d) {
return d['key'];
}
// FIXED PART
// Define the svg element
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin)
.attr("height", height + margin);
// Define the div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// Data preprocessing - grouping
var nested_year = d3.nest()
.key( function(d) { return d.Year } )
.key( function(d) { return d.BorrowerState })
.rollup( function(points) {
// debugger
var mean_loan = d3.mean(points, function(d) { return d.LoanOriginalAmount; } );
var mean_lenderyield = d3.mean(points, function(d) { return d.LenderYield; } );
// var std_loan = d3.deviation(points, function(d) { return d.LoanOriginalAmount; } );
var sum_loan = d3.sum(points, function(d) { return d.LoanOriginalAmount; } );
var max_loan = d3.max(points, function(d) { return d.LoanOriginalAmount; } );
var min_loan = d3.min(points, function(d) { return d.LoanOriginalAmount; } );
var max_ly = d3.max(points, function(d) { return d.LenderYield; } );
var min_ly = d3.min(points, function(d) { return d.LenderYield; } );
// debugger
return {
"meanLoan" : mean_loan,
"meanLenderyield" : mean_lenderyield,
// "stdLoan" : std_loan,
"sumLoan" : sum_loan,
};
}
)
.entries(data);
// Determining X/Y Max & Min
var LOA_E1 = d3.min(nested_year, function(d) {return d3.min(d.values, function(da) { return da.value.meanLoan; });})
var LOA_E2 = d3.max(nested_year, function(d) {return d3.max(d.values, function(da) { return da.value.meanLoan; });})
var LY_E1 = d3.min(nested_year, function(d) {return d3.min(d.values, function(da) { return da.value.meanLenderyield; });})
var LY_E2 = d3.max(nested_year, function(d) {return d3.max(d.values, function(da) { return da.value.meanLenderyield; });})
var LenderYield_Extent = [LY_E1 , LY_E2 ];
var LoanOriginalAmount_Extent = [LOA_E1 , LOA_E2];
// Creating a scale
var XScale = d3.scaleLinear()
.range([margin , width])
.domain([ 1.05 * LenderYield_Extent[0] - 0.05 * LenderYield_Extent[1] ,
1.05 * LenderYield_Extent[1] - 0.05 * LenderYield_Extent[0] ] );
// debugger
var YScale = d3.scaleLinear()
.range([height , margin])
.domain([ 0, 1.025 * LoanOriginalAmount_Extent[1]]);
var SUM_LOAN_Extent = [70E3 , 1.2E7]; // d3.extent(red_data.value, d => d.value.sumLoan);
var radius = d3.scaleSqrt()
.domain(SUM_LOAN_Extent)
.range([3,0.375 * margin/2]);
// Creating axis
var x_axis = d3.axisBottom()
.scale(XScale)
.tickFormat(d3.format(".2%"));
var y_axis = d3.axisLeft()
.scale(YScale)
.tickFormat(d3.format(".0f"));
svg.append('g')
.attr('transform', "translate(0," + height + ")")
.call(x_axis);
svg.append('g')
.attr('transform', "translate(" + margin + ",0)")
.call(y_axis);
// Text label for the x axis
svg.append("text")
.attr("transform",
"translate(" + (width/2 + margin) + " ," +
(height + margin/2) + ")")
.style("text-anchor", "middle")
.style("font-size", 20)
.text("Mean lender yield (percentage)");
// Text label for the y axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.style("font-size", 20)
.text("Mean loan original amount [US dollars]");
// Creating gridlines
function make_x_gridlines() {
return d3.axisBottom(XScale)
.ticks(5);
};
function make_y_gridlines() {
return d3.axisLeft(YScale)
.ticks(5);
};
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + height + ")")
.call(make_x_gridlines()
.tickSize(-(height - 1 * margin ))
.tickFormat(""));
svg.append("g")
.attr("class", "grid")
.attr('transform', "translate(" + margin + ",0)")
.call(make_y_gridlines()
.tickSize(-(width - 1 * margin ))
.tickFormat(""));
// Add legend
var DELTA = SUM_LOAN_Extent[1] - SUM_LOAN_Extent[0];
var valuesToShow = [SUM_LOAN_Extent[0] + 0.00 * DELTA, SUM_LOAN_Extent[0] + 0.25 * DELTA ,
SUM_LOAN_Extent[0] + 0.50 * DELTA, SUM_LOAN_Extent[0] + 0.75 * DELTA ,
SUM_LOAN_Extent[0] + 1.00 * DELTA ];
var xCircle = width + 0.35 * margin;
var xLabel = 200;
var yCircle = 150;
var legend = svg.append("g")
.attr("class","legend")
.attr("trasform","translate(" + (width - 100) + "," + 20 + ")" )
.selectAll("g")
.data(valuesToShow)
.enter()
.append("g");
legend.append("circle")
.attr("cy", function(d,i) {
return (i+1)* 0.4 * yCircle + radius(d)/2; })
.attr("cx", xCircle)
.attr("r", function(d) {
return radius(d); })
.attr("stroke","black")
.attr("stroke-width",1.)
.attr("opacity", op);
// Add legend: labels
svg.selectAll("legend")
.data(valuesToShow)
.enter()
.append("text")
.attr('x', function(d,i){ return width + 0.55 * margin; } )
.attr('y', function(d,i){ return (i+1)* 0.4 * yCircle + radius(d)/2 } )
.text( function(d){ return formatM(d) } )
.style("font-size", 10)
.attr('alignment-baseline', 'middle');
svg.append("text")
.attr('x', 0.98 * xCircle )
.attr('y', 0.15 * yCircle )
.text( "Size = " )
.style("font-size", 12.5)
.attr('alignment-baseline', 'middle');
svg.append("text")
.attr('x', 0.98 * xCircle )
.attr('y', 0.15 * yCircle + 10 )
.text( "Total loan" )
.style("font-size", 12.5)
.attr('alignment-baseline', 'middle');
svg.append("text")
.attr('x', 0.98 * xCircle )
.attr('y', 0.15 * yCircle + 20)
.text( "amount (US \$)" )
.style("font-size", 12.5)
.attr('alignment-baseline', 'middle');
// Add color legend
var colors = d3.scaleOrdinal()
.domain([0, 1, 2])
.range(["blue", "green" , "black"]);
var stat = d3.scaleOrdinal()
.domain([0, 1, 2])
.range(["CA", "TX" , "Others"]);
var aaa = [ SUM_LOAN_Extent[0] + 1.00 * DELTA , SUM_LOAN_Extent[0] + 1.00 * DELTA, SUM_LOAN_Extent[0] + 1.00 * DELTA];
var legend1 = svg.append("g")
.attr("class","legend")
.attr("trasform","translate(" + (width - 100) + "," + 20 + ")" )
.selectAll("g")
.data(aaa)
.enter()
.append("g");
legend1.append("circle")
.attr("cy", function(d,i) { //debugger
return (6+i)* 0.4 * yCircle + radius(d)/2; })
.attr("cx", xCircle)
.attr("r", function(d) {
return radius(d); })
.attr("fill", function(d,i){ return colors(i)})
.attr("opacity", op);
// Add legend: labels
svg.selectAll(".legend1")
.data(aaa)
.enter()
.append("text")
.attr('x', function(d,i){ return width + 0.55 * margin; } )
.attr('y', function(d,i){ return (6+i)* 0.4 * yCircle + radius(d)/2 } )
.text( function(d,i){ return stat(i) } )
.style("font-size", 10)
.attr('alignment-baseline', 'middle');
// Appending first circles
// Accessing 1st group
var YearGroups = svg.selectAll(".YearGroups")
.data(nested_year, key_func)
.enter()
.append("g")
.attr("class", "YearGroups");
// Accessing 2nd group
var circles = YearGroups.selectAll("circle")
.data(function(d){
return d.values
})
.enter()
.append("circle")
.transition()
.duration(500)
.attr("cx", function(d) { return XScale(d.value.meanLenderyield) } )
.attr("cy", function(d) { return YScale(d.value.meanLoan ) } )
.attr("r" , function(d) { return radius(d.value.sumLoan ) } )
.attr("fill" , function(d) { if
(d.key == "CA") {return "blue"}
if (d.key == "TX") {return "green"}
else
{ return "black" }})
.attr("opacity" , op);
// debugger
// VARIABLE PART
function update(year) {
var filt = nested_year.filter(function(d) {return d.key == year;} );
var YearGroups1 = svg.selectAll(".YearGroups")
.data(filt, key_func);
YearGroups1.exit().remove();
var circles = YearGroups1.enter().append('g')
.attr("class", "YearGroups").selectAll('circle')
.data(function(d){ return d.values });
var CircPl = circles.enter()
.append("circle")
.transition()
.duration(500)
.attr("cx", function(d) { // debugger
return XScale(d.value.meanLenderyield); })
.attr("cy", function(d) { return YScale(d.value.meanLoan ); })
.attr("r" , function(d) { return radius(d.value.sumLoan ); })
.attr("fill" , function(d) { if
(d.key == "DC") {return "blue"}
if (d.key == "AR") {return "green"}
else
{ return "black" }})
.attr("opacity" , op)
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html("Lender yield : " + formatper(d.value.meanLenderyield) + "<br/>" +
"Loan original amount : " + formatDec(d.value.meanLoan) + " $ <br/>" +
"State : " + d.key)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
CircPl
// debugger
// Chart Title
svg.selectAll(".Title").remove()
svg
.append("text")
.attr("class","Title")
.attr("x", (margin + width) / 2)
.attr("y", margin / 2)
.attr("font-weight", "bold")
.attr("text-anchor", "middle")
.style("font-size", "32px")
.text("US Loans per State in " + year); // Title updated
}
var year_idx = 0;
var year_interval = setInterval(function() {
update(years[year_idx]);
year_idx++;
if(year_idx >= years.length) {
clearInterval(year_interval);
}
}, 1000);
});
</script>
</body>
</html>
I got the solution for the issue (thanks Myles C. from Udacity Mentor team).
In fact, the data to be accessed where easily stored in a dummy variable that has been used for data binding afterwards and the .merge() statement is used to update both to all circles.
function update_video(year) {
var filt = nested_year.filter(function(d) {
return d.key == year;
});
var data = filt[0].values
// Selection and Bind Data
const update = YearGroups.selectAll("circle")
.data(data);
var upd = update.enter()
.select("circle")
.merge(update)
.transition()
.duration(1500)
.attr("class", "node_circle")
.attr("cx", function(d) {
return XScale(d.value.meanLenderyield);
})
.attr("cy", function(d) {
return YScale(d.value.meanLoan);
})
.attr("r", function(d) {
return radius(d.value.sumLoan);
})
.attr("fill", function(d) {
if (d.key == "CA") { return "blue";}
if (d.key == "TX") { return "green";}
else { return "black"}
});
// Command necessary to remove States that are not available in the
// database for that year
update.exit().remove();
// Chart Title
svg.selectAll(".Title").remove();
svg.append("text")
.attr("class", "Title")
.attr("x", (margin + width) / 2)
.attr("y", margin / 2)
.attr("font-weight", "bold")
.attr("text-anchor", "middle")
.style("font-size", "32px")
.text("US Loans per State in " + year); // Title update
};
With this function, each circle is updated, regardless of the state (second key of nested data); the .exit().remove() command has been used to remove all points not bound to data.
Afterward, a second function is used to let the user choose a year and update the circles without transitions, but adding tooltips to get data information hoovering the points with the mouse.
function update_with_tooltip(year) {
var filt = nested_year.filter(function(d) {
return d.key == year;
});
var data = filt[0].values;
// Selection and Bind Data
const update = YearGroups.selectAll("circle")
.data(data);
// Update and merge
var upd = update.enter()
.select("circle")
.merge(update)
.attr("class", "node_circle")
.attr("cx", function(d) {
return XScale(d.value.meanLenderyield);
})
.attr("cy", function(d) {
return YScale(d.value.meanLoan);
})
.attr("r", function(d) {
return radius(d.value.sumLoan);
})
.attr("fill", function(d) {
if (d.key == "CA") { return "blue";}
if (d.key == "TX") { return "green";}
else { return "black"}
})
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html("Lender yield : " + formatper(d.value.meanLenderyield) +
"<br/>" + "Loan original amount : " + formatDec(d.value.meanLoan)
+ " $ <br/>" + "State : " + d.key)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
// Command necessary to remove States that are not available in the
// database for that year
update.exit().remove();
// Chart Title
svg.selectAll(".Title").remove();
svg.append("text")
.attr("class", "Title")
.attr("x", (margin + width) / 2)
.attr("y", margin / 2)
.attr("font-weight", "bold")
.attr("text-anchor", "middle")
.style("font-size", "32px")
.text("US Loans per State in " + year); // Title update
}
I have a bar chart and somehow the whole svg container is only rects and everything that falls in the area where the rects are, and so y-axis ticks are left outside of the container. How can I fix that so that the text at the top and y-axis ticks which are images are not left outside.
my code,
var barsData = [{
name: "walnuts",
value: 332
}, {
name: "apples",
value: 206
}]
// mapBars start here
/* Set chart dimensions */
var widthBar = 960,
heightBar = 250,
marginBar = {top:10, right:10, bottom:20, left:60};
//subtract margins
widthBar = widthBar - marginBar.left - marginBar.right;
heightBar = heightBar - marginBar.top - marginBar.bottom;
//sort data from highest to lowest
barsData = barsData.sort(function(a, b){ return b.value - a.value; });
//Sets the y scale from 0 to the maximum data element
var y = d3.scale.ordinal()
.domain(barsData.map(function(d){ return d.name}))
.rangeRoundBands([0, heightBar], .1);
var x = d3.scale.linear()
.range([0, widthBar])
.domain([0, d3.max(barsData, function(d){return d.value})])
var yAxis = d3.svg.axis()
.scale(y)
.orient('left')
var barsSvg = d3.select("#chart")
.append("svg")
.attr("class", "barChart")
.attr("width", "100%")
.attr('preserveAspectRatio', 'xMidYMin')
.attr("viewBox", '0 0 ' + parseInt(widthBar + marginBar.left + marginBar.right) + ' ' + parseInt(heightBar + marginBar.top + marginBar.bottom));
var bar = barsSvg.selectAll(".bar")
.data(barsData)
.enter()
.append("rect")
.attr("id", function(d, i) {return "bar" + d.name;})
.attr('class', 'mapBars')
.attr("x", 0)
.attr("y", function(d){ return y(d.name)})
.attr("width", function(d) {return x(d.value)})
.attr("height", y.rangeBand())
.attr("fill", function(d, i){
if(d.name == 'walnuts') {return '#006600'} else {return '#2980b9'}
});
var labelsBar = barsSvg.selectAll('text')
.data(barsData)
.enter()
.append('text')
.text(function(d){return d.value})
.attr('x', marginBar.top)
.attr('y', function(d, i) {return 90*i + 80;})
.attr("font-size", "38px")
.attr("fill", "#fff")
.style("font-weight", "bold");;
var y_xis = barsSvg.call(yAxis);
var lineEnd = 270;
var line = barsSvg.append("line")
.attr('class', 'endLine')
.attr("x1", function(){ return x(lineEnd)})
.attr("x2", function(){ return x(lineEnd)})
.attr("y1", 0)
.attr("y2", heightBar)
.attr("stroke-width", 6)
.attr("stroke", "red")
.attr("stroke-dasharray", "8,8")
var myText = barsSvg.append("text")
.attr("x", function(){ return x(lineEnd - 107)})
.attr("class", "myLabel")//easy to style with CSS
.attr("y", 9)//magical number here
.text("Winner crosses this line")
.attr('fill', 'red')
.attr('font-size', '25px');
barsSvg.selectAll(".tick text").remove()
barsSvg.selectAll(".tick")
.each( function(d) {
var p = d3.select(this);
p.append("svg:image")
.attr("x", -120)
.attr("y", -40)
.attr("dy", widthBar)
.attr("width", 100)
.attr("height", 100)
.attr("xlink:href",
function(d){
if(d == 'walnuts') { return 'http://nutritionfacts.org/wp-content/uploads/2015/08/NF-Aug11-Walnuts-and-Artery-Function.jpg'}
else if (d == 'apples') { return 'http://nutritionfacts.org/wp-content/uploads/2015/08/NF-Aug11-Walnuts-and-Artery-Function.jpg'}
});
})
see plunker
You should edit the svg viewBox
.attr("viewBox", '0 0 ' + parseInt(widthBar + marginBar.left + marginBar.right) + ' ' + parseInt(heightBar + marginBar.top + marginBar.bottom));
to show elements below 0 in x or y
.attr("viewBox", '-120 -40 ' + parseInt(widthBar + marginBar.left + marginBar.right) + ' ' + parseInt(heightBar + marginBar.top + marginBar.bottom));
I chose -120 and -40 based on the relative position of your images, although I might have missed something: feel free to adjust if it's too large/too small.
hi I created a spiral chart in d3.js, and I want to add circle to different position of the spiral lines.according to there values.
circle closes to the center will have highest priority.
any idea how to do that.
here is the code which i wrote
var width = 400,
height = 430
num_axes = 8,
tick_axis = 1,
start = 0
end = 4;
var theta = function(r) {
return -2*Math.PI*r;
};
var arc = d3.svg.arc()
.startAngle(0)
.endAngle(2*Math.PI);
var radius = d3.scale.linear()
.domain([start, end])
.range([0, d3.min([width,height])/2-20]);
var angle = d3.scale.linear()
.domain([0,num_axes])
.range([0,360])
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width/2 + "," + (height/2+8) +")");
var pieces = d3.range(start, end+0.001, (end-start)/1000);
var spiral = d3.svg.line.radial()
.interpolate("cardinal")
.angle(theta)
.radius(radius);
//svg.append("text")
// .text("And there was much rejoicing!")
// .attr("class", "title")
// .attr("x", 0)
// .attr("y", -height/2+16)
// .attr("text-anchor", "middle")
//svg.selectAll("circle.tick")
// .data(d3.range(end,start,(start-end)/4))
// .enter().append("circle")
// .attr("class", "tick")
// .attr("cx", 0)
// .attr("cy", 0)
// .attr("r", function(d) { return radius(d); })
svg.selectAll(".axis")
.data(d3.range(num_axes))
.enter().append("g")
.attr("class", "axis")
.attr("transform", function(d) { return "rotate(" + -angle(d) + ")"; })
.call(radial_tick)
.append("text")
.attr("y", radius(end)+13)
.text(function(d) { return angle(d) + "°"; })
.attr("text-anchor", "middle")
.attr("transform", function(d) { return "rotate(" + -90 + ")" })
svg.selectAll(".spiral")
.data([pieces])
.enter().append("path")
.attr("class", "spiral")
.attr("d", spiral)
.attr("transform", function(d) { return "rotate(" + 90 + ")" });
function radial_tick(selection) {
selection.each(function(axis_num) {
d3.svg.axis()
.scale(radius)
.ticks(5)
.tickValues( axis_num == tick_axis ? null : [])
.orient("bottom")(d3.select(this))
d3.select(this)
.selectAll("text")
.attr("text-anchor", "bottom")
.attr("transform", "rotate(" + angle(axis_num) + ")")
});
}
please see the second solution for my implementation. Help me with connecting the circle with the center
Here is a model for the technique you seem to be looking for...
var width = 400,
height = 430,
num_axes = 8,
tick_axis = 1,
start = 0,
end = 4,
testValue = 2;
var theta = function (r) {
return -2 * Math.PI * r;
};
var arc = d3.svg.arc()
.startAngle(0)
.endAngle(2 * Math.PI);
var radius = d3.scale.linear()
.domain([start, end])
.range([0, (d3.min([width, height]) / 2 - 20)]);
var angle = d3.scale.linear()
.domain([0, num_axes])
.range([0, 360]);
var chart = d3.select("#chart")
.style("width", width + "px");
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 8) + ")");
var pieces = d3.range(start, end + 0.001, (end - start) / 500);
var spiral = d3.svg.line.radial()
.interpolate("linear")
.angle(theta)
.radius(radius);
svg.append("text")
.text("Title")
.attr("class", "title")
.attr("x", 0)
.attr("y", -height/2+16)
.attr("text-anchor", "middle")
svg.selectAll("circle.tick")
.data(d3.range(end,start,(start-end)/4))
.enter().append("circle")
.attr("class", "tick")
.style({fill: "black", opacity: 0.1})
.attr("cx", 0)
.attr("cy", 0)
.attr("r", function(d) { return radius(d); })
svg.selectAll(".axis")
.data(d3.range(num_axes))
.enter().append("g")
.attr("class", "axis")
.attr("transform", function (d) { return "rotate(" + -angle(d) + ")"; })
.call(radial_tick)
.append("text")
.attr("y", radius(end) + 13)
.text(function (d) { return angle(d) + "°"; })
.attr("text-anchor", "middle")
.attr("transform", function (d) { return "rotate(" + -90 + ")" })
svg.selectAll(".axis path")
.style({fill: "none", stroke: "black"})
.attr("stroke-dasharray", "5 5")
svg.selectAll(".spiral")
.data([pieces])
.enter().append("path")
.attr("class", "spiral")
.attr("fill", "none")
.attr("stroke", "black")
.attr("d", spiral)
.attr("transform", function (d) { return "rotate(" + 90 + ")" });
function radial_tick(selection) {
selection.each(function (axis_num) {
d3.svg.axis()
.scale(radius)
.ticks(5)
.tickValues(axis_num == tick_axis ? null : [])
.tickSize(1)
.orient("bottom")(d3.select(this))
d3.select(this)
.selectAll("text")
.attr("text-anchor", "bottom")
.attr("transform", "rotate(" + angle(axis_num) + ")")
});
}
function radialScale(x) {
var t = theta(x), r = radius(x);
d3.select(this)
.attr("cx", r * Math.cos(t))
.attr("cy", r * Math.sin(t))
}
slider = SliderControl("#circleSlider", "data", update, [start, end], ",.3f");
function update(x) {
if (typeof x != "undefined") testValue = x;
var circles = svg.selectAll(".dataPoints")
.data([testValue]);
circles.enter().append("circle");
circles.attr("class", "dataPoints")
.style({ fill: "black", opacity: 0.6 })
.attr("r", 10)
.each(radialScale)
circles.exit().remove();
return testValue
}
function SliderControl(selector, title, value, domain, format) {
var accessor = d3.functor(value), rangeMax = 1000,
_scale = d3.scale.linear().domain(domain).range([0, rangeMax]),
_$outputDiv = $("<div />", { class: "slider-value" }),
_update = function (value) {
_$outputDiv.css("left", 'calc( '
+ (_$slider.position().left + _$slider.outerWidth()) + 'px + 1em )')
_$outputDiv.text(d3.format(format)(value));
$(".input").width(_$outputDiv.position().left + _$outputDiv.outerWidth() - _innerLeft)
},
_$slider = $(selector).slider({
value: _scale(accessor()),
max: rangeMax,
slide: function (e, ui) {
_update(_scale.invert(ui.value));
accessor(_scale.invert(ui.value));
}
}),
_$wrapper = _$slider.wrap("<div class='input'></div>")
.before($("<div />").text(title + ":"))
.after(_$outputDiv).parent(),
_innerLeft = _$wrapper.children().first().position().left;
_update(_scale.invert($(selector).slider("value")))
return d3.select(selector)
};
.domain {
stroke-width: 1px;
}
<link href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart">
<div id="circleSlider"></div>
</div>
I'm trying to add a legend to a d3 scatterplot matrix (using this example as a template: http://bl.ocks.org/mbostock/4063663), and while the scatterplot itself is displaying as expected, I have been unable to successfully add a legend. The code for the plot and one of the attempts at adding a legend are below:
var width = 960,
size = 150,
padding = 19.5;
var x = d3.scale.linear()
.range([padding / 2, size - padding / 2]);
var y = d3.scale.linear()
.range([size - padding / 2, padding / 2]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5);
var color = d3.scale.category10();
d3.csv(datafilename, function(error, dataset) {
var domainByTrait = {},
traits = d3.keys(dataset[0]).filter(function(d) { return d !== "class"; }),
n = traits.length;
traits.forEach(function(trait) {
domainByTrait[trait] = d3.extent(dataset, function(d) { return d[trait]; });
});
xAxis.tickSize(size * n);
yAxis.tickSize(-size * n);
var brush = d3.svg.brush()
.x(x)
.y(y)
.on("brushstart", brushstart)
.on("brush", brushmove)
.on("brushend", brushend);
var svg = d3.select("#visualizationDiv").append("svg")
.attr("width", size * n + padding)
.attr("height", size * n + padding)
.append("g")
.attr("transform", "translate(" + padding + "," + padding / 2 + ")");
svg.selectAll(".x.axis")
.data(traits)
.enter().append("g")
.attr("class", "x axis")
.attr("transform", function(d, i) { return "translate(" + (n - i - 1) * size + ",0)"; })
.each(function(d) { x.domain(domainByTrait[d]); d3.select(this).call(xAxis); });
svg.selectAll(".y.axis")
.data(traits)
.enter().append("g")
.attr("class", "y axis")
.attr("transform", function(d, i) { return "translate(0," + i * size + ")"; })
.each(function(d) { y.domain(domainByTrait[d]); d3.select(this).call(yAxis); });
var cell = svg.selectAll(".cell")
.data(cross(traits, traits))
.enter().append("g")
.attr("class", "cell")
.attr("transform", function(d) { return "translate(" + (n - d.i - 1) * size + "," + d.j * size + ")"; })
.each(plot);
// Titles for the diagonal.
cell.filter(function(d) { return d.i === d.j; }).append("text")
.attr("x", padding)
.attr("y", padding)
.attr("dy", ".71em")
.text(function(d) { return d.x; });
cell.call(brush);
function plot(p) {
var cell = d3.select(this);
x.domain(domainByTrait[p.x]);
y.domain(domainByTrait[p.y]);
cell.append("rect")
.attr("class", "frame")
.attr("x", padding / 2)
.attr("y", padding / 2)
.attr("width", size - padding)
.attr("height", size - padding);
cell.selectAll("circle")
.data(dataset)
.enter().append("circle")
.attr("cx", function(d) { return x(d[p.x]); })
.attr("cy", function(d) { return y(d[p.y]); })
.attr("r", 3)
.style("fill", function(d) { return color(d.class); });
}
var brushCell;
// Clear the previously-active brush, if any.
function brushstart(p) {
if (brushCell !== this) {
d3.select(brushCell).call(brush.clear());
x.domain(domainByTrait[p.x]);
y.domain(domainByTrait[p.y]);
brushCell = this;
}
}
// Highlight the selected circles.
function brushmove(p) {
var e = brush.extent();
svg.selectAll("circle").classed("hidden", function(d) {
return e[0][0] > d[p.x] || d[p.x] > e[1][0]
|| e[0][1] > d[p.y] || d[p.y] > e[1][1];
});
}
// If the brush is empty, select all circles.
function brushend() {
if (brush.empty()) svg.selectAll(".hidden").classed("hidden", false);
}
function cross(a, b) {
var c = [], n = a.length, m = b.length, i, j;
for (i = -1; ++i < n;) for (j = -1; ++j < m;) c.push({x: a[i], i: i, y: b[j], j: j});
return c;
}
d3.select(self.frameElement).style("height", size * n + padding + 20 + "px");
// add legend
var legend = svg.append("g")
.attr("class", "legend")
.attr("height", 100)
.attr("width", 100)
.attr('transform', 'translate(-20,50)');
legend.selectAll('rect')
.data(dataset)
.enter()
.append("rect")
.attr("x", width - 65)
.attr("y", function(d, i){ return i * 20;})
.attr("width", 10)
.attr("height", 10)
.style("fill", function(d) { return color(d.class); });
legend.selectAll('text')
.data(dataset)
.enter()
.append("text")
.attr("x", width - 52)
.attr("y", function(d, i){ return i * 20 + 9;})
.text(function(d) { return d.class; });
});
Among my other unsuccessful attempts at adding a legend are
var legend = svg.selectAll("g")
.data(dataset)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 28)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d) { return color(d.class); });
legend.append("text")
.attr("x", width - 34)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d.class; });
and
var legend = svg.selectAll('g').data(dataset)
.enter()
.append('g')
.attr("class", "legend");
legend.append("rect")
.attr("x", width - 45)
.attr("y", 25)
.attr("height", 50)
.attr("width", 50)
.each(function(d, i) {
var g = d3.select(this);
g.append("rect")
.attr("x", width - 65)
.attr("y", i*25)
.attr("width", 10)
.attr("height", 10)
.style("fill", function(d) { return color(d.class); });
g.append("text")
.attr("x", width - 50)
.attr("y", i * 25 + 8)
.attr("height",30)
.attr("width",100)
.style("fill", function(d) { return color(d.class); })
.text(function(d) { return d.class; });
all based on examples I've found on the web. None of these approaches seem to be working - I must be missing something here. Any insights or suggestions would be greatly appreciated.
The problem is right at the beginning:
var legend = svg.selectAll('g').data(dataset)
.enter()
.append('g')
.attr("class", "legend");
The selectAll('g') is going to select one of the groups already in your diagram, and then nothing will happen because enter() indicates that everything from there on (including the value that gets saved to the legend variable) only applies to groups that don't exist yet.
I'm pretty sure this legend code is supposed to be run from within its own <g> element. That way, it won't interfere with the rest of your graph.
var legendGroup = svg.append('g')
.attr('class', 'legend')
.attr('transform', /* translate as appropriate */);
var legendEntry = legendGroup.selectAll('g')
.data(dataset);
//create one legend entry for each series in the dataset array
//if that's not what you want, create an array that has one
//value for every entry you want in the legend
legendEntry.enter().append("g")
.attr("class", "legend-entry")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
//shift each entry down by approx 1 line (20px)
legendEntry.append("rect") //add a square to each entry
/* and so on */