html текст
All interests
  • All interests
  • Design
  • Food
  • Gadgets
  • Humor
  • News
  • Photo
  • Travel
  • Video
Click to see the next recommended page
Like it
Don't like
Add to Favorites

Визуализируем в 3D, или как подружить D3 и Three.js tutorial

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





Откуда ноги растут?



Некоторое время назад мы в CodeOrchestra экспериментировали с портом D3 на AS3/DSL под кодовым названием «D6» (от D3 + 3D). Наш порт покрывал лишь самые базовые функции D3, но зато умел работать с популярными 3D движками на AS3 «из коробки». И хотя мы так и не вывели D6 в свет, та самая идея использовать D3 для 3D с тех пор не покидает наши умы. Действительно, если Вы заглянете в галерею D3, Вы не найдёте там ни одного трёхмерного примера. Причина в том, что D3 сильно заточена на работу с DOM браузера, и вроде как не поддерживает выборки произвольных объектов. Однако, обладая достаточной мотивацией, мы можем её заставить.


Итак, начнём



Начнём с простейшего примера двухмерной гистограммы с использованием D3 (тут и далее код адаптирован из официальных уроков D3 [1] и [2], и сокращён ради читабельности):

d3.select(".chart")
  .selectAll()
    .data(data)
  .enter().append("div")
    .style("width", function(d) { return d * 10 + "px"; });

В этом примере видно, что основные методы D3 принимают в качестве аргументов волшебные DOM-зависимые строки (такие как селектор .chart или имя тега div), что крайне неудобно для наших целей. К счастью, у этих методов имеются альтернативные сигнатуры. Эти сигнатуры существуют для скучных вещей вроде повторного использования выборок. Мы же воспользуемся ими, чтобы переписать наш пример следующим образом:

function newDiv() {
    return document.createElement("div");
}
 
var chart = {
    appendChild: function (child) {
        // эта функция будет вызвана из append() после newDiv()
        return document.getElementById("chartId")
            .appendChild(child);
    },
    querySelectorAll: function () {
        // эта функция будет вызвана из selectAll()
        return [];
    }
}
 
d3.select( chart )
  .selectAll()
    .data(data)
  .enter().append( newDiv )
    .style("width", function(d) { return d * 10 + "px"; });

Как видим, мы 1) указали D3 как создавать div в явном виде, и 2) убедили D3 в том, что наш объект chart — утка. При этом результат нашего кода совершенно не изменился.


Так что насчёт 3D ?



Стандартом де-факто для 3D графики в JavaScript на сегодняшний день является Three.js. Если мы хотим делать 3D в D3, нам надо аналогичным образом убедить D3 работать с выборками из трёхмерных объектов Three.js. Для этого мы добавим следующие методы в прототип Object3D:

// эти методы нужны для D3-шных .append() и .selectAll()
THREE.Object3D.prototype.appendChild = function (c) { this.add(c); return c; };
THREE.Object3D.prototype.querySelectorAll = function () { return []; };

// а этот - для D3-шного .attr()
THREE.Object3D.prototype.setAttribute = function (name, value) {
    var chain = name.split('.');
    var object = this;
    for (var i = 0; i < chain.length - 1; i++) {
        object = object[chain[i]];
    }
    object[chain[chain.length - 1]] = value;
}

Этого вполне достаточно для создания простейшей трёхмерной гистограммы:

function newBar () {
    return new THREE.Mesh( geometry, material );
}

chart3d = new THREE.Object3D();

d3.select( chart3d )
  .selectAll()
    .data(data)
  .enter().append( newBar )
    .attr("position.x", function(d, i) { return 30 * (i - 3); })
    .attr("position.y", function(d, i) { return d; })
    .attr("scale.y", function(d, i) { return d / 10; })



Это всё ?



Вовсе нет. Чтобы использовать главную фишку D3 — обработку изменения данных — нам необходимо пересмотреть наши обманки. Во-первых, чтобы D3 могла интерполировать значения «аттрибутов», нам необходимо добавить в прототип Object3D метод getAttribute:

THREE.Object3D.prototype.getAttribute = function (name) {
    var chain = name.split('.');
    var object = this;
    for (var i = 0; i < chain.length - 1; i++) {
        object = object[chain[i]];
    }
    return object[chain[chain.length - 1]];
}

Во-вторых, selectAll() должен на самом деле работать, чтобы построить выборку обновляющихся объектов. Например, мы можем реализовать отбор наследников Object3D определённого типа:

THREE.Object3D.prototype.querySelectorAll = function (selector) {
    var matches = [];
    var type = eval(selector);
    for (var i = 0; i < this.children.length; i++) {
        var child = this.children[i];
        if (child instanceof type) {
            matches.push(child);
        }
    }
    return matches;
}

Чтобы заставить наши столбцы танцевать, теперь достаточно просто периодически изменять данные:

var N = 9, v = 30, data = d3.range(9).map(next);

function next () {
    return (v = ~~Math.max(10, Math.min(90, v + 20 * (Math.random() - .5))));
}

setInterval(function () {
  data.shift(); data.push(next()); update();
}, 1500);

function update () {
    // используем D3 для создания и обновления 3D столбцов
    var bars = d3.select( chart3d )
        .selectAll("THREE.Mesh")
        .data(data);
    
    bars.enter().append( newBar )
        .attr("position.x", function(d, i) { return 30 * (i - N/2); });
    
    bars.transition()
        .duration(1000)
        .attr("position.y", function(d, i) { return d; })
        .attr("scale.y", function(d, i) { return d / 10; });
}

Итак, общий принцип спаривания D3 с Three.js Вам должен быть ясен — мы постепенно добавляем в прототип Object3D методы, достаточные для работы интересующего нас функционала D3. Но для закрепления рассмотрим последний вариант гистограммы, в котором используем привязку данных по ключу и работу с выборкой удаляемых объектов. Добавим в прототип Object3D метод removeChild:

THREE.Object3D.prototype.removeChild = function (c) { this.remove(c); }

Если бы Вы попробовали теперь воспользоваться методом remove() выборки удаляемых объектов, то обнаружили бы, что ничего не происходит. Почему? Ответ легко увидеть в исходниках D3 — метод remove() не использует parentNode выборки, а пытается удалять объект из своего непосредственного родителя. Чтобы сделать это возможным, необходимо скорректировать нашу реализацию appendChild():

THREE.Object3D.prototype.appendChild = function (c) {
    this.add(c);
    // создаём свойство parentNode
    c.parentNode = this;
    return c;
}



Итог



А в итоге у нас получилась вот такая красота:

var N = 9, t = 123, v = 30, data = d3.range(9).map(next);

function next () {
  return {
    time: ++t,
    value: v = ~~Math.max(10, Math.min(90, v + 20 * (Math.random() - .5)))
  };
}

function update () {
	// используем D3 для создания, обновления и удаления 3D столбцов
	var bars = d3.select( chart3d )
		.selectAll("THREE.Mesh")
		.data(data, function(d) { return d.time; });

	bars.transition()
		.duration(1000)
		.attr("position.x", function(d, i) { return 30 * (i - N / 2); })

	bars.enter().append( newBar )
		.attr("position.x", function(d, i) { return 30 * (i - N / 2 + 1); })
		.attr("position.y", 0)
		.attr("scale.y", 1e-3)
	  .transition()
		.duration(1000)
		.attr("position.x", function(d, i) { return 30 * (i - N / 2); })
		.attr("position.y", function(d, i) { return d.value; })
		.attr("scale.y", function(d, i) { return d.value / 10; })
		
	bars.exit().transition()
		.duration(1000)
		.attr("position.x", function(d, i) { return 30 * (i - N / 2 - 1); })
		.attr("position.y", 0)
		.attr("scale.y", 1e-3)
		.remove()
}

Как видим, D3 прекрасно справляется с 3D, если ей немного помочь, а Three.js не создаёт в этом никаких проблем. Обе библиотеки имеют свои сильные стороны, и я надеюсь, что эта статья открыла Вам путь к их гармоничному сочетанию в Ваших будущих работах.
Читать дальше
Twitter
Одноклассники
Мой Мир

материал с habrahabr.ru

5

      Add

      You can create thematic collections and keep, for instance, all recipes in one place so you will never lose them.

      No images found
      Previous Next 0 / 0
      500
      • Advertisement
      • Animals
      • Architecture
      • Art
      • Auto
      • Aviation
      • Books
      • Cartoons
      • Celebrities
      • Children
      • Culture
      • Design
      • Economics
      • Education
      • Entertainment
      • Fashion
      • Fitness
      • Food
      • Gadgets
      • Games
      • Health
      • History
      • Hobby
      • Humor
      • Interior
      • Moto
      • Movies
      • Music
      • Nature
      • News
      • Photo
      • Pictures
      • Politics
      • Psychology
      • Science
      • Society
      • Sport
      • Technology
      • Travel
      • Video
      • Weapons
      • Web
      • Work
        Submit
        Valid formats are JPG, PNG, GIF.
        Not more than 5 Мb, please.
        30
        surfingbird.ru/site/
        RSS format guidelines
        500
        • Advertisement
        • Animals
        • Architecture
        • Art
        • Auto
        • Aviation
        • Books
        • Cartoons
        • Celebrities
        • Children
        • Culture
        • Design
        • Economics
        • Education
        • Entertainment
        • Fashion
        • Fitness
        • Food
        • Gadgets
        • Games
        • Health
        • History
        • Hobby
        • Humor
        • Interior
        • Moto
        • Movies
        • Music
        • Nature
        • News
        • Photo
        • Pictures
        • Politics
        • Psychology
        • Science
        • Society
        • Sport
        • Technology
        • Travel
        • Video
        • Weapons
        • Web
        • Work

          Submit

          Thank you! Wait for moderation.

          Тебе это не нравится?

          You can block the domain, tag, user or channel, and we'll stop recommend it to you. You can always unblock them in your settings.

          • habrahabr.ru
          • домен habrahabr.ru

          Get a link

          Спасибо, твоя жалоба принята.

          Log on to Surfingbird

          Recover
          Sign up

          or

          Welcome to Surfingbird.com!

          You'll find thousands of interesting pages, photos, and videos inside.
          Join!

          • Personal
            recommendations

          • Stash
            interesting and useful stuff

          • Anywhere,
            anytime

          Do we already know you? Login or restore the password.

          Close

          Add to collection

             

            Facebook

            Ваш профиль на рассмотрении, обновите страницу через несколько секунд

            Facebook

            К сожалению, вы не попадаете под условия акции