假设我有一个不同运动员在不同日子进行相同测试的数据集。每天,他们将进行几次试验/运行。我想使用 d3.js 可视化这些天来每位运动员的发展,但我很难理解如何完成这项任务。
在 Python 中使用 seaborn 或在 R 中使用 ggplot2,我会使用 facetplot,其中每一天都是一个方面。在这些方面,我将在 x 轴上进行试验,在 I 轴上进行性能测试。但是如何在 d3.js 中做到这一点?
d3.group and group请允许我按运动员对数据集进行分组,并且我了解如何迭代每个运动员的值。但我不明白如何从这里开始在 d3.js 中实际创建 facetplot。
我曾尝试搜索有关 observable 的相关教程,但运气不佳。一种有趣且相关的可视化是[Trumps Golfs bt Bostock]。另一个is this scatterplot .
有人可以帮助我朝着正确的方向前进吗?我已经创建了一个简单的数据集和散点图,可以作为起始点
const data = d3.range(10).map(i => ({
bib: Math.floor(i / 5) + 1,
ratio: -1 + Math.random() * 5,
run: [1, 2, 3, 4, 5][i % 5],
run: [1, 2, 3, 4, 5][i % 5],
name: ['GIRL1', 'GIRL2', 'GIRL3', 'GIRL4'][Math.floor(i / 5)]
}));
const width = 250;
const height = 150;
const svg = d3.select('svg')
.attr('width', width)
.attr('height', height)
const margin = {
top: 20,
right: 20,
bottom: 20,
left: 50
}
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const xScale = d3.scaleLinear()
.domain(d3.extent(data, d => d.run))
.range([0, innerWidth])
const yScale = d3.scaleLinear()
.domain(d3.extent(data, d => d.ratio))
.range([0, innerHeight])
const g = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`)
g.selectAll('circle')
.data(data)
.join('circle')
.attr('r', 3)
.attr('cx', d => xScale(d.run))
.attr('cy', d => yScale(d.ratio))
g.append("g")
.call(d3.axisBottom(xScale))
.attr('transform', `translate(0,${innerHeight})`);;
g.append("g")
.call(d3.axisLeft(yScale))
<script src="https://unpkg.com/d3@6.2.0/dist/d3.min.js"></script>
<svg></svg>
从 R 中绘制示例:
最佳答案
设置 SVG
首先,让我们制定一个关于如何分配和构建页面的约定。只有一行图表,这更容易一些。
我们需要在上图中定义变量。然后我们可以推导出每个图的宽度和高度:
const height = 500;
const width = 500;
const margin = {
top: 50,
left: 50,
right: 50,
bottom: 50
}
const padding = 10; // labelled `pad` in image due to space constraints
const plotWidth = (width-padding)/numberOfPlots - padding;
const plotHeight = height-padding*2;
当然你可以随心所欲地安排,我包含这个部分只是为了让其余的答案更容易理解。
下面我使用 g
来保持由边距界定的区域,以便以后定位更容易。
现在我们也有两种不同的比例,你已经在你的代码中做了这个,但是我们现在可以使用上面的 plotWidth
和 plotHeight
定义范围间距约定。
绘制数据
现在我们准备好创建绘图了。为此,我们将创建一个嵌套数据集:我们希望按图对数据进行分组,正如您所指出的,我们可以使用 d3.group:
const grouped = d3.group(data,d=>d.bib);
对于每个分组,我们将创建一个 g
并使用基于上述结构的翻译来定位:
const plots = g.selectAll(".plot")
.data(grouped)
.enter()
.append("g")
.attr("transform", function(d,i) {
return "translate("+[i*(padding+plotWidth)+padding,padding]+")";
})
我们已经准备好使用嵌套数据进行绘图了:
plots.selectAll(null)
.data(d=>d[1])
.enter()
.append("circle")
... // and so forth.
下面的代码片段使用 d=>d[1]
,因为这是属于该组的项目数组所在的位置。 d[0]
是组的标识符。如果我的数据数组是一个数组数组,我会简单地使用 d=>d;
。
现在我们可以在每个图上附加 x 轴:
plots.append("g")
.attr("transform","translate("+[0,plotHeight]+")")
.call(d3.axisBottom(x));
还有一个y轴:
g.append("g")
.attr("transform","translate("+[0,padding]+")");
.call(d3.axisLeft(y));
我没有添加父 x 轴,根据您的使用情况,您可能想要标记它,或者不添加它。我还注意到我按 bib 而非 run 对数据进行分组,但原则保持不变:对数据进行分组,输入并定位父图,然后将数据点添加到父图。
示例
// Data and manipluation:
const data = d3.range(15).map(i => ({
bib: Math.floor(i / 5) + 1,
ratio: -1 + Math.random() * 5,
run: [1, 2, 3, 4, 5][i % 5],
run: [1, 2, 3, 4, 5][i % 5],
name: ['GIRL1', 'GIRL2', 'GIRL3', 'GIRL4'][Math.floor(i / 5)]
}));
const grouped = d3.group(data,d=>d.bib);
// Dimensions:
const height = 500;
const width = 500;
const margin = {
top: 10,
left: 50,
right: 50,
bottom: 50
}
const padding = 30; // labelled `pad` in image due to space constraints
const plotWidth = (width-padding)/grouped.size - padding;
const plotHeight = height-padding*2;
const svg = d3.select("body")
.append("svg")
.attr("width", margin.left+width+margin.right)
.attr("height", margin.top+height+margin.bottom);
const g = svg.append("g")
.attr("transform","translate("+[margin.left,margin.top]+")");
//Scales:
const xScale = d3.scaleLinear()
.domain(d3.extent(data, d => d.run))
.range([0, plotWidth]);
const yScale = d3.scaleLinear()
.domain(d3.extent(data, d => d.ratio))
.range([plotHeight, 0]);
// Place plots:
const plots = g.selectAll(null)
.data(grouped)
.enter()
.append("g")
.attr("transform", function(d,i) {
return "translate("+[i*(padding+plotWidth)+padding,padding]+")";
})
//Optional plot background:
plots.append("rect")
.attr("width",plotWidth)
.attr("height",plotHeight)
.attr("fill","#ddd");
// Plot actual data
plots.selectAll(null)
.data(d=>d[1])
.enter()
.append("circle")
.attr("r", 4)
.attr("cy", d=>yScale(d.ratio))
.attr("cx", d=>xScale(d.run))
// Plot line if needed:
plots.append("path")
.attr("d", function(d) {
return d3.line()
.x(d=>xScale(d.run))
.y(d=>yScale(d.ratio))
(d[1])
})
.attr("stroke", "#333")
.attr("stroke-width", 1)
.attr("fill","none")
// Plot names if needed:
plots.append("text")
.attr("x", plotWidth/2)
.attr("y", -10)
.text(function(d) {
return d[1][0].name;
})
.attr("text-anchor","middle");
// Plot axes
plots.append("g")
.attr("transform","translate("+[0,plotHeight]+")")
.call(d3.axisBottom(xScale).ticks(4));
g.append("g")
.attr("transform","translate("+[0,padding]+")")
.call(d3.axisLeft(yScale))
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.1.0/d3.min.js"></script>
应该产生什么:
关于javascript - 如何在 d3.js 中创建 'facetplot'?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65499073/