Как правильно объединить / сгруппировать несколько линейных графиков в один общий граф с помощью d3.js, если значения x не совпадают точно? - PullRequest
0 голосов
/ 18 июня 2019

У меня есть несколько наборов данных, таких как:

const server1 = [
  { date: '2019-06-15T00:22:25.000Z', online: 451 },
  { date: '2019-06-15T01:08:58.000Z', online: 499 }
];

const server2 = [
  { date: '2019-06-15T00:14:18.000Z', online: 599 },
  { date: '2019-06-15T00:58:56.000Z', online: 554 }
];

const server3 = [
  { date: '2019-06-15T00:10:18.000Z', online: 321 },
  { date: '2019-06-15T00:54:56.000Z', online: 300 }
];

Мне удалось нарисовать линейный график для каждого из них, и диаграмма выглядит так: enter image description here

Но теперь я хочу нарисовать дополнительный линейный график, который объединяет данные каждого графика в отдельный график, так что он показывает оценку общего числа в сети со всех серверов.

Я незнаете, как я мог бы справиться с этим, потому что даты (значения x) не совпадают точно, поэтому я не могу просто сложить значения.

Вот jsfiddle с кодом, который у меня есть до сих пор


Обновление 1:

Вот функция, которую я собрал вместе с предложениями Кодерино Хаварино :

const mergeDatasets = (xPropName, yPropName, ...datasets) => {
  const makeScale = dataset => {
    return d3
      .scaleTime()
      .domain(dataset.map(obj => new Date(obj[xPropName])))
      .range(dataset.map(obj => obj[yPropName]));
  };
  const sumOtherDataSets = (scales, object, curDataSetIndex) => {
    let sum = object[yPropName];
    scales.forEach((_scale, scaleIndex) => {
      if (curDataSetIndex !== scaleIndex) {
        sum += scales[scaleIndex](new Date(object[xPropName]));
      }
    });
    return sum;
  };

  // make a scale for each dataset
  const scales = datasets.map(dataset => makeScale(dataset));

  /* make an new array where each datapoint is summed with the interpolated 
        /* counterparts from all other datasets */
  let merged = [];
  datasets.forEach((dataset, curDataSetIndex) => {
    const summedDataset = dataset.map(object => {
      const objCopy = { ...object };
      objCopy[yPropName] = sumOtherDataSets(scales, object, curDataSetIndex);
      return objCopy;
    });
    merged = [].concat(merged, summedDataset);
  });

  // restore the correct order by date
  merged = merged.sort((a, b) => new Date(a[xPropName]) - new Date(b[xPropName]));
  return merged;
};

Затем я могу легко использовать его следующим образом:

    const merged = mergeDatasets("date", "online", server1, server2, server3);

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

enter image description here

Вот обновленныйскрипка, которая также показывает проблему

1 Ответ

1 голос
/ 18 июня 2019

Подход, который я выбрал бы:

  1. извлечь все значения x, где по крайней мере одна строка имеет указанное значение.
  2. создать масштаб для каждой линии, где домен - это все его точки x, а диапазон - все его точки y
  3. перебрать список x, получить интерполированное значение y в каждой строке и сложить вместе.

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

const server1 = [
  { date: '2019-06-15T00:22:25.000Z', online: 451 },
  { date: '2019-06-15T01:08:58.000Z', online: 499 },
  { date: '2019-06-15T02:25:35.000Z', online: 464 },
  { date: '2019-06-15T13:25:42.000Z', online: 252 },
  { date: '2019-06-15T15:45:24.000Z', online: 247 },
  { date: '2019-06-15T17:02:09.000Z', online: 254 },
  { date: '2019-06-15T20:11:00.000Z', online: 300 },
  { date: '2019-06-15T21:22:33.000Z', online: 296 },
  { date: '2019-06-15T22:24:58.000Z', online: 298 },
  { date: '2019-06-15T22:58:18.000Z', online: 270 }
];

const server2 = [
  { date: '2019-06-15T00:14:18.000Z', online: 599 },
  { date: '2019-06-15T00:58:56.000Z', online: 554 },
  { date: '2019-06-15T02:15:13.000Z', online: 505 },
  { date: '2019-06-15T13:32:19.000Z', online: 221 },
  { date: '2019-06-15T15:19:08.000Z', online: 265 },
  { date: '2019-06-15T16:04:55.000Z', online: 277 },
  { date: '2019-06-15T17:31:16.000Z', online: 275 },
  { date: '2019-06-15T18:41:16.000Z', online: 303 },
  { date: '2019-06-15T20:05:41.000Z', online: 333 },
  { date: '2019-06-15T21:39:44.000Z', online: 288 },
  { date: '2019-06-15T22:46:01.000Z', online: 277 },
  { date: '2019-06-15T23:29:16.000Z', online: 264 }
];

const server3 = [
  { date: '2019-06-15T00:10:18.000Z', online: 321 },
  { date: '2019-06-15T00:54:56.000Z', online: 300 },
  { date: '2019-06-15T02:11:13.000Z', online: 280 },
  { date: '2019-06-15T13:28:19.000Z', online: 110 },
  { date: '2019-06-15T15:15:08.000Z', online: 130 },
  { date: '2019-06-15T16:01:55.000Z', online: 133 },
  { date: '2019-06-15T17:25:16.000Z', online: 140 },
  { date: '2019-06-15T18:37:16.000Z', online: 172 },
  { date: '2019-06-15T20:01:41.000Z', online: 180 },
  { date: '2019-06-15T21:35:44.000Z', online: 201 },
  { date: '2019-06-15T22:41:01.000Z', online: 210 },
  { date: '2019-06-15T23:23:16.000Z', online: 197 }
];

var x_list = [].concat(server1, server2, server3)
  .map(d => new Date(d.date))
  .sort(d3.ascending);
  
var servers_scales = [server1, server2, server3].map(s => {
  return d3.scaleLinear()
    .domain(s.map(d => new Date(d.date)))
    .range(s.map(d => d.online));
});

var combinedData = x_list.map(x => {
  var val = 0;
  for (var i = 0; i < servers_scales.length; i++)
    val += servers_scales[i](x);
  return {
    date: x,
    online: val
  };
});


const combined = [].concat(server1, server2, server3);

const margin = {top: 50, right: 50, bottom: 50, left: 50};
const width = window.innerWidth - margin.left - margin.right;
const height = window.innerHeight - margin.top - margin.bottom;

const dateMin = d3.min(combined, d => new Date(d.date));
const dateMax = d3.max(combined, d => new Date(d.date));
const onlineMin = d3.min(combined, d => d.online);
const onlineMax = d3.max(combined, d => d.online);

const xScale = d3
  .scaleTime()
  .domain([dateMin, dateMax])
  .range([0, width]);

const yScale = d3
  .scaleLinear()
  .domain([0, onlineMax * 3])
  .range([height, 0]);
  
const line = d3
  .line()
  .x(d => xScale(new Date(d.date)))
  .y(d => yScale(d.online))
  .curve(d3.curveMonotoneX);
  
const svg = d3
  .select('body')
  .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 + ")");
  
svg
  .append('path')
  .datum(server1)
  .attr('class', 'line')
  .attr('fill', 'none')
  .attr('stroke', 'blue')
  .attr('d', line);
  
svg
  .append('path')
  .datum(server2)
  .attr('class', 'line')
  .attr('fill', 'none')
  .attr('stroke', 'red')
  .attr('d', line);
  
svg
  .append('path')
  .datum(server3)
  .attr('class', 'line')
  .attr('fill', 'none')
  .attr('stroke', 'green')
  .attr('d', line);

svg
  .append('path')
  .datum(combinedData)
  .attr('class', 'line')
  .attr('fill', 'none')
  .attr('stroke', 'orange')
  .attr('d', line);

svg
	.append("g")
  .attr("class", "y axis")
  .call(d3.axisLeft(yScale));
  
svg.append("g")
  .attr("class", "x axis")
  .attr("transform", "translate(0," + height + ")")
  .call(d3.axisBottom(xScale));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
...