Эта статья относится к Angular 2.X или новее и D3 4.X или новее.

Фрагменты кода в статье выделены отрывками. Полный рабочий код можно найти здесь: https://github.com/ygaller/d3-ng2-circle-packing.

Щелкните здесь, чтобы просмотреть живую демонстрацию кода.

Angular - отличный фреймворк для создания веб-приложений, а D3 - наша любимая библиотека визуализации данных.

На Github есть несколько отличных проектов, демонстрирующих, как их совместить. Прежде всего, мы можем использовать Сервис D3 для Angular 2, который предоставляет функциональность D3 в Angular как услугу. Затем есть проект D3 Angular Demo, который берет существующий компонент и реализует его в Typescript внутри компонента Angular.

Хотя это отличная отправная точка, когда мы думали о том, как встраивать элементы D3 в приложение Leadspace на основе Angular, мы решили, что было бы неплохо применить некоторые принципы проектирования SOLID, чтобы улучшить возможность повторного использования. наших компонентов. В частности, принцип единой ответственности« S » и принцип инверсии зависимости« D ».

Абстракции Angular предоставляют нам удобные реализации этих принципов: обязанности разделены на компоненты, которые представляют представления пользовательского интерфейса и сервисы, которые инкапсулируют бизнес-логику в повторно используемой форме. Кроме того, Angular предоставляет нам готовую инъекцию зависимостей, так что любому компоненту или сервису нужно только объявить свои требования к зависимости, и экземпляр этой зависимости автоматически предоставляется компоненту (в отличие от компонента, который должен инициализировать свою сами зависимости).

Кроме того, веб-приложение Leadspace использует Redux в качестве хранилища данных, через которое пользовательский интерфейс публикует события и потребляет обновления. Поэтому нам нужно, чтобы наш компонент обменивался данными через хранилище данных Redux.

План игры

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

Итерации

Простой универсальный компонент

В качестве первого шага давайте реализуем простую версию компонента:

Компонент загружает flare.csv через http-вызов, преобразует данные в иерархическую структуру и передает их функции, которая отображает данные.

Отлично работает, но при более внимательном рассмотрении кода можно выделить одну серьезную проблему: загрузка данных выполняется внутри компонента. Хотя мы предоставляем имя файла данных в качестве входных данных, у нас нет никакой гибкости в отношении формата исходных данных или этапов преобразования данных. По сути, наша составляющая «толстая», на которую возложено множество обязанностей. Кроме того, если мы хотим попробовать использовать те же данные в каком-либо другом компоненте, не существует простого способа передать эти данные этому другому компоненту.

Поэтому нашим следующим шагом будет отделение обработки данных от рендеринга данных.

Компонент службы данных и рендеринга

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

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

Компонент визуализации получает эти обработанные данные в иерархическом формате и визуализирует холст на основе этих данных.

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

Чтобы продемонстрировать преимущества этого подхода, давайте создадим еще одну версию сервиса поставщика данных. В качестве входных данных используется файл json, а не файл csv. Обработка данных, выполняемая в этом сервисе, полностью отличается от обработки данных в сервисе csv:

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

Служба данных Redux и компонент рендеринга Redux

В качестве последнего шага мы реализуем Redux в Angular с помощью ng2-redux.

Чтобы и наш сервис, и наш компонент могли взаимодействовать друг с другом через Redux, оба получают объект хранилища данных NgRedux в своих конструкторах.

Эта реализация дает нам возможность легко переносить наш компонент рендеринга в любое другое приложение на основе Angular / Redux.

Вывод

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

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

Я надеюсь, что это дало интересное представление о другом способе работы с Angular, D3 и Redux. Я хотел бы услышать комментарии и идеи по улучшению!