我正在使用d3创建一个多折线图.我 for each 图例条目添加了一个复选框,对应于一行,其 idea 是,人们可以取消选中一个复选框以删除该行,重新选中一个复选框以将其添加回来.
现在的情况是,每次选中复选框时,我都会重新绘制图表.如果这是drawChart函数第一次运行,我会选中每个复选框. 如果选中了复选框,数据将被过滤,drawChart函数将再次运行,这一次将原始数据与新数据进行比较.
虽然它(排序)工作,传奇色彩正在改变,我觉得这是令人难以置信的笨重.有没有更好的方法?
let timeW = 960,
timeH = 450
let timeMargin = {
top: 20,
right: 300,
bottom: 80,
left: 60
},
timeWidth = timeW - timeMargin.left - timeMargin.right,
timeHeight = timeH - timeMargin.top - timeMargin.bottom;
var x2 = d3.scaleTime().range([0, timeWidth]),
y2 = d3.scaleLinear().range([timeHeight, 0]);
var xAxis = d3.axisBottom(x2),
yAxis = d3.axisLeft(y2);
var line = d3.line()
.x(function(d) {
return x2(d.date);
})
.y(function(d) {
return y2(d.value);
});
const parseDate = d3.timeParse("%Y%m%d");
d3.csv("https://raw.githubusercontent.com/sprucegoose1/sample-data/main/age.csv").then(function(data) {
var long_data = [];
data.forEach(function(row) {
row.date = parseDate(row.Date)
let tableKeys = data.columns.slice(1);
Object.keys(row).forEach(function(colname) {
if (colname !== "date" && colname !== "Date") {
long_data.push({
"date": row["date"],
"value": +row[colname],
"bucket": colname
});
}
});
});
data.sort((a, b) => a.date - b.date)
let dataNest = d3.group(long_data, d => d.bucket)
let tableKeys = data.columns.slice(1);
drawChart(long_data, dataNest, tableKeys, "init")
})
function drawChart(data, dataNest, tableKeys, which) {
d3.select("#timeseries").remove()
let timeseries = d3.select("#chart").append('svg')
.attr('id', 'timeseries')
.attr("width", timeWidth + timeMargin.left + timeMargin.right)
.attr("height", timeHeight + timeMargin.top + timeMargin.bottom)
var graph = timeseries.append('g').attr('transform', 'translate(' + timeMargin.left + ',' + timeMargin.top + ')');
var focus = timeseries.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + timeMargin.left + "," + timeMargin.top + ")");
x2.domain(d3.extent(data, function(d) {
return d.date;
}));
y2.domain([0, d3.max(data, function(d) {
return d.value;
})]);
const seriesColors = ['#ff3300', 'royalblue', 'green', 'turquoise', 'navy']
var color = d3.scaleOrdinal()
.range(seriesColors);
focus
.selectAll("path")
.data(dataNest)
.enter().append("path")
.attr('class', 'groups')
.attr("d", d => {
d.line = this;
return line(d[1]);
})
.style("stroke", d => color(d[0]))
.style("stroke-width", 1)
.style('fill', 'none')
.attr("clip-path", "url(#clip)")
focus.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + timeHeight + ")")
.call(xAxis);
focus.append("g")
.attr("class", "axis axis--y")
.call(yAxis);
// add a legend
if (which == "init") {
var legend = timeseries.append('g')
.attr('class', 'legend')
.attr("transform", "translate(" + (timeW - timeMargin.right + 10) + "," + timeMargin.top + ")")
var legendGroups = legend
.selectAll(".legendGroup")
.data(tableKeys, d => d)
var enterGroups = legendGroups
.enter()
.append("g")
.attr("class", d => "legendGroup " + d.replaceAll(" ", "_"))
legendGroups
.exit()
.remove();
enterGroups.append("text")
.attr('class', 'legend-text')
.text(d => d)
.attr("x", 45)
.attr("y", (d, i) => 10 + i * 20);
enterGroups
.append("rect")
.attr("width", 10)
.attr("height", 10)
.attr("fill", d => color(d))
.attr("x", 30)
.attr("y", (d, i) => i * 20)
.attr("class", d => d + ' legend-rect')
enterGroups
.append("foreignObject")
.attr("x", 15)
.attr("y", (d, i) => i * 20)
.attr("width", 12)
.attr("height", 13)
.attr("id", (d, i) => 'a' + i)
.append("xhtml:div")
.html("<input type='checkbox' checked class='check'>")
.attr('class', 'checkcontainer')
}
// legend items for all data, but update checkboxes checked
else if (which == "checkBoxes") {
let oldKeys = ['18-25', '26-40', '41-55', '56+', '<18']
var legend = timeseries.append('g')
.attr('class', 'legend')
.attr("transform", "translate(" + (timeW - timeMargin.right + 10) + "," + timeMargin.top + ")")
var legendGroups = legend
.selectAll(".legendGroup")
.data(oldKeys, d => d)
var enterGroups = legendGroups
.enter()
.append("g")
.attr("class", d => "legendGroup " + d.replaceAll(" ", "_"))
legendGroups
.exit()
.remove();
enterGroups.append("text")
.attr('class', 'legend-text')
.text(d => d)
.attr("x", 45)
.attr("y", (d, i) => 10 + i * 20);
enterGroups
.append("rect")
.attr("width", 10)
.attr("height", 10)
.attr("fill", d => color(d))
.attr("x", 30)
.attr("y", (d, i) => i * 20)
.attr("class", d => d + ' legend-rect')
enterGroups
.append("foreignObject")
.attr("x", 15)
.attr("y", (d, i) => i * 20)
.attr("width", 12)
.attr("height", 13)
.attr("id", (d, i) => 'a' + i)
.append("xhtml:div")
.html(function(d) {
if (tableKeys.indexOf(d) >= 0) {
return "<input type='checkbox' checked class='check'>"
} else {
return "<input type='checkbox' class='check'>"
}
})
.attr('class', 'checkcontainer')
}
d3.selectAll('.check').on('click', function(d) {
let isChecked = this.checked
let parentEnterGroup = d3.select(this.parentNode.parentNode.parentNode)
let parentGroupClass = parentEnterGroup._groups[0]
let parentGroupString = parentGroupClass[0].className.baseVal.split(" ")[1]
if (isChecked !== true) {
let newData = data.filter(d => d.bucket !== parentGroupString.replaceAll("_", " "))
let newDataNest = d3.group(newData, d => d.bucket)
let newTableKeysa = Array.from(newDataNest.keys())
drawChart(newData, newDataNest, newTableKeysa, "checkBoxes")
} else {
console.log('add it back')
}
})
};
#chart {
height: 450px;
width: 760px;
}
.check {
width: 11px;
height: 12px;
filter: grayscale(1);
margin: 0;
margin-top: -1px !important;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script>
<div id="chart"></div>