У меня есть двойной вложенный набор данных в d3. Я хочу создать точечную диаграмму, которая будет обновляться для каждого значения первого ключа (переменная «время»), но данные каждой точки привязаны к значениям второго ключа (переменная «пространство»). Таким образом, для ясности, каждая точка должна быть переведена в новые координаты, и ее радиус также должен быть обновлен.
Вот пример данных (в файле "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
А вот и мой код. Я думал, что удаления YearGroups1.exit (). Remove () было достаточно, но это не так. Я не могу правильно выбрать круги и привязать их к новому набору данных (на втором вложенном уровне). Вы можете мне помочь?
<!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>