Участие в разработке

Это руководство по участию в разработке Plots и окружающей экосистемы. Plots представляет собой сложный и перспективный набор программных компонентов, поэтому его эффективность будет наиболее высокой, если сообщество добавит в него свой опыт, знания, виды на будущее и приложит соответствующие усилия. Документ условно разбит на следующие категории, и после прочтения этого введения вы сможете перейти к тому разделу, который вас больше всего интересует.

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

4cd1d578 f876 11e6 92dc 222b52598054

Организация JuliaPlots

JuliaPlots является центром для всего, что связано с Plots. Набор был основан Томом Брелоффом (Tom Breloff) и расширен за счет многочисленных вкладов участников сообщества и других пользователей. Первым шагом к сотрудничеству будет понимание того, какой пакет (пакеты) является подходящим местом для вашего кода.

Plots

Это основной пакет для:

Этот пакет зависит от RecipesBase, PlotUtils и PlotThemes. При участии в разработке нового функционала или возможностей следует приложить максимум усилий, чтобы найти более подходящее место (StatsPlots, PlotUtils и т. д.), чем сам пакет Plots. В целом мы стремимся уменьшить размер и объем Plots, когда это возможно, и перенести функции в другие пакеты.

Бэкенды

Код бэкенда (например, код, связывающий Plots с GR) находится в каталоге Plots/src/backends. Таким образом, совершенствования кода бэкенда должны выполняться в самом Plots. GR и Plotly — это единственные бэкенды, установленные по умолчанию. Весь остальной код бэкенда загружается условно с помощью Requires.jl в Plots/src/init.jl.

PlotDocs

PlotDocs является централизованным расположением этой документации. Создание документации осуществляется с помощью Documenter.jl.

RecipesBase

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

PlotUtils

Компоненты, которые могут быть использованы для других пакетов (отличных от Plots). Сюда можно добавлять все, что является достаточно универсальным и полезным.

  • Цвет (преобразования, создание, удобство работы)

  • Цветовые градиенты/карты

  • Вычисление делений

PlotThemes

Визуальные темы (т. е. атрибуты по умолчанию), такие как «темная», «оранжевая» и т. д.

StatsPlots

Расширение Plots: построение статистических графиков и табличные данные. Сложные гистограммы и плотности, корреляционные графики и поддержка DataFrames. Здесь должно находиться все, что связано со статистикой или специальной обработкой табличных данных.

GraphRecipes

Расширение StatsPlots: графики, карты и многое другое.


Выбор проекта

Для тех, кто только начинает работать с Plots, первым шагом должно стать чтение (и перечитывание) документации. Напишите несколько примеров кода, поэкспериментируйте с атрибутами и попробуйте использовать несколько бэкендов. Очень трудно вносить вклад в проект, которым не умеешь пользоваться.

Идеи проектов для начинающих участников

  • Создание нового шаблона. Желательно то, что вас интересует. Может быть, вам нужны пользовательские наложения тепловых карт и рассеяний? Может быть, у вас есть формат ввода, который в настоящее время не поддерживается? Создайте шаблон, чтобы можно было просто построить график (plot(thing)).

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

  • Добавление шаблонов во внешние пакеты. В зависимости от RecipesBase пакет может определить шаблон для своих пользовательских типов. Отправьте PR в интересующий вас пакет, добавив к нему шаблон. Например, см. этот PR для добавления графиков OHLC для TimeSeries.jl.

Идеи проектов для участников среднего уровня

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

  • Помощь в разработке документации. Может иметь вид улучшенных описаний, дополнительных примеров или полноценных руководств. Вносите улучшения в PlotDocs.

  • Расширение функциональности StatsPlots. qqplot, DataStreams или что-то другое, что может прийти вам на ум.

Идеи проектов для продвинутых участников

  • Переработка ColorBar. Панелям цветов требуется особое внимание. Для этого, вероятно, потребуется новый тип Colorbar, который будет связан с соответствующим объектом (объектами) Series и независим при создании макета подграфика. Мы хотим, чтобы многие ряды (возможно, из нескольких подграфиков) использовали одни и те же шаблоны и имели общую панель цветов или несколько панелей цветов, которые можно гибко позиционировать.

  • Переработка PlotSpec. Это давнее предложение по переработке могло бы обеспечить универсальную сериализацию/десериализацию данных и атрибутов Plot, а также некоторые улучшения/оптимизации при изменении графиков. Например, мы могли бы неспешно вычислять значения атрибутов и сознательно помечать их как «грязные», когда они изменяются, что позволило бы бэкендам пропускать большую часть бесполезной обработки и ненужных перестроек, которые осуществляются в настоящее время.

  • Улучшение шаблонов графиков. Здесь есть над чем поработать: привести в порядок визуальные элементы, улучшить прорисовку ребер, реализовать алгоритмы макетов и многое другое.


Основные принципы проектирования

Гибкость и универсальность — вот основные принципы, лежащие в основе разработки Plots, которые, как правило, приводят к путанице, когда пользователи акцентируют свое внимание на конкретном варианте использования.

Я, Том, кропотливо разрабатывал основную логику для поддержки практически любого варианта использования, который существует или может существовать. Я не претендую на то, что знаю, как вы хотите использовать Plots, или какой тип данных можете передать, или какой шаблон применить. Поэтому я стараюсь избегать излишнего ограничения типов, принудительных преобразований и многих других подводных камней ограниченных платформ визуализации. Результатом будет высокомодульная система, возможности которой ограничиваются только вашей фантазией.

Добавляя новые функции в Plots (или окружающую экосистему), вы также должны стремиться к такому образу мышления. Новые функции должны оставаться максимально универсальными. При этом следует избегать их явных конфликтов.

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

using Plots, StatsBase
gr(size = (300, 300), leg = false)

@userplot MyCount
@recipe function f(mc::MyCount)
    # получение массива из поля аргументов.
    arr = mc.args[1]

    T = typeof(arr)
    if T.parameters[1] == Float64
        seriestype := :histogram
        arr
    else
        seriestype := :bar
        cm = countmap(arr)
        x = sort!(collect(keys(cm)))
        y = [cm[xi] for xi ∈ x]
        x, y
    end
end

Шаблон, определенный выше, является пользовательским, который строит гистограмму для массивов Float64, а в противном случае отображает «сопоставление количества» отсортированных уникальных значений и их наблюдаемого количества. Для вас важны только Float64 и String, поэтому ваши результаты вполне приемлемы:

mycount(rand(500))

mycount(rand(["A","B","C"],100))

Но вы не учли того, что в будущем пользователи могут захотеть передавать целые числа в этот шаблон:

mycount(rand(1:500, 500))

Пользователь ожидал, что целые числа будут обрабатываться как числа и в результате будет выведена гистограмма, но вместо этого целые числа обрабатывались как строки. Простым решением была бы замена if T.parameters[1] == Float64 на if T.parameters[1] <: Number. Однако стоит ли вообще полагаться на то, что первым параметром T будет тип элемента? (Нет.) Поэтому еще лучшим вариантом будет if eltype(arr) <: Number, который теперь позволяет любому контейнеру с любым числовым типом запускать логику «гистограммы».

Этот простой пример описывает общую тему при разработке Plots (или любого другого пакета Julia). Постарайтесь создать максимально универсальную реализацию, сохранив при этом корректность. Вы не знаете, какие невероятные и странные типы будет использовать кто-то другой, пытаясь получить доступ к вашей функциональности.


Организация кода

Как правило, схожая функциональность хранится в одном файле. Внутри каталога src большая часть файлов должна быть самопоясняющей (например, в файле animation.jl вы найдете методы/макросы анимации), но некоторые из них могут быть снабжены кратким описанием содержимого.

  • Plots.jl: импорты, экспорты, сокращения и инициализация

  • args.jl: настройки по умолчанию, псевдонимы и обработка атрибутов

  • components.jl: фигуры, шрифты и другие разнообразные возможности

  • pipeline.jl: код, который строит графики и подграфики путем рекурсивного применения шаблонов

  • recipes.jl: главным образом шаблоны основных рядов

  • series.jl: обработка основных входных данных

  • utils.jl: многие функциональные возможности, у которых не было места хранения…​ getindex/setindex! для Plot/Subplot/Axis/Series, push!/append! для добавления данных в ряды, cycle/unzip и аналогичные служебные функции, Segments/SegmentsIterator и т. д.

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

Создание новых бэкендов

Моделируйте новые бэкенды в Plots/src/backends/template.jl. Реализуйте соответствующие обратные вызовы, в частности _display и _show для графического интерфейса и вывода изображений, соответственно.

Рекомендации по стилю и дизайну

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

  • Будьте осторожны, добавляя сигнатуры методов к существующим методам с типами Base (Array и т. д.), так как можно переопределить ключевую функциональность. Это особенно актуально для шаблонов. Рассмотрите возможность заключения входных данных в новый тип (как в «шаблонах пользователя»).

  • Лаконичный код, как и подробный, является нормой. Важны понимание и контекст. Поймет ли кто-нибудь, читающий ваш код, что вы имеете в виду? Если нет, подумайте о том, чтобы написать комментарии с описанием причин, побудивших вас к кодированию именно таким образом. Или четко опишите только что реализованный прием. Иногда это нормально, что комментарии длиннее кода.

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


Git-fu (или…​ механизм участия в разработке)

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

Рекомендации

Ниже приведены некоторые рекомендации по организации рабочего процесса разработки (примечание: даже если в прошлом вы сделали 20 запросов на вытягивание в Plots, прочитайте эту статью, поскольку она может отличаться от предыдущих рекомендаций).

  • Выполняйте фиксации в ветвь, которая принадлежит вам. Как правило, это означает, что вы должны давать своим ветвям уникальные имена, которые могут содержать информацию о разрабатываемой вами функции. Например, в начале работы над шрифтами я могу выбрать git checkout -b tb-fonts.

  • Открывайте запрос на вытягивание в ветви master. master — это очень актуально. (Примечание: раньше я рекомендовал создание PR в dev.)

  • Объединяйте другие изменения только в случае крайней необходимости. Вместо git rebase origin/master используйте git merge origin/master. При перемещении изменений из одной ветви в другую последние фиксации накладываются поверх самых последних master, что позволяет избежать сложных и беспорядочных фиксаций слияния и вообще избежать путаницы. Если вы следуете первому правилу, скорее всего, у вас не возникнет проблем. Неприятные моменты, связанные с перемещением изменений, обычно возникают, когда в одной ветви работает много людей. Я считаю, что этот ресурс отлично подходит для понимания важных частей git rebase.


Рабочий процесс разработки

Далее приводятся мои предложения по организации эффективного процесса разработки.

Создание вилки репозитория

Перейдите на сайт репозитория (https://github.com/JuliaPlots/Plots.jl) и нажмите кнопку Fork (Создать вилку). Вам может быть предложено выбрать, в какой учетной записи или в какой организации разместить вилку. В дальнейшем я буду считать, что вы создали вилку в Github под именем пользователя user123.

Настройка удаленного git

Перейдите в локальный репозиторий. Примечание. Я предполагаю, что вы ведете разработку в своем каталоге Julia и используете Mac или Linux. Скорректируйте при необходимости.

cd ~/.julia/v0.5/Plots
git remote add forked git@github.com:user123/Plots.jl.git

После выполнения этих команд git remote -v должен отобразить два удаленных расположения: origin (основной репозиторий) и forked (ваша вилка). Удаленное расположение — это просто ссылка/указатель на сайт Github, на котором размещен репозиторий, а вилка — это любой другой репозиторий git со специальной ссылкой на исходный репозиторий.

Создание новой ветви

Если вы только начинаете работу над новой функцией:

git fetch origin
git checkout master
git merge --ff-only origin/master
git checkout -b user123-myfeature
git push -u forked user123-myfeature

Первые три строки позволяют убедиться, что вы начинаете с ветви master основного репозитория. Флаг --ff-only гарантирует, что вы будете переходить только к более новым фиксациям, и позволяет избежать создания новой фиксации слияния, когда этого не требовалось. Строка git checkout как создает новую ветвь (-b), указывающую на текущую фиксацию, так и делает эту ветвь текущей. Строка git push добавляет эту ветвь в вашу вилку Github и настраивает локальную ветвь для отслеживания (-u) в удаленной ветви последующих вызовов git push и git pull.

или…​ повторное использование старой ветви

Если у вас есть текущая ветвь разработки (например, user123-dev), которую вы предпочитаете использовать (и которая ранее была объединена с master!), вы можете обновить ее следующим образом:

git fetch origin
git checkout user123-dev
git merge --ff-only origin/master
git push forked user123-dev

Мы обновляем локальную копию исходника, проверяем ветвь разработки, а затем пытаемся перейти к текущей ветви master. Если все прошло успешно, мы отправляем ветвь обратно в вилку репозитория.

Написание кода и форматирование изменений

Откройте свой любимый редактор (возможно, Juno?) и внесите изменения кода в репозиторий.

Отформатируйте изменения (согласованность стиля кода) следующим образом.

$ julia -e 'using JuliaFormatter; format(["src", "test"])'

Фиксации

После внесения изменений необходимо «зафиксировать» или сохранить снимок всех сделанных изменений. После фиксации вы можете отправить эти изменения в свою вилку репозитория на Github:

git add src/my_new_file.jl
git commit -am "my commit message"
git push forked user123-dev

Первая строка является необязательной и используется при добавлении новых файлов в репозиторий. -a означает «зафиксировать все мои изменения», а -m позволяет написать примечание о фиксации (это нужно делать всегда и, надеюсь, сделать его описательным).

Отправка запроса на вытягивание

Вы почти у цели! Перейдите к своей вилке (https://github.com/user123/Plots.jl). Скорее всего, чуть выше кода будет раздел с вопросом о том, хотите ли вы создать запрос на вытягивание из ветви user123-dev. Если нет, то можно нажать кнопку New pull request (Новый запрос на вытягивание).

Убедитесь, что «базовой» ветвью является JuliaPlots master, а ветвью «сравнения» — user123-dev. Добавьте информативное название и описание, а также ссылку на соответствующие проблемы или обсуждения, затем нажмите кнопку Create pull request (Создать запрос на вытягивание). Вам может быть задано несколько вопросов по этому поводу и, возможно, приведены предложения о том, как исправить его, чтобы он был «готов к слиянию». Затем, надеюсь, будет выполнено объединение. Спасибо за вклад!

Очистка

После всего этого вы, скорее всего, захотите вернуться к использованию master (или, возможно, к использованию помеченного тегами выпуска, когда ваша функция будет помечена тегами). Чтобы выполнить очистку, сделайте следующее.

git fetch origin
git checkout master
git merge --ff-only origin/master
git branch -d user123-dev

При этом локальная ветвь master переносится в удаленную ветвь master, а затем удаляется ветвь разработки. Чтобы вернуться к помеченным тегами выпускам, выполните Pkg.free("Plots") из REPL Julia.


Теги

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

Создавать теги могут только участники JuliaPlots. Чтобы создать тег, создадим выпуск на Github и с помощью attobot сгенерируем запрос на вытягивание в METADATA. Создайте выпуск по адресу https://github.com/JuliaPlots/Plots.jl/releases/new (разумеется, заменив имя репозитория именем помечаемого пакета).

Номер версии (vMAJOR.MINOR.PATCH) должен увеличиваться с помощью semver, что в общем случае означает, что критические изменения должны увеличивать основной номер, обратно совместимые изменения — дополнительный номер, а исправления ошибок — номер исправления. Для версий v0.x.y это требование снижено. Дополнительный номер версии может быть увеличен для критических обновлений.


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

VisualRegressionTests

Тестирование в Plots осуществляется с помощью VisualRegressionTests. Эталонные изображения хранятся в PlotReferenceImages. Иногда эталонные изображения нуждаются в обновлении (при изменении функций или базового бэкенда). В VisualRegressionTests обновление эталонных изображений выполняется достаточно легко.

Из REPL Julia выполните Pkg.test(name="Plots"). Будет предпринята попытка построения тестов, а затем результаты сравниваются с хранимыми эталонными изображениями. Если выходные данные теста достаточно сильно отличаются от эталонных (для сравнения используется отличный алгоритм Тима Холи (Tim Holy)), появится GTK-окно с наглядным параллельным сравнением. В зависимости от того, была ли обнаружена реальная ошибка, можно заменить эталонное изображение или оставить все как есть.

После обновления эталонных изображений перейдите в PlotReferenceImages и отправьте изменения на Github:

cd ~/.julia/v0.5/PlotReferenceImages
git add Plots/*
git commit -am "a useful message"
git push

При наличии несоответствий, вызванных ошибками, не обновляйте эталонное изображение.

Непрерывная интеграция

Если указано git push, тесты будут запускаться автоматически в рамках системы непрерывной интеграции. При этом выполняются те же тесты, что и выше, со скачиванием и сравнением с эталонными изображениями, но с большим допуском к различиям. Ошибки могут быть вызваны временем ожидания, устаревшими эталонными изображениями или другими причинами. Проверьте журналы, чтобы определить причину. Если тесты не выполняются из-за новой фиксации, рассмотрите возможность отката.