如何使用从 csv 文件过滤的数据创建多系列折线图?
How to create a multiseries line chart using data filtered from a csv file?
我有一个 data.csv
文件是这样完成的:
CODE,YEAR,MODALITY,VALUE
AB,2000,first,15
AB,2000,second,
AB,2000,third,33
AB,2001,first,20
AB,2001,second,25
AB,2001,third,87
AB,2002,first,6
AB,2002,second,
AB,2002,third,16
AB,2003,first,50
AB,2003,second,50
AB,2003,third,10
AB,2004,first,20
AB,2004,second,55
AB,2004,third,8
AC,2000,first,
AC,2000,second,97
AC,2000,third,77
AC,2001,first,42
AC,2001,second,5
AC,2001,third,81
AC,2002,first,
AC,2002,second,63
AC,2002,third,14
AC,2003,first,5
AC,2003,second,7
AC,2003,third,0
AC,2004,first,5
AC,2004,second,7
AC,2004,third,0
AD,2000,first,11
AD,2000,second,2
AD,2000,third,36
AD,2001,first,95
AD,2001,second,78
AD,2001,third,88
AD,2002,first,89
AD,2002,second,32
AD,2002,third,79
AD,2003,first,5
AD,2003,second,32
AD,2003,third,9
AD,2004,first,7
AD,2004,second,32
AD,2004,third,91
AE,2000,first,15
AE,2000,second,78
AE,2000,third,1
AE,2001,first,5
AE,2001,second,2
AE,2001,third,64
AE,2002,first,44
AE,2002,second,51
AE,2002,third,
AE,2003,first,40
AE,2003,second,52
AE,2003,third,85
AE,2004,first,45
AE,2004,second,50
AE,2004,third,80
我创建了一个 index.html
文件,其中包含一些用户 select 可用的元素(在示例中,为简单起见,我选择了 4 个代码为 AB 的圆圈, AC, AD 或 AE) 和 3 个单选按钮 (first、second 和 third).
用户可以 select 一个或多个圆圈以及 select 一个单选按钮。
所以我有一个数组 codes
,其中包含 selected 圆圈的代码和一个 modalitySelected
变量,其中包含所选的单选按钮。
我想做的是一个折线图,表示基于用户制作的 selection 的数据。
例子:
每个选择的代码都有一行,值是与 selected 单选按钮对应的值。
在这个例子中有一些缺失的数据,虚线就是这个。
(目前这并不重要代表)。
这是我的代码。最初管理圆的 selection 并创建 codes
数组,其中包含 selected 元素的代码。
然后创建折线图。
index.html:
<body>
<div id="circles">
<svg>
<circle id="AB" cx="10" cy="10" r="10" fill="purple" />
<circle id="AC" cx="60" cy="60" r="5" fill="red" />
<circle id="AD" cx="110" cy="110" r="15" fill="orange" />
<circle id="AE" cx="90" cy="50" r="7" fill="yellow" />
</svg>
</div>
<button type="button" id="finalSelection">Finish</button>
<span style="display:block;margin-top: 10px;">Selected codes: <span class="values"></span></span><br>
<div id="modality-selector-container">
<form id="modality-selector">
<input type="radio" name="modality-selector" id="rb-first" value="first" checked />
<label for="rb-first">First</label>
<input type="radio" name="modality-selector" id="rb-second" value="second" />
<label for="rb-second">Second</label>
<input type="radio" name="modality-selector" id="rb-third" value="third" />
<label for="rb-third">Third</label>
</form>
</div>
<div id="line-chart-container"></div>
<script src="./script.js"></script>
</body>
script.js:
var codes = [];
modalitySelected = document.querySelector('input[name=modality-selector]:checked').value;
var filtered_data = null;
// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 30, left: 50};
var width = 600 - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// parse the date/time
var parseTime = d3.timeParse("%Y");
var svg = null;
var valueline = null;
d3.selectAll('#circles svg circle').on('click', function() {
var id = d3.select(this).attr('id');
if(d3.select(this).classed('clicked')) {
d3.select(this).classed('clicked', false).style('stroke', null);
codes.splice(codes.indexOf(id), 1);
}
else {
if(codes.length) {
if(d3.event.ctrlKey) {
d3.select(this).classed('clicked', true).style('stroke', 'blue');
codes.push(id);
}
else {
d3.selectAll(".clicked").classed('clicked', false).style('stroke', null);
codes = [];
d3.select(this).classed('clicked', true).style('stroke', 'blue');
codes.push(id);
}
}
else {
d3.select(this).classed('clicked', true).style('stroke', 'blue');
codes.push(id);
}
}
$('span.values').html(codes.join(', '));
});
$('button#finalSelection').click(function() {
$('span.values').html(codes.join(', '));
console.log("compare: " + codes);
compareCodes();
});
function compareCodes() {
// define the line
valueline = d3.line()
.x(function(d) {
return x(d.YEAR);
})
.y(function(d) {
return y(d.VALUE);
});
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
svg = d3.select("#line-chart-container").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
getData();
}
function getData() {
d3.queue()
.defer(d3.csv, './data.csv')
.await(makeLineChart);
}
function makeLineChart(error, data) {
if(error) {
console.log(error);
}
// radio button change
d3.selectAll("input[name='modality-selector']")
.on("change", function(){
console.log(this.value);
modalitySelected = this.value;
// filter data
filtered_data = data.filter(function(d) {
return d.MODALITY == modalitySelected && d.CODE == codes[0];
});
// format the data
filtered_data.forEach(function(d) {
d.YEAR = parseTime(d.YEAR);
d.VALUE = +d.VALUE;
});
updateGraph(filtered_data);
}); // end radio on change
// generate initial line chart - filter data
filtered_data = data.filter(function(d) {
return d.MODALITY == modalitySelected && d.CODE == codes[0];
});
updateGraph(filtered_data);
}
function updateGraph(data) {
var numTickXaxis = data.length;
// scale the range of the data
x.domain(d3.extent(filtered_data, function(d) {
return d.YEAR;
}));
y.domain([0, d3.max(filtered_data, function(d) {
return d.VALUE;
})]);
// add the valueline path
svg.append("path")
.data([filtered_data])
.attr("class", "line")
.attr("d", valueline);
// add the X Axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x)
.tickFormat(d3.timeFormat("%Y"))
.ticks(numTickXaxis))
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)");
// add the Y Axis
svg.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y));
var state = svg.selectAll(".line");
state.exit().remove();
}
完整代码为HERE。
这段代码有两个问题:
- 更改单选按钮 selection 时,图形将被覆盖(尽管存在
state.exit().remove();
)
- 如您所见,该代码仅适用于
codes
中的第一个元素。我不知道如何处理 codes
由多个元素组成并显示更多行的情况。
我可以考虑修改data.csv
的结构,保持相同的内容。
例如,我可以这样编辑文件:
CODE,YEAR,FIRST,SECOND,THIRD
AB,2000,15,50,33
AB,2001,20,25,87
AB,2002,6,,16
AB,2003,50,50,10
AB,2004,20,55,8
AC,2000,,97,77
AC,2001,42,5,81
AC,2002,,63,14
AC,2003,5,7,0
AC,2004,5,7,0
AD,2000,11,2,36
AD,2001,95,78,88
AD,2002,89,32,79
AD,2003,5,32,9
AD,2004,7,32,91
AE,2000,15,78,1
AE,2001,5,2,64
AE,2002,44,51,
AE,2003,40,52,85
AE,2004,45,50,80
有人知道如何帮助我吗?谢谢!
解决方案
跟随this example,我能够解决问题。
HERE代码。
现在我只有一个小的(我希望)图形问题。
这张图我们可以很清楚的看出问题所在
线条在轴之前开始。为什么?
问题是您 createAxis()
中的 "transform", "translate("
将轴移到了一边。
所以我添加了
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
到您原来的 svg
变量,使其与轴对齐并删除
.attr("transform", "translate(" + 0 + "," + 0 + ")");
来自createAxis()
这是 link:Plunker
我有一个 data.csv
文件是这样完成的:
CODE,YEAR,MODALITY,VALUE
AB,2000,first,15
AB,2000,second,
AB,2000,third,33
AB,2001,first,20
AB,2001,second,25
AB,2001,third,87
AB,2002,first,6
AB,2002,second,
AB,2002,third,16
AB,2003,first,50
AB,2003,second,50
AB,2003,third,10
AB,2004,first,20
AB,2004,second,55
AB,2004,third,8
AC,2000,first,
AC,2000,second,97
AC,2000,third,77
AC,2001,first,42
AC,2001,second,5
AC,2001,third,81
AC,2002,first,
AC,2002,second,63
AC,2002,third,14
AC,2003,first,5
AC,2003,second,7
AC,2003,third,0
AC,2004,first,5
AC,2004,second,7
AC,2004,third,0
AD,2000,first,11
AD,2000,second,2
AD,2000,third,36
AD,2001,first,95
AD,2001,second,78
AD,2001,third,88
AD,2002,first,89
AD,2002,second,32
AD,2002,third,79
AD,2003,first,5
AD,2003,second,32
AD,2003,third,9
AD,2004,first,7
AD,2004,second,32
AD,2004,third,91
AE,2000,first,15
AE,2000,second,78
AE,2000,third,1
AE,2001,first,5
AE,2001,second,2
AE,2001,third,64
AE,2002,first,44
AE,2002,second,51
AE,2002,third,
AE,2003,first,40
AE,2003,second,52
AE,2003,third,85
AE,2004,first,45
AE,2004,second,50
AE,2004,third,80
我创建了一个 index.html
文件,其中包含一些用户 select 可用的元素(在示例中,为简单起见,我选择了 4 个代码为 AB 的圆圈, AC, AD 或 AE) 和 3 个单选按钮 (first、second 和 third).
用户可以 select 一个或多个圆圈以及 select 一个单选按钮。
所以我有一个数组 codes
,其中包含 selected 圆圈的代码和一个 modalitySelected
变量,其中包含所选的单选按钮。
我想做的是一个折线图,表示基于用户制作的 selection 的数据。
例子:
每个选择的代码都有一行,值是与 selected 单选按钮对应的值。
在这个例子中有一些缺失的数据,虚线就是这个。 (目前这并不重要代表)。
这是我的代码。最初管理圆的 selection 并创建 codes
数组,其中包含 selected 元素的代码。
然后创建折线图。
index.html:
<body>
<div id="circles">
<svg>
<circle id="AB" cx="10" cy="10" r="10" fill="purple" />
<circle id="AC" cx="60" cy="60" r="5" fill="red" />
<circle id="AD" cx="110" cy="110" r="15" fill="orange" />
<circle id="AE" cx="90" cy="50" r="7" fill="yellow" />
</svg>
</div>
<button type="button" id="finalSelection">Finish</button>
<span style="display:block;margin-top: 10px;">Selected codes: <span class="values"></span></span><br>
<div id="modality-selector-container">
<form id="modality-selector">
<input type="radio" name="modality-selector" id="rb-first" value="first" checked />
<label for="rb-first">First</label>
<input type="radio" name="modality-selector" id="rb-second" value="second" />
<label for="rb-second">Second</label>
<input type="radio" name="modality-selector" id="rb-third" value="third" />
<label for="rb-third">Third</label>
</form>
</div>
<div id="line-chart-container"></div>
<script src="./script.js"></script>
</body>
script.js:
var codes = [];
modalitySelected = document.querySelector('input[name=modality-selector]:checked').value;
var filtered_data = null;
// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 30, left: 50};
var width = 600 - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// parse the date/time
var parseTime = d3.timeParse("%Y");
var svg = null;
var valueline = null;
d3.selectAll('#circles svg circle').on('click', function() {
var id = d3.select(this).attr('id');
if(d3.select(this).classed('clicked')) {
d3.select(this).classed('clicked', false).style('stroke', null);
codes.splice(codes.indexOf(id), 1);
}
else {
if(codes.length) {
if(d3.event.ctrlKey) {
d3.select(this).classed('clicked', true).style('stroke', 'blue');
codes.push(id);
}
else {
d3.selectAll(".clicked").classed('clicked', false).style('stroke', null);
codes = [];
d3.select(this).classed('clicked', true).style('stroke', 'blue');
codes.push(id);
}
}
else {
d3.select(this).classed('clicked', true).style('stroke', 'blue');
codes.push(id);
}
}
$('span.values').html(codes.join(', '));
});
$('button#finalSelection').click(function() {
$('span.values').html(codes.join(', '));
console.log("compare: " + codes);
compareCodes();
});
function compareCodes() {
// define the line
valueline = d3.line()
.x(function(d) {
return x(d.YEAR);
})
.y(function(d) {
return y(d.VALUE);
});
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
svg = d3.select("#line-chart-container").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
getData();
}
function getData() {
d3.queue()
.defer(d3.csv, './data.csv')
.await(makeLineChart);
}
function makeLineChart(error, data) {
if(error) {
console.log(error);
}
// radio button change
d3.selectAll("input[name='modality-selector']")
.on("change", function(){
console.log(this.value);
modalitySelected = this.value;
// filter data
filtered_data = data.filter(function(d) {
return d.MODALITY == modalitySelected && d.CODE == codes[0];
});
// format the data
filtered_data.forEach(function(d) {
d.YEAR = parseTime(d.YEAR);
d.VALUE = +d.VALUE;
});
updateGraph(filtered_data);
}); // end radio on change
// generate initial line chart - filter data
filtered_data = data.filter(function(d) {
return d.MODALITY == modalitySelected && d.CODE == codes[0];
});
updateGraph(filtered_data);
}
function updateGraph(data) {
var numTickXaxis = data.length;
// scale the range of the data
x.domain(d3.extent(filtered_data, function(d) {
return d.YEAR;
}));
y.domain([0, d3.max(filtered_data, function(d) {
return d.VALUE;
})]);
// add the valueline path
svg.append("path")
.data([filtered_data])
.attr("class", "line")
.attr("d", valueline);
// add the X Axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x)
.tickFormat(d3.timeFormat("%Y"))
.ticks(numTickXaxis))
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)");
// add the Y Axis
svg.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y));
var state = svg.selectAll(".line");
state.exit().remove();
}
完整代码为HERE。
这段代码有两个问题:
- 更改单选按钮 selection 时,图形将被覆盖(尽管存在
state.exit().remove();
) - 如您所见,该代码仅适用于
codes
中的第一个元素。我不知道如何处理codes
由多个元素组成并显示更多行的情况。
我可以考虑修改data.csv
的结构,保持相同的内容。
例如,我可以这样编辑文件:
CODE,YEAR,FIRST,SECOND,THIRD
AB,2000,15,50,33
AB,2001,20,25,87
AB,2002,6,,16
AB,2003,50,50,10
AB,2004,20,55,8
AC,2000,,97,77
AC,2001,42,5,81
AC,2002,,63,14
AC,2003,5,7,0
AC,2004,5,7,0
AD,2000,11,2,36
AD,2001,95,78,88
AD,2002,89,32,79
AD,2003,5,32,9
AD,2004,7,32,91
AE,2000,15,78,1
AE,2001,5,2,64
AE,2002,44,51,
AE,2003,40,52,85
AE,2004,45,50,80
有人知道如何帮助我吗?谢谢!
解决方案
跟随this example,我能够解决问题。
HERE代码。
现在我只有一个小的(我希望)图形问题。 这张图我们可以很清楚的看出问题所在
线条在轴之前开始。为什么?
问题是您 createAxis()
中的 "transform", "translate("
将轴移到了一边。
所以我添加了
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
到您原来的 svg
变量,使其与轴对齐并删除
.attr("transform", "translate(" + 0 + "," + 0 + ")");
来自createAxis()
这是 link:Plunker