Послушайте — я люблю Codepen. Codepen — невероятный и жизненно важный инструмент, и поздние ночи, проведенные за повышением квалификации на платформе, вероятно, во многом являются причиной того, что я профессионально работаю в этой отрасли. Но когда дело доходит до прототипирования визуализаций D3.js, это может быть своего рода обломом.

D3 в целом имеет тенденцию быстро соскальзывать на территорию спагетти, особенно когда ваши взаимодействия становятся более сложными и запутанными. У среднего разработчика D3 есть инструменты в своем арсенале для борьбы с плохими практиками, но эти методы, по общему признанию, сложно реализовать в Codepen.

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

Давайте начнем с создания каталога для работы.

mkdir d3-boilerplate && cd d3-boilerplate
#If you have Oh-My-Zsh installed try this
take d3-boilerplate

Далее давайте инициализируем наш package.json и установим наши зависимости

npm init
# enter through the defaults or fill em out -- do what you feel
npm install --save-dev babel-cli babel-preset-env beefy browserify

Мы устанавливаем и добавляем в наш проект четыре зависимости: Babel-Cli и Babel-Preset-Env, которые мы будем использовать для переноса нашего ES6 в ES5, и Browserify, который позволит нам использовать синтаксис Common.js, подобный Node, для сделать наш D3 более удобным в сопровождении и связать все это перед отправкой в ​​наш браузер, и, наконец, Beefy, как наш сверхлегкий сервер и исполнителя горячей перезагрузки.

Откройте package.json и добавьте следующее под "scripts".

...
"scripts": {
  "start": "beefy d3.js --live",
  "bundle": "browserify app.js -o ./bundle.js"
},
...

Сценарий "start" вызывает наш сервер Beefy, сообщает ему, что точкой входа нашего приложения является d3.js, и что мы хотим включить горячую перезагрузку с помощью --live. Мол, аккуратно!

Сценарий "bundle" — это то, что мы запустим, когда закончим писать код, чтобы упаковать все наши разрозненные JS-файлы в один красивый и аккуратный экспорт для использования в любом месте.

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

{
  "presets": ["env"]
}

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

Поскольку D3.js полагается на DOM, нам понадобится немного HTML.

touch index.html

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">    
    <title>D3 Boilerplate</title>
  </head>
  <body>
    <svg height="500" width="500"></svg>
    <script src="https://d3js.org/d3.v4.min.js"></script>    
    <script src="d3.js"></script>
  </body>
</html>

Это ваш стандартный шаблон HTML с тремя исключениями. Во-первых, мы собираемся предварительно определить SVG, к которому мы добавим нашу визуализацию. В качестве альтернативы вы можете сделать это div с идентификатором, например root, выбрать и добавить к нему SVG, но я предпочитаю избегать посторонних тегов <div>, когда это возможно.

Во-вторых, и в-третьих, нам нужна наша библиотека, а затем наш код — и то, и другое. Обязательно делайте их именно в этом порядке перед закрывающим тегом </body>. Почему мы повышаем уровень D3, а не npm, устанавливая его в наше приложение? У D3 есть некоторые проблемы со сборкой. Короче говоря, D3 хочет быть включен в вашу визуализацию как глобально доступная переменная, и у меня есть давний опыт предоставления D3 всего, что он хочет, так зачем останавливаться сейчас?

В корне прямо рядом с index.html создайте вышеупомянутую точку входа для вашей визуализации D3. Назовите его как хотите, но убедитесь, что вы используете одно и то же имя в своем скрипте npm и в теге скрипта вашего index.html. Мне нравится называть вещи в честь того, что в них содержится.

touch d3.js

Давайте остановимся и посмотрим, что мы сделали. Ваш шаблон d3 должен выглядеть примерно так:

node_modules/
.babelrc
d3.js
index.html
package-lock.json
package.json

Худощавый и подлый. Внутри d3.js давайте добавим что-нибудь приятное на вид, просто чтобы проверить нашу настройку.

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height"),
    angles = d3.range(0, 2 * Math.PI, Math.PI / 200);

var path = svg.append("g")
    .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
    .attr("fill", "none")
    .attr("stroke-width", 10)
    .attr("stroke-linejoin", "round")
  .selectAll("path")
  .data(["cyan", "magenta", "yellow"])
  .enter().append("path")
    .attr("stroke", function(d) { return d; })
    .style("mix-blend-mode", "darken")
    .datum(function(d, i) {
      return d3.radialLine()
          .curve(d3.curveLinearClosed)
          .angle(function(a) { return a; })
          .radius(function(a) {
            var t = d3.now() / 1000;
            return 200 + Math.cos(a * 8 - i * 2 * Math.PI / 3 + t) * Math.pow((1 + Math.cos(a - t)) / 2, 3) * 32;
          });
    });

d3.timer(function() {
  path.attr("d", function(d) {
    return d(angles);
  });
});

Запуск npm start, а затем переход к localhost:9966 в вашем браузере должен вызвать транс Майка Бостока, вызывающий визуализацию круговой волны. Хорошая вещь.

Этот кусок кода довольно аккуратный, но что, если бы мы знали позже, что будем расширять эту функцию d3.timer в конце? Вместо того, чтобы увеличивать этот код в два или, может быть, в три раза, мы можем использовать Browserify, чтобы просто разбить таймер на его собственный timer.js файл, который мы затем можем require добавить в этот. Давайте сделаем это!

touch timer.js

И в этот вырез вставьте функцию синхронизации, обернутую в module.exports, с путем и переменными, переданными в качестве аргументов.

module.exports = (path, angles) => {
  d3.timer(function() {
    path.attr("d", function(d) {
      return d(angles);
    });
  });
}

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

//THIS IS NEW
var timer = require('./timer.js')
var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height"),
    angles = d3.range(0, 2 * Math.PI, Math.PI / 200);

var path = svg.append("g")
    .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
    .attr("fill", "none")
    .attr("stroke-width", 10)
    .attr("stroke-linejoin", "round")
  .selectAll("path")
  .data(["cyan", "magenta", "yellow"])
  .enter().append("path")
    .attr("stroke", function(d) { return d; })
    .style("mix-blend-mode", "darken")
    .datum(function(d, i) {
      return d3.radialLine()
          .curve(d3.curveLinearClosed)
          .angle(function(a) { return a; })
          .radius(function(a) {
            var t = d3.now() / 1000;
            return 200 + Math.cos(a * 8 - i * 2 * Math.PI / 3 + t) * Math.pow((1 + Math.cos(a - t)) / 2, 3) * 32;
          });
    });
//THIS IS ALSO NEW
timer(path, angles);

Итак, теперь timer.js может расти настолько, насколько захочет, и наш проект по-прежнему можно поддерживать. Вы также можете разбить это на path.js и даже svg.js, если хотите. У вас может быть mkddir несколько папок и даже несколько проектов, которые вы вызываете с помощью разных скриптов npm — с ума сойти!

репо