Трехмерный (X, Y и Z) график с использованием D3.js

116
18 января 2020, 23:40

Я ищу график, который имеет 3 измерения (x, y и z) и использует D3.js.

Пожалуйста, подскажите, есть ли какой-либо сайт по визуализации данных, где я могу найти такой график, или есть на самом сайте d3js.org, где я не смог его найти.

Answer 1

Вот такая вот попытка изобразить перспективу на чистом js+svg:

let f = (x, z) => Math.cos(z/20)*20 + Math.sin(x/10)*10 + x/3*Math.atan2(z,x); 
 
let cos = Math.cos, sin = Math.sin, xyz = 'xyz'.split(''), 
    k = 500, a1 = 0, a2 = 0, far = 300, p, w, h, a, 
    points = [], lines = [], s = 100; 
     
for (var x = -s; x < s; x += 5)  
for (var z = -s; z < s; z += 5)  
  points.push({x, z, r:2}); 
 
for (var i = 0; i < 6; i++) lines.push([ 
  {x:-s, y:-s, z:-s, color:`hsl(${i*120},55%,55%)`, state:{}}, 
  {x:i%3==0?s:-s, y:i%3==1?s:-s, z:i%3==2?s:-s, state:{}} 
]); 
 
points.forEach(d=>d.state={fill:`rgb(${d.x+s},${(d.y=f(d.x,d.z))+s},${d.z+s})`}); 
pointsGroup.innerHTML=points.map((d,i)=>`<circle ind="${i}"></circle>`).join(''); 
linesGroup.innerHTML=lines.map(d=>`<path stroke="${d[0].color}"></path>`).join(''); 
let circles = pointsGroup.querySelectorAll('circle'); 
let paths = linesGroup.querySelectorAll('path'); 
 
function project(p) { 
  let x = p.x*cos(a1) + p.z*sin(a1); 
  let z = p.z*cos(a1) - p.x*sin(a1); 
  let y = p.y*cos(a2) +   z*sin(a2); 
  let d =   z*cos(a2) - p.y*sin(a2) + far; 
  p.state.cx = (k/d)*x + w/2; 
  p.state.cy = (k/d)*y + h/2; 
  p.state.r = far/d*p.r; 
} 
 
function render() { 
  if (a) for (var i=0; i<3; i++)  
    xyz.forEach((c, j) => lines[i][0][c] = i==j ? -s : (lines[i][1]=a)[c]); 
  points.forEach(project);	 
  points.sort((a, b) => a.state.r - b.state.r); 
  lines.forEach(line => line.forEach(project));	 
  points.forEach((d, i) => Object.entries(d.state) 
      .forEach(e => circles[i].setAttribute(...e))); 
  lines.forEach((l, i) => paths[i].setAttribute('d',  
     `M${l[0].state.cx} ${l[0].state.cy} L${l[1].state.cx} ${l[1].state.cy}`)); 
} 
 
let evt = (t, f) => addEventListener(t, e => render(f(e))); 
evt('click', e => a = points[e.target.getAttribute('ind')]) 
evt('wheel', e => k *= 1 - Math.sign(e.deltaY)*0.1); 
evt('mouseup', e => p = null); 
evt('mousedown', e => p = {x: e.x, y: e.y, a1, a2}); 
evt('mousemove', e => p && (a1 = p.a1-(e.x-p.x)/100) + (a2 = p.a2-(e.y-p.y)/100)); 
evt('resize', e=>svg.setAttribute('viewBox',`0 0 ${w=innerWidth} ${h=innerHeight}`)); 
dispatchEvent(new Event('resize'));
<svg id="svg" stroke-width="2"><g id="pointsGroup"></g><g id="linesGroup"></g></svg>

Почти тот же график, но уже на канве

let f = (x, z) => Math.cos(z/20)*20 + Math.sin(x/10)*10 + x/3*Math.atan2(z,x); 
let ctx = canvas.getContext('2d'); 
let cos = Math.cos, sin = Math.sin, xyz = 'xyz'.split(''), 
    k = 500, a1 = 0, a2 = 0, far = 300, p, w, h, a, 
    points = [], lines = [], s = 100; 
     
for (var x = -s; x < s; x += 5)  
for (var z = -s; z < s; z += 3)  
  points.push({x, y: f(x,z), z, r:2, state:{}}); 
 
points.forEach(d => d.color = `rgb(${d.x+s},${(d.y=f(d.x,d.z))+s},${d.z+s})`) 
 
for (var i = 0; i < 3; i++) lines.push([ 
  {x:-s, y:-s, z:-s, color:`hsl(${i*120},55%,55%)`, state:{}}, 
  {x:i%3==0?s:-s, y:i%3==1?s:-s, z:i%3==2?s:-s, state:{}} 
]); 
 
function project(p) { 
  let x = p.x*cos(a1) + p.z*sin(a1); 
  let z = p.z*cos(a1) - p.x*sin(a1); 
  let y = p.y*cos(a2) +   z*sin(a2); 
  let d =   z*cos(a2) - p.y*sin(a2) + far; 
  p.state.cx = (k/d)*x + w/2; 
  p.state.cy = (k/d)*y + h/2; 
  p.state.r = far/d*p.r; 
} 
 
function render() { 
  points.forEach(project);	 
  points.sort((a, b) => a.state.r - b.state.r); 
  lines.forEach(line => line.forEach(project)); 
  ctx.clearRect(0,0,w,h) 
  points.forEach(drawPoint); 
  lines.forEach(drawLine); 
} 
 
function drawPoint(p){ 
    ctx.beginPath(); 
    ctx.arc(p.state.cx, p.state.cy, p.state.r, 0, 2 * Math.PI); 
    ctx.fillStyle = p.color; 
    ctx.fill(); 
} 
 
function drawLine(l){ 
    ctx.beginPath(); 
    ctx.moveTo(l[0].state.cx, l[0].state.cy); 
    ctx.lineTo(l[1].state.cx, l[1].state.cy); 
    ctx.strokeStyle = l[0].color; 
    ctx.stroke(); 
} 
 
let evt = (t, f) => addEventListener(t, e => render(f(e))); 
evt('wheel', e => k *= 1 - Math.sign(e.deltaY)*0.1); 
evt('mouseup', e => p = null); 
evt('mousedown', e => p = {x: e.x, y: e.y, a1, a2}); 
evt('mousemove', e => p && (a1 = p.a1-(e.x-p.x)/100) + (a2 = p.a2-(e.y-p.y)/100)); 
evt('resize', e => (w=canvas.width=innerWidth-20)+(h=canvas.height=innerHeight-20)); 
dispatchEvent(new Event('resize'));
<canvas id="canvas"></canvas>

Answer 2

Трехмерные графики рассеяния, ссылки, на которые даны VividD и Lars Kotthoff, вероятно, является лучшим примером того, что вы просите, но я собираюсь предположить, что, возможно, вы задаете не тот вопрос.

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

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

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

Bubble Scatterplot -- click for original

Вы сказали, что ваши три измерения - это Customer, Product и content. Я не знаю, какое значение имеет content (число или категория), но я почти уверен, что Customer и Product - это категории.

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

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

d3-bubble-matrix

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

Day / Hour Heatmap

Конечно, простая старая гистограмма с накоплением - это еще один способ показать две категории и числовое значение:

Stacked Bar Graphs

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

Как здесь:

Scatterplot Matrix

Или это приложение (где неделя и день недели - это два измерения данных, а категория / сумма - два других):

Pie Chart Small Multiples

Я надеюсь, что это дало вам много идей ...

Источник ответа:@AmeliaBR

3D Surface Plot in D3.js

3D Vector Field in D3.js

Источник ответа:@aturc

Answer 3

Вот один пример, который напоминает то, что вы ищете:

3D scatter plot

Обратите внимание, что в этом примере используется X3DOM, довольно новая попытка стандартизировать 3D-рендеринг в HTML, которая поддерживается не всеми браузерами.

Ищите также X3DOM в группе Google D3.

Showing GPS tracks in 3D with three.js and d3.js

В целом, D3.js больше ориентирован на визуализацию данных, чем на научную визуализацию, это означает, что он не имеет широкой поддержки для отображения трехмерного пространства (за исключением отображения географических 3D-данных, которые D3.js поддерживает превосходным образом, но это не то, что нужно).

Например (этот пример не связан напрямую с вашими примерами, он просто для пояснения): D3 предоставляет алгоритм для 2D-рисования деревьев, но не предоставляет никакого устройства для 3D-размещения деревьев и последующей визуализации такого размещения на 2D-экране.

Если вы не ограничены D3.js, возможно, вы могли бы достичь тех же целей проще и быстрее с другими библиотеками, написанными специально для целей, аналогичных вашим. Например, вы можете использовать Pre3D

Посмотрите на этот пример не использует X3DOM, просто рендеринг HTML на 2D-холсте. Я думаю, что анимация - вращение его трехмерных графиков, более плавная, чем в первом примере D3/X3DOM.

Источник ответа:@VividD

READ ALSO
Как проверить ширину каждого элемента?

Как проверить ширину каждого элемента?

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

117
Не работает WebSocket

Не работает WebSocket

Пытаюсь сделать минимальный рабочий пример с веб-сокетамиНа стороне сервера использую Spring Framework, клиент - JavaScript

95
Настройка даты и времени в input

Настройка даты и времени в input

Как сделать, чтобы <input type="datetime-local" > имел максимальное значение даты и времени, сегодняЧерез JS

126
Классы JS (В частности ES5)

Классы JS (В частности ES5)

Допустим такой код:

145