Coder Social home page Coder Social logo

alor-broker / astras-trading-ui Goto Github PK

View Code? Open in Web Editor NEW
60.0 3.0 20.0 27.21 MB

Astras. The Angular's trading terminal from Alor Broker. https://alorbroker.ru/

License: Apache License 2.0

JavaScript 0.15% TypeScript 52.26% HTML 45.62% Less 1.95% CSS 0.01% Shell 0.02%

astras-trading-ui's Introduction

Astras – торговое веб-приложение с системой виджетов

Frame 93

🔹 Особенности

Динамическая система виджетов

Увеличивайте, уменьшайте, раздвигайте, добавляйте, в общем делайте то, что хотите.

Совместимость со всеми рынках

От ценных бумаг до криптовалюты и индексов.

Интегрированный технический анализ от TradingView

  • Неограниченное колличество графиков на одном экране
  • 25 индикаторов на одном графике
  • 7 основных видов графиков

Frame 103

🔸 Architecture

Мы расмотрим общую архитектуру (backend+frontend) и архитектуру самого фронтенда

Whole picture

Для работы терминала необходимо WebSocket-API, а также Http Rest API. Вам необходимо реализовать backend самостоятельно. Спецификация API доступна тут. Для получения часто изменяющихся данных, используется WebSocket API. Через него передаются такие данные как, например, котировки, сделки, заявки, изменения позиций, средние цены, риск-параметры и так далее. Для данных которые не подразумевают частое изменение, таких как: данные об инструменте, данные о клиенте, справка, авторизация и так далее используется Http API. Авторизация реализована через редирект на SSO и обратно и jwt/refresh токены. В случае если у вас реализована другая система авторизации, вам необходимо отредактировать сервис auth.service. Если ваша схема авторизации такая же, то нужно поменять настройку ssoUrl.

Front-end part

Instruments

Терминал использует следующие инструменты.

  • ant-design - дизайн от Ant
  • ng-zorro - библиотека компонентов
  • angular-gridster2 - библиотека для показа виджетов
  • karma + jasmine - тестирование
  • ng2-charts (chart.js) + lightweight-charts + d3 + tradingview(charting_library) - графики и прочая визуализация
  • ngx-markdown - для отображение некоторого контента, типо справки по виджету.
  • ngrx - для управления глобальным состоянием
  • ngx-device-info - для получения информации об устройстве, на котором запущено приложение
  • ngx-joyride - для ознакомления пользователя с ключевым функционалом
  • less - css препроцессор
  • transloco - локализация UI
  • angular-fire - для подключения PUSH-уведомлений через firebase
  • apollo - для запросов через GraphQL
  • pnpm - менеджер пакетов

Start

Чтобы запустить проект, необходимо установить пакеты

pnpm install

Запустить

pnpm start

И пройти по ссылке http://localhost:4200/dashboard

Project structure

Проект разбит на модули. Каждый виджет выделен в отдельный модуль. Функционал, который может быть использован в разных виджетах, находится в модуле Shared. Через него же осуществляется импорт общих зависимостей. Модуль shared импортируется почти во все виджеты.

Внутри каждого модуля есть 4 основных папки.

  • components - компоненты, которые используются в этом виджете. Они НЕ экспортируются наружу;
  • widgets - компоненты, которые используются из других виджетов. Они экспортируются и используют компоненты из /components как строительные блоки;
  • services - сервисы, которые используются только в этом модуле;
  • models - модели, которые используются только в этом модуле.

Так же есть дополнительные

  • utils - вспомогательный код, который используется только в этом модуле;
  • pages - если наш модуль не является виджетом, а является полноценной страницей со своим роутингом.

Поподробней расмотрим структуру shared модуля. Если вам необходимо добавить какую-либо angular сущность, то скорее всего это лучше сделать именно в shared модуле. Пример: interceptors, guards, pipes. Если вам нужно добавить какой-либо визуальный компонент, импорт скорее всего лучше сделать из shared модуля, ведь вряд ли этот компонент будет использоваться только в одном единственном модуле.

Отдельно выделен store модуль. Он содержит определения частей (feature) глобального состояния в терминах ngrx. Модуль store уже импортирован в shared. Для каждой части глобального состояния необходимо создавать свою папку, которая содержит файлы с определениями actions, reducers, effects и selectors.

Другим важным модулем является dashboard. Это основной модуль, который клиенты видят. Именно тут они работают с терминалом. Именно тут они торгуют, смотрят графики, добавляют новые виджеты и так далее. В нём мы используем все остальные компоненты и именно на него прописан основной маршрут. Данный модуль адаптирован для работы на desktop и mobile устройствах.

Также проект содержит папку build-specifics с файлами ext-modules.ts и ext-modules.prod.ts. Данные файлы позволяют подключать функционал, наличие которого зависит от текущей конфигурации, используемой при сборке (development или production). Так в ext-modules.ts следует перечислять модули, которые должны быть доступны только в development конфирурации, например, dev tools. А в ext-modules.prod.ts - модули, доступные только в production. Обратите внимание, что содержимое данных файлов заменяется одно на другое, а не мержится.

Routing

На данный момент существует только один основной маршрут /dashboard. В зависимости от типа устройства, на котором запускается приложение, данный маршрут автоматически будет изменен на /dashboard/mobile для загрузки версии, адаптированной для mobile устройств.

Общие компоненты

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

  • widget-header - общий скелет заголовка виджета с наименованием, кнопками справки, настроек, удаления виджета и т.д.;
  • widget-header-instrument-switch общий скелет заголовка виджета с функцией смены инструмента прямо через заголовок;
  • widget-settings - общий скелет настроек виджета с кнопками копирования виджета (если есть такая возможность) и сохранения настроек;
  • widget-skeleton - общий скелет виджета с заголовком, контентом виджета и его настройками;
  • base-table и lazy-loading-base-table - базовые классы для компонентов с обычными таблицами и таблицами с ленвой подгрузкой данных;
  • infinite-scroll-table - общий компонент для отображения таблицы с ленивой подгрузкой данных
  • instrument-search - автокомплит для поиска и выбора инструментов;
  • instrument-board-select - селект для выбора режима торгов инструмента;
  • instrument-badge-display и merged-badge - компонент для отображения бейджа у инструмента;
  • input-number - поле ввода числа. Принимает параметры числа и валидирует введённое значение, имеет обработку клавиш "ВВЕРХ" и "ВНИЗ" клавиатуры, а так же скролла;

Key services

Опишем сервисы, которые считаем ключевыми в приложении. Все они находятся в модуле shared в папке services

  • AuthService - сервис, специфичный для каждого проекта. Реализует логику авторизации и получения токена для последующей работы с бэкендом;
  • DashboardContextService - сервис, который позволяет получать информацию о текущем активном дашборде, выбранных на нем портфеле и инструментах;
  • ErrorHandlerService - глобальный обработчик ошибок (ErrorHandler). Перехватывая ошибки, данный сервис делегирует их обработку зарегистрированным обработчикам: HttpErrorHandler и LogErrorHandler;
  • LoggerService - сервис логгирования. Используется в компонентах и сервисах. Делегирует запись логов зарегистрированным логгерам. На текущий момент логи записываются в консоль браузера (ConsoleLogger) и на сервер Elasticsearch (RemoteLogger);
  • ManageDashboardsService - сервис для управления коллекцией дашбордов, а также виджетами текущего дашборда;
  • SubscriptionsDataFeedService - сервис через который проходит работа с Websocket API;
  • WidgetSettingsService - сервис для управления настройками виджетов;
  • AuthInterceptor - сервис который подставляет свежий токен во все http запросы.
  • EnvironmentService - сервис для получения данных окружения

Стили

В проекте используется less. Глобальные стили добавляются в styles.less.

В проекте реализованы две цветовые схемы: dark и light. Переменные и стили, специфичные для цветовой схемы, следует добавлять в файлы dark.less и default.less, расположенные в папке /src/styles/themes. При необходимости получить variables цветовой схемы или variables/mixins из библиотеки компонентов ng-zorro, например @primary-color и другие, нужно использовать themeMixin.

Общие стили, используемые разными компонентами, следует оформлять в виде mixins. Mixin должен быть расположен в папке styles/mixins.

Общие вспомогательные стили (utils) или кастомные стили для компонентов ng-zorro следует оформлять в виде отдельных файлов в папках /src/styles/utils и /src/styles/components соответственно. Все такие файлы должны быть импортированы в styles.less.

Environment настройки

  • apiUrl: - API для торгового API, пример 'https://api.alor.ru',
  • wsUrl: - Websocket URL, пример 'wss://api.alor.ru/ws',
  • clientDataUrl: - Api для получения данных по клиенту, 'https://lk-api.alor.ru',
  • ssoUrl: - адрес редиректа для sso 'https://login.alor.ru',
  • warpUrl: - Api для получения данных о релизах 'https://warp.alor.dev',
  • remoteSettingsStorageUrl: - Api для хранения настроек пользователя 'https://astras-dev.alor.ru/identity/v5/UserSettings',
  • teamlyDatabaseUrl: адрес для получения ссылок на справку виджетов,
  • logging: настройки логгирования (опциональные)
    • console: настройки логов, записываемых в консоль браузера (опциональные - если данная секция отсутствует, то логи в консоль не записываются)
      • minLevel: минимальный уровень записываемых логов (доступные значения в порядке повышения уровня: 'trace', 'info', 'warn', 'error')
    • remote: настройки логов, записываемых в elastic (опциональные - если данная секция отсутствует, то логи в Elasticsearch не записываются)
      • minLevel: минимальный уровень записываемых логов (доступные значения в порядке повышения уровня: 'trace', 'info', 'warn', 'error')
      • environment: метка окружения, на котором развернуто приложение ('prod' - боевое окружение, 'dev' - dev-контур, 'local' - локальное окружение)
      • loggingServerUrl: адрес сервера elastic 'https://astras-dev.alor.ru/eslogs'
      • authorization: настройки авторизации Elasticsearch (заполняются CI/CD)
        • name: имя пользователя
        • password: пароль
  • firebase: настройки сервера push-уведомлений,
  • externalLinks: список статических ссылок на вшешние ресурсы.

Конфигурация бирж

Файл assets/marketSettings.json позволяет настроить список бирж, с которыми работает приложение, а также указать некоторые аспекты, необходимые при отображении информации и формировании запросов к API. В данном файле должно быть отмечено какая из бирж является биржей по умолчанию и какой инструмент должен быть выбран при первом запуске (сбросе настроек) приложения пользователем.

Конфигурация виджетов

Файл assets/widgets-meta.json содержит список всех доступных в приложении виджетов. Для каждого виджета в данном списке указана служебная информация (typeId и definition), которая не должна изменяться, и настройки отображения (widgetName, icons).

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

Чтобы скрыть виджет только в меню виджетов desktop версии, нужно указать значение false для параметра desktopMeta.enabled.

Чтобы скрыть виджет только в mobile версии, нужно указать значение false для параметра mobileMeta.enabled. Отсутсвие секции mobileMeta говорит о том, что виджет для mobile версии не применяется. Аналогично осутствие секции desktopMeta означает, что виджет не применим для desktop.

ВАЖНО: при добавлении нового виджета в приложение он должен быть описан в данном файле.

Конфигурация дашборда по умолчанию

Дашборд по умолчанию отображается:

  • при первом запуске
  • при нажатии на кнопку Виджеты->Стандартный вид
  • при сбросе настроек терминала
  • в мобильной версии

Набор, расположение и порядок следования (в мобильной версии) виджетов определяется в файле assets/default-dashboard-config.json. Данный файл содержит раздельные настройки для desktop и mobile версий.

ВАЖНО: если какой-либо виджет отключен на desktop версии (см. предыдущую секцию), то его следует также убрать из конфигурации desktop дашборда, иначе при сбросе он будет появляться у пользователя.

Internationalization

В проекте используется библиотека transloco для интернационализации текста. Чтобы подключить новый язык переводов, необходимо описать его в transloco-root.module.ts, а так же в assets/i18n и всех вложенных директориях добавить JSON-файл именнованый, как язык, который был описан в transloco-root.module.ts, в котором будут описаны переводы. Для того, чтобы использовать переводы, описанные в assets/i18n в шаблонах компонентов, необходимо подключить структурную директиву *transloco с описанным scope при вложенности директорий (например *transloco="let t; scope: 'blotter/settings'"), а затем описанную функцию, которая принимает строку с ключевым словом из JSON-файлов переводов, включая scope, перобразованный к camelCase (например, учитывая scope из прошлого примера: <p>{{ t('blotterSettings.portfolioLabel') }}</p>). Чтобы использовать переводы внутри классов компонентов, был создан TranslatorService, в котором есть методы получения/изменения языка, а так же метод getTranslator, который принимает строку с scope и возвращает Observable с функцией, которая принимает массив строк ключей (для вложенных полей из JSON-файла переводов) и возвращает перевод по этим ключам.

PUSH-уведомления

Для показывания PUSH-уведомлений необходимо в файле firebase-messaging-sw.js исправить конфиг firebase на свой, а так же, при енобходимости, описать свою обработку сообщения в коллбеке messaging.onBackgroundMessage. Так же, для показывания firebase-уведомлений ng-zorro внутри приложения необходимо в environment исправить конфиг firebase на свой, и поправить обработку сообщений при необходимости в modules/push-notifications/services/push-notifications-provider.ts.

GraphQL

В некоторых сервисах для получения данных используется GraphQL API. Для таких запросов в проект была добавлена библиотека apollo-angular. Настроить url отправки запроса и прочие конфигурации можно в graphql.module.ts. В shared/services был добавлен сервис GraphQlService, в котором есть метод watchQuery, принимающий строку запроса, переменные и дополнительные опции (такие, как сохранение кэша). На бэкенде используется фреймворк chillicream. С более подробной информацией, как пользоваться GraphQL, вы можете ознакомиться здесь.

Тестирование

Самый мимимум, который должен быть в каждом компоненте и сервисе, это тест на успешное создание компонента. Он создается ng generate по умочанию, но необходимо поддерживать, добавлять моки и стабы для input и используемых сервисов. В shared/utils/testing.ts есть интрументы для создания моков компонентов, рандомных данных, а так же моки основных компонентов ng-zorro и модулей. В ключевых сервисах, покрытие тестами выше. Ну ладно, будет. В планах это есть, точно =)

astras-trading-ui's People

Contributors

agushasok avatar anvivasilev avatar dependabot[bot] avatar extred avatar exvion avatar pilgrimviis avatar sbelashevskiy avatar scarletcloak avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

astras-trading-ui's Issues

[BUG] Блоттер. Фильтрация по тикеру (имени) визуально не отображается

Опишите баг

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

Воспроизведение

  1. Выставить заявку
  2. Открыть виджет блоттер, перейти на вкладку "Заявки"
  3. В колонке "Тикер" нажать на Поиск (лупа), ввести любые данные, не совпадающие с тикером.
  4. Нажать на поиск еще раз, для скрытия поля поиска.

Среда.

  • ОС Windows 10
  • Браузер Chrome
  • Версия 100.0.4896.127

Ожидаемое поведение

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

Скриншоты

No response

Дополнительная информация

No response

Правила

  • Я согласен следовать правилам

[BUG] Профиль-Настройки. Не сохраняется опция "Вертикальная прокрутка"

Опишите баг

В настройках профиля не сохраняется опция "Вертикальная прокрутка"

Воспроизведение

  1. Нажать на иконку профиля
  2. Выбрать "Настройки"
  3. Изменить опцию "Вертикальная прокрутка"
  4. Нажать "Ок"
  5. Повторно зайти в настройки.

Среда.

  • ОС Windows 10
  • Браузер Chrome
  • Версия 100.0.4896.127

Ожидаемое поведение

Сохранение состоянии опции после нажатия "Ок"

Скриншоты

No response

Дополнительная информация

No response

Правила

  • Я согласен следовать правилам

[FEATURE] Показывать текущий портфель в окне создания новой заявки.

Is your feature request related to a problem? Please describe. \ Опишите проблему, которую решает ваша фича
Иногда забываешь переключить портфель и случайно выставляешь заявку по другому счёту.

Describe the solution you'd like \ Опишите предлагаемое вами решение
Показывать портфель при выставлении заявки

Describe alternatives you've considered \ Опишите альтернативные решения, о которых вы думали

Additional context \ Дополнительная информация

[BUG] Профиль-Отчеты. Неактуальная ссылка

Опишите баг

Кнопка "Отчеты" из иконки профиля ведет на страницу: "https://lk.alor.ru/broker/reports"

Воспроизведение

  1. Нажать на иконку профиля
  2. Выбрать "Отчеты"

Среда.

  • ОС Windows 10
  • Браузер Chrome
  • Версия 100.0.4896.127

Ожидаемое поведение

Переход на страницу "https://lk.alor.ru/reports/broker"

Скриншоты

No response

Дополнительная информация

No response

Правила

  • Я согласен следовать правилам

[FEATURE] Сделать onboarding для новых пользователей

Is your feature request related to a problem? Please describe. \ Опишите проблему, которую решает ваша фича
Когда заходишь в приложение, непонятно что означают некоторые значки.

Describe the solution you'd like \ Опишите предлагаемое вами решение
Сделать при первом заходе, небольшой мини-тур, где будут объяснены назначения основных кнопок.

Describe alternatives you've considered \ Опишите альтернативные решения, о которых вы думали
Можно сделать, чтобы при наведении мышкой, сплывала справка по кнопке. Но это будет надоедать, при долгом использовании.

Additional context \ Дополнительная информация

[BUG] Курсор над стаканом меняет свой вид

Опишите баг

Если стакан достаточно динамично меняется, то курсор, находясь над ним, дергается.

  1. При нахождении над цифрами меняется между "Рука" и "Курсор"
  2. При нахождении в области без цифр меняется между "Рука" и "Стрелка"
    Если произвести щелчок в тот момент когда курсор сменился на "Курсор" или "Стрелка", то ожидаемого открытия виджета "Выставить заявку" не происходит

Воспроизведение

  1. Открыть виджет "Стакан" и выбрать инструмент с динамичным обновлением.
  2. Навести курсор на любую позицию цены.

Среда.

  • ОС Windows 10
  • Браузер Chrome
  • Версия 100.0.4896.127

Ожидаемое поведение

Курсор над стаканом не изменяет свой вид.
При щелчке на позицию в стакане - возникает виджет "Выставить заявку"

Скриншоты

No response

Дополнительная информация

No response

Правила

  • Я согласен следовать правилам

[BUG] Instrument's info widget makes unnecessary http requests

Describe the bug \ Опишите баг
In the info widget, there're a lot of http requests for data that are not showing right now, e.g. if the "description" tab is opened, it makes not only /description request, but also /finance and /dividends

**To Reproduce \ Как воспроизвести **

  1. Open Info widget.
  2. Loot at browser's network tab

Expected behavior \ Ожидаемое поведение
Only one request per opened tab

Desktop (please complete the following information) \ ПК. Опишите среду в которой баг воспроизводится:

  • OS \ ОС: Windows 10
  • Browser \ Браузер Chrome
  • Version \ Версия 102

Additional context \ Дополнительная информация

[BUG] При выставлении заявки на SPBX не отображается комиссия сделки

Опишите баг

При выставлении заявки на MOEX в окне автоматически пересчитывается значение суммы сделки и комиссии при изменении лотов/цены.
На SPBX эти поля отсутствуют

Воспроизведение

  1. Открыть окно "Выставить заявку" по инструменту на SPBX

Среда.

  • ОС Windows 10
  • Браузер Chrome
  • Версия 100.0.4896.127

Ожидаемое поведение

При выставлении заявки на SPBX отображается стоимость покупки и комиссия.

Скриншоты

Заявки_SPBX-MOEX

Дополнительная информация

No response

Правила

  • Я согласен следовать правилам

[BUG] Кнопки расположены близко к границам виджета

Опишите баг

Около границ виджета, при наведении курсора на кнопки управления (закрыть, настройки, справка) он переходит в режим изменения размера, хотя визуально находится еще над кнопкой. Для кнопки "Закрыть" особенно актуально, нажать ее затруднительно.

Воспроизведение

  1. Создать виджет
  2. Подвести курсор на кнопку закрыть (крестик) прямо в самый центр.

Среда.

  • ОС Windows 10
  • Браузер Chrome
  • Версия 100.0.4896.127

Ожидаемое поведение

При нахождении над кнопкой управления, курсор не должен переходить в режим изменения размера виджета.

Скриншоты

No response

Дополнительная информация

No response

Правила

  • Я согласен следовать правилам

[BUG] Chart loading "animation"

Describe the bug \ Опишите баг
When I change candlestick interval on the chart from "D" to "5m" the graph is loaded with "animation".

Expected behavior \ Ожидаемое поведение
Show preloader?

[BUG] Профиль. Сообщения об ошибках при выходе.

Опишите баг

В процессе выхода из сеанса появляются сообщения об ошибках 403

Воспроизведение

  1. Нажать на иконку профиля
  2. Выбрать "Выход"

Среда.

  • ОС Windows 10
  • Браузер Chrome
  • Версия 100.0.4896.127

Ожидаемое поведение

Корректный выход из сеанса

Скриншоты

logout_403_1
logout_403_2

Дополнительная информация

No response

Правила

  • Я согласен следовать правилам

[BUG] Выставление заявки. Логотип меняет положение при изменении цены

Опишите баг

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

Воспроизведение

  1. Открыть окно "Выставить заявку" по инструменту, в цене которого меняется количество знаков дробных разрядов

Среда.

  • ОС Windows 10
  • Браузер Chrome
  • Версия 100.0.4896.127

Ожидаемое поведение

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

Скриншоты

Заявка_с_ценой

Дополнительная информация

No response

Правила

  • Я согласен следовать правилам

[BUG] After widget's settings save, you need to double click on a gear to settings reappear.

Describe the bug \ Опишите баг
After widget's settings save, you need to double click on a gear to settings reappear.

**To Reproduce \ Как воспроизвести **

  1. Create any widget
  2. Go to settings and change something
  3. Save the settings
  4. Try to click on the settings button one more time.

Expected behavior \ Ожидаемое поведение
After settings' save I want to single click, to open the settings once more.

Screenshots \ Скриншоты

Desktop (please complete the following information) \ ПК. Опишите среду в которой баг воспроизводится:

  • OS \ ОС: Windows 10
  • Browser \ Браузер Chrome
  • Version \ Версия 102

Additional context \ Дополнительная информация

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.