Как разместить круги (точки) в линейной диаграмме с помощью библиотеки "d3.js"? - PullRequest
1 голос
/ 26 марта 2019

Я хочу сделать линейный график, используя 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'
          ]
 }

Ссылка на выходное изображение: https://ibb.co/pwRdXjx

, в котором первый круг должен быть помещен 25 января, но он расположен в неправильной позиции.

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);
  });
}
}

Ответы [ 2 ]

1 голос
/ 28 марта 2019

Масштаб полосы предполагает, что каждый элемент данных представлен чем-то, имеющим ширину, например столбец в гистограмме. Ширина масштабированного пространства (полосы) для этого элемента составляет от xBandScale(value) до xBandScale(value) + xBandScale.bandwidth(). Это удобно тем, что позволяет установить значение x прямоугольника на xBandScale(value) и ширину на xBandScale.bandwidth().

Но если вы располагаете круги, вы хотите, чтобы они были по центру в середине этого пространства. Вы позиционируете свои очки с помощью xScale(value), который является левой стороной полосы:

enter image description here

Тики оси смещены, потому что они предполагают ширину полосы, после чего мы передаем масштаб полосы на генератор оси.

Да, вы можете расположить свои круги с помощью:

.attr("cx", function(d) { return xScale(d.someProperty) + xScale.bandwidth()/2 })

Это поместит круг в середину полосы, которая выровняет ваши круги по отметкам вашей оси. Но ваша линия не будет начинаться с вертикальной оси, и на самом деле это не целевое назначение масштаба полосы, даже если оно работает.

Вместо этого мы могли бы попробовать d3.scalePoint - который также является порядковым масштабом и похож на масштаб диапазона. В документе указано: «Точечные шкалы обычно используются для диаграмм рассеяния с порядковым или категориальным измерением». Они идеальны, когда у вас есть объекты, которые не имеют ширины (или, точнее, расположены в середине полосы).

С этим мы получаем график, который выглядит так (используя те же данные и структуру, что и выше):

enter image description here

Обратите внимание, что интервал между точками равен xScale.step(), отметки перемещены (также, последний шаг превышает линию оси - это только для иллюстрации, ось заканчивается в d).

Наконец, если у нас есть ось, которая представляет что-то вроде времени или какую-то другую непрерывную величину, мы можем использовать любую непрерывную шкалу в d3, а не порядковую шкалу, которая часто может быть предпочтительнее.

Для простоты я предполагаю, что нет отступов, чтобы облегчить чтение изображений

0 голосов
/ 26 марта 2019

У вас есть запас слева в 50 в SVG, но ваш диапазон 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]);
...