如何使用 "d3.js " 库在折线图中放置圆(点)?
How to place Circles(dots) in line chart using "d3.js " library?
我想使用 d3.js 制作折线图,其中我想在数据位置放置圆圈。但是圆圈在折线图中的位置错误。我该如何解决这个问题?
我在 Angular 工作。
基本数据集看起来像
{
type: 'Advanced',
wcpm: 40,
date: '11-25-2019',
comment: [
'Lorem Ipsum Lorem Ipsum Lorem 1',
'Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum 2'
]
}
输出图像Link:
https://ibb.co/pwRdXjx
其中第一个圆应该放在 Jan25 上,但它放在了错误的位置。
class AppComponent implements OnInit {
title = 'chart';
margin = { top: 50, right: 50, bottom: 50, left: 50 };
width = 1200 - this.margin.left - this.margin.right;
height = 700 - this.margin.top - this.margin.bottom;
svg: any;
xScale: any;
yScale: any;
line: any;
toolTipDiv: any;
ngOnInit() {
// XAXIS
this.xScale = d3.scaleBand()
.domain(this.dataset[0].fluencyData.map((data) => {
return new Date(data.date);
}))
.range([0, this.width]);
// YAXIS
this.yScale = d3.scaleLinear()
.domain([0, 110])
.range([this.height, 0]);
// Line Generator
this.line = d3.line()
.x((d) => this.xScale(new Date(d.date)))
.y((d) => this.yScale(d.wcpm));
// .curve(d3.curveMonotoneX);
// Add SVG to Div
this.svg = d3.select('#displayChart').append('svg')
.attr('width', this.width + this.margin.left + this.margin.right)
.attr('height', this.height + this.margin.top + this.margin.bottom)
.append('g')
.attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');
// Define the div for the tooltip
this.toolTipDiv = d3.select('#displayChart').append('div')
.attr('class', 'tooltip')
.style('opacity', 0);
// Append XAXIS to the SVG
this.svg.append('g')
.attr('class', 'xAxis')
.attr('transform', 'translate(0,' + this.height + ')')
.call(d3.axisBottom(this.xScale).tickSizeOuter(0).tickFormat(d3.timeFormat('%b %d')));
// Append YAXIS to SVG
this.svg.append('g')
.attr('class', 'yAxis')
.call(d3.axisLeft(this.yScale).tickSize(-this.width)
);
// Make a Path for Dataset
this.svg.append('path')
.datum(this.dataset[0].fluencyData)
.attr('class', 'line')
.attr('d', this.line);
// Text Heading of DATE in chart
this.svg.append('text')
.attr('transform', 'translate(' + (-20) + ',' + (this.height + 13) + ')')
.attr('dy', '.35em')
.attr('class', ' xAxis')
.text('Date');
// Append Circles in chart with different Colors
this.appendCircles();
}
appendCircles() {
this.svg.selectAll('.developing')
.data(this.dataset[0].fluencyData)
.enter().append('circle')
.attr('class', (d) => d.type)
.attr('cx', (d) => this.xScale(new Date(d.date)))
.attr('cy', (d) => this.yScale(d.wcpm))
.attr('r', 5)
.on('mouseover', (d) => {
this.toolTipDiv.transition()
.duration(200)
.style('opacity', .9);
this.toolTipDiv.html(d.wcpm + '<br/>' + d.type + '<br/>' + d.date)
.style('left', (d3.event.pageX - 50) + 'px')
.style('top', (d3.event.pageY - 50) + 'px');
})
.on('mouseout', () => {
this.toolTipDiv.transition()
.duration(500)
.style('opacity', 0);
});
}
}
您的 SVG 中的 margin-left 为 50,但您的 xScale 范围从 0 开始。这就是为什么一切都向左移动的原因。你的 xScale 范围应该从你的边距开始,到 svg 宽度结束。
this.xScale = d3.scaleBand()
.domain(this.dataset[0].fluencyData.map((data) => {
return new Date(data.date);
}))
.range([this.margin.left, this.width]);
带刻度期望每个数据项都用具有宽度的东西表示,例如条形图中的条形。该元素的缩放 space(带)的宽度是从 xBandScale(value)
到 xBandScale(value) + xBandScale.bandwidth()
。这很方便,因为它允许您将矩形的 x 值设置为 xBandScale(value)
,将宽度设置为 xBandScale.bandwidth()
。
但是如果你要定位圆圈,你希望它们位于这个 space 的中间。您正在使用 xScale(value)
定位您的点,这是带的左侧:
轴刻度是偏移的,因为它们预测了带的宽度,毕竟我们将带刻度传递给轴生成器。
是的,您可以将您的圈子定位为:
.attr("cx", function(d) { return xScale(d.someProperty) + xScale.bandwidth()/2 })
这会将圆圈放在带子的中间 - 使您的圆圈与您的轴刻度线对齐。但是,你的线不会从垂直轴开始,这并不是带刻度的真正目的,即使它有效。
相反,我们可以尝试 d3.scalePoint
- 这也是一个序数量表,类似于波段量表。文档状态:"Point scales are typically used for scatterplots with an ordinal or categorical dimension." 它们非常适合没有宽度的对象(或者更准确地说,位于带的中间)。
有了这个,我们得到了一个看起来像(使用与上面相同的数据和结构)的图表:
注意点之间的间距是xScale.step()
,轴刻度移动了(另外,最后一步超过了轴线-只是为了说明,轴结束于d)。
最后,如果我们有一个表示时间或其他连续量的轴,我们也可以使用 d3 中的任何连续刻度而不是序数刻度,后者通常可能更可取。
为简单起见,我假设没有填充以使图像更易于阅读
我想使用 d3.js 制作折线图,其中我想在数据位置放置圆圈。但是圆圈在折线图中的位置错误。我该如何解决这个问题?
我在 Angular 工作。 基本数据集看起来像
{
type: 'Advanced',
wcpm: 40,
date: '11-25-2019',
comment: [
'Lorem Ipsum Lorem Ipsum Lorem 1',
'Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum 2'
]
}
输出图像Link: https://ibb.co/pwRdXjx
其中第一个圆应该放在 Jan25 上,但它放在了错误的位置。
class AppComponent implements OnInit {
title = 'chart';
margin = { top: 50, right: 50, bottom: 50, left: 50 };
width = 1200 - this.margin.left - this.margin.right;
height = 700 - this.margin.top - this.margin.bottom;
svg: any;
xScale: any;
yScale: any;
line: any;
toolTipDiv: any;
ngOnInit() {
// XAXIS
this.xScale = d3.scaleBand()
.domain(this.dataset[0].fluencyData.map((data) => {
return new Date(data.date);
}))
.range([0, this.width]);
// YAXIS
this.yScale = d3.scaleLinear()
.domain([0, 110])
.range([this.height, 0]);
// Line Generator
this.line = d3.line()
.x((d) => this.xScale(new Date(d.date)))
.y((d) => this.yScale(d.wcpm));
// .curve(d3.curveMonotoneX);
// Add SVG to Div
this.svg = d3.select('#displayChart').append('svg')
.attr('width', this.width + this.margin.left + this.margin.right)
.attr('height', this.height + this.margin.top + this.margin.bottom)
.append('g')
.attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');
// Define the div for the tooltip
this.toolTipDiv = d3.select('#displayChart').append('div')
.attr('class', 'tooltip')
.style('opacity', 0);
// Append XAXIS to the SVG
this.svg.append('g')
.attr('class', 'xAxis')
.attr('transform', 'translate(0,' + this.height + ')')
.call(d3.axisBottom(this.xScale).tickSizeOuter(0).tickFormat(d3.timeFormat('%b %d')));
// Append YAXIS to SVG
this.svg.append('g')
.attr('class', 'yAxis')
.call(d3.axisLeft(this.yScale).tickSize(-this.width)
);
// Make a Path for Dataset
this.svg.append('path')
.datum(this.dataset[0].fluencyData)
.attr('class', 'line')
.attr('d', this.line);
// Text Heading of DATE in chart
this.svg.append('text')
.attr('transform', 'translate(' + (-20) + ',' + (this.height + 13) + ')')
.attr('dy', '.35em')
.attr('class', ' xAxis')
.text('Date');
// Append Circles in chart with different Colors
this.appendCircles();
}
appendCircles() {
this.svg.selectAll('.developing')
.data(this.dataset[0].fluencyData)
.enter().append('circle')
.attr('class', (d) => d.type)
.attr('cx', (d) => this.xScale(new Date(d.date)))
.attr('cy', (d) => this.yScale(d.wcpm))
.attr('r', 5)
.on('mouseover', (d) => {
this.toolTipDiv.transition()
.duration(200)
.style('opacity', .9);
this.toolTipDiv.html(d.wcpm + '<br/>' + d.type + '<br/>' + d.date)
.style('left', (d3.event.pageX - 50) + 'px')
.style('top', (d3.event.pageY - 50) + 'px');
})
.on('mouseout', () => {
this.toolTipDiv.transition()
.duration(500)
.style('opacity', 0);
});
}
}
您的 SVG 中的 margin-left 为 50,但您的 xScale 范围从 0 开始。这就是为什么一切都向左移动的原因。你的 xScale 范围应该从你的边距开始,到 svg 宽度结束。
this.xScale = d3.scaleBand()
.domain(this.dataset[0].fluencyData.map((data) => {
return new Date(data.date);
}))
.range([this.margin.left, this.width]);
带刻度期望每个数据项都用具有宽度的东西表示,例如条形图中的条形。该元素的缩放 space(带)的宽度是从 xBandScale(value)
到 xBandScale(value) + xBandScale.bandwidth()
。这很方便,因为它允许您将矩形的 x 值设置为 xBandScale(value)
,将宽度设置为 xBandScale.bandwidth()
。
但是如果你要定位圆圈,你希望它们位于这个 space 的中间。您正在使用 xScale(value)
定位您的点,这是带的左侧:
轴刻度是偏移的,因为它们预测了带的宽度,毕竟我们将带刻度传递给轴生成器。
是的,您可以将您的圈子定位为:
.attr("cx", function(d) { return xScale(d.someProperty) + xScale.bandwidth()/2 })
这会将圆圈放在带子的中间 - 使您的圆圈与您的轴刻度线对齐。但是,你的线不会从垂直轴开始,这并不是带刻度的真正目的,即使它有效。
相反,我们可以尝试 d3.scalePoint
- 这也是一个序数量表,类似于波段量表。文档状态:"Point scales are typically used for scatterplots with an ordinal or categorical dimension." 它们非常适合没有宽度的对象(或者更准确地说,位于带的中间)。
有了这个,我们得到了一个看起来像(使用与上面相同的数据和结构)的图表:
注意点之间的间距是xScale.step()
,轴刻度移动了(另外,最后一步超过了轴线-只是为了说明,轴结束于d)。
最后,如果我们有一个表示时间或其他连续量的轴,我们也可以使用 d3 中的任何连续刻度而不是序数刻度,后者通常可能更可取。
为简单起见,我假设没有填充以使图像更易于阅读