Как нарисовать точечный график с несколькими значениями y для каждого значения x?

#javascript #d3.js #data-visualization #visualization

#javascript #d3.js #визуализация данных #визуализация

Вопрос:

У меня есть данные в следующем формате, которые я хотел бы отобразить с помощью d3:

 data = [
        { x: 0.2, y: [ 1, 2, 4 ] },
        { x: 0.3, y: [ 2 ] },
        { x: 0.5, y: [ 4, 7, 8, 12, 19 ] }
        { x: 1.4, y: [ 1, 3 ] }
       ]
 

Обычно значения по оси y являются целыми числами, но здесь они представляют собой массивы, поэтому следующий код работает не так, как предполагалось:

 svg.selectAll("circle")
    .data(data)
    .enter()
    .append("circle")
        .attr("cx", function(d){ return x(d.x) })
        .attr("cy", function(d){ return y(d.y) })
        .attr("r", 2)
 

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

Другие подобные вопросы на этом веб-сайте касаются только данных с фиксированным числом значений по оси y, поэтому я не нашел способа изменить эти решения для этой проблемы.

Ответ №1:

Традиционным ответом D3 здесь было бы добавление группы для каждого объекта, а затем добавление круга для каждого y значения для каждой группы.

Однако, поскольку вы, похоже, новичок в D3 (поправьте меня, если я ошибаюсь), я бы предложил просто создать один массив объектов, к которому вы можете перейти data .

Для этого есть несколько способов, таких как:

 const newData = data.reduce(function(a, c) {
  return a.concat(c.y.map(function(d) {
    return {
      x: c.x,
      y: d
    }
  }));
}, []);
 

Вот ваш код с этим изменением:

 const data = [{
    x: 0.2,
    y: [1, 2, 4]
  },
  {
    x: 0.3,
    y: [2]
  },
  {
    x: 0.5,
    y: [4, 7, 8, 12, 19]
  }, {
    x: 1.4,
    y: [1, 3]
  }
];

const newData = data.reduce(function(a, c) {
  return a.concat(c.y.map(function(d) {
    return {
      x: c.x,
      y: d
    }
  }));
}, []);

const x = d3.scaleLinear()
  .domain([0, 2])
  .range([0, 300]);

const y = d3.scaleLinear()
  .domain([0, 20])
  .range([0, 150]);

const svg = d3.select("svg");
svg.selectAll("circle")
  .data(newData)
  .enter()
  .append("circle")
  .attr("cx", function(d) {
    return x(d.x)
  })
  .attr("cy", function(d) {
    return y(d.y)
  })
  .attr("r", 4) 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg> 

Ответ №2:

Почему бы не попробовать развернуть массив данных раньше? Это должно быть легко обработать.

 data = [{
    x: 0.2,
    y: [1, 2, 4]
  },
  {
    x: 0.3,
    y: [2]
  },
  {
    x: 0.5,
    y: [4, 7, 8, 12, 19],
  },
  {
    x: 1.4,
    y: [1, 3]
  }
];
unwrappedData = [];

for (b in data) {
  var temp = data[b].y.map((foo) => {
    return {
      x: data[b].x,
      y: foo
    }
  })
  unwrappedData = unwrappedData.concat(temp);

}

console.log(unwrappedData);

var svg = d3.select("body").append("svg")
  .attr("width", 100)
  .attr("height", 100)
  .style("margin-top", "58px");

svg.selectAll("circle")
  .data(unwrappedData)
  .enter()
  .append("circle")
  .attr("cx", function(d) {
    return d.x
  })
  .attr("cy", function(d) {
    return d.y
  })
  .attr("r", 2) 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script> 

Кроме того, не должны ли атрибуты cx и cy возвращать d.x и d.y вместо этого?

Комментарии:

1. Спасибо за ваш ответ. Ваше решение работает, но я выбрал другое, потому что оно примерно в пять раз быстрее. Я подумал, что дам вам знать, если вам интересно посмотреть на разницу в производительности.