Hone.jl

Добавление линейных графиков в мою библиотеку графиков

Продолжаем развивать функции, реализованные в Hone.jl, и отлаживать возникающие проблемы.

Hone.jl прошел долгий путь с момента своего скромного происхождения, и с каждым последующим обновлением, безусловно, становится намного более удобным. В прошлый раз, когда я немного поработал над Hone, я остановился на новой форме управления под названием «Рамка», в которую мы можем помещать объекты и масштабировать их соответствующим образом. Однако с этим продвижением возникла новая проблема, связанная с масштабированием всех нанесенных на график точек, а также других элементов, необходимых для нашего графика. Для этого мы можем просто просто умножить процент, который мы изначально получили от вершины X, на соответствующую ширину или высоту кадра в нашем парсере координат.

"Ноутбук"

Так, например, у нас может быть две точки на плоскости X, восемь и четыре. Поскольку восемь — это самое большое число, оно будет присвоено переменной «topx» здесь:

И когда координаты будут разобраны, наша четверка станет .5, потому что это пятьдесят процентов (половина) от восьмерки. Затем мы умножаем этот результат на нашу ширину, которая, скажем, равна 1920. Произведение 0,5 и 1920 равно 960, и в результате мы получаем точку, нанесенную посередине нашей оси x.

Довольно круто, правда?

Конечно круто, но помимо этого надо будет менять и ось X и Y. Это довольно просто, так как нам просто нужно установить точки для линий, чтобы вместо этого они соответствовали нашей ширине и высоте.

И теперь, тестируя пересмотренную функцию, мы можем получить наш первый график High-Definition Hone:

Последнее, что еще нужно исправить, это сетка. Сетка — это проблема, которая на первый взгляд кажется сложной, но оказывается довольно простой. Во-первых, нам нужно изменить верхнюю часть X и Y на правильную верхнюю часть размера кадра. Затем нам просто нужно заменить все единицы в координатах объекта Line на длину соответствующей оси.

И…

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

Новые возможности

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

Линия

Начиная с HPlot; точечные диаграммы — это круто и все такое, но Hone, очевидно, нужно будет сделать намного лучше, чтобы стать действительно отличной графической библиотекой. Одним из других наиболее важных типов непрерывных графиков являются линейные графики, которые должны быть относительно простыми в создании. Для первого шага мы начнем с создания нашей функции _arrayline(). Вот мой первоначальный черновик:

function _arrayline(x,y,axiscolor=:lightblue,
    grid=Grid(3), custom="", frame=Frame(1280,720,0mm,0mm,0mm,0mm))
    pairs = []
    for (i,w) in zip(x,y)
        append!(pairs,[i,w])
    end
    println(pairs)
    lin = Line(pairs)
    expression = string(",(context(),",lin.update(),",")
    expression = string(expression, "(context(),", axisx_tag,grid_tag,custom, axisy_tag,"),")
    tt = transfertype(expression)
    frame.add(tt)
    show() = frame.show()
    tree() = introspect(composition)
    save(name) = draw(SVG(name), composition);
    get_frame() = frame
    (var)->(show;composition;tree;save;get_frame)
end

Итак, первая ошибка, с которой я столкнулся с этой функцией, заключается в том, что добавление пар — это просто добавление двух чисел, а не NTuple двух пар. Если быть честным, я на самом деле не совсем уверен, как это изменить, но моя первая мысль заключается в том, что, возможно, я мог бы использовать толчок! функция, а не добавление! функция. Итак, с этой идеей я создал небольшой тест, в котором я помещал бы две отдельные пары в массив пар, например так:

И испытание удалось!

Однако всякий раз, когда я пробовал это в функции, я все еще получал ошибку границ.

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

Здесь мы видим, что на самом деле это тип данных словаря, который используется для создания типа данных пары. Это немного сбивает с толку, потому что я не думаю, что смогу указать тип Pair, на самом деле, я думал, что тип называется как-то по-другому, но интересно, всякий раз, когда синтаксис словаря используется с ключом и соответствующим значением он автоматически станет парой. Мы также можем просмотреть это, распечатав его.

Теперь, раскомментировав и сделав тип передачи итерируемым в методе frame.add(), мы сталкиваемся с этой ошибкой:

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

Сейчас эта функция полностью рабочая, но если бы мы ее запустили, мы бы, конечно, ничего не получили:

Вам может быть интересно:

Это почему?

Здесь может быть несколько вещей. Во-первых, линия, которую мы используем, может не обязательно быть изогнутой, и это нормально, потому что мы можем довольно легко добавить новую изогнутую линию в HDraw.jl. Во-вторых, масштабирование также важно и будет оставаться проблемой для любого графика, который мы рисуем… Так что давайте это исправим.

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

Но и это не сработало…

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

И чтобы подтвердить, что это подозрение верно, я сделал то же самое с исходным синтаксисом:

Ладно, значит, я был прав.

Итак, теперь мне нужно выяснить, как называется этот тип:

А теперь попробуем применить этот тип к паре координат.

Это сработало!

Теперь нам просто нужно применить это к нашим парам!

Но когда мы пытаемся запустить это:

Кажется, эта проблема возникает из-за метода push!(). Чтобы проверить эту теорию, я решил попробовать запустить кортеж в обычном режиме.

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

Конечно же, пары были успешно напечатаны. Однако возникает вопрос, откуда именно исходит ошибка? Возможно, виновата функция Line()?

Эврика!

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

tupearr = []
tupe = Tuple([5,10])
push!(tupearr,tupe)
lin = Line(tupearr)

Но это делает:

lin = line([5,10])

Я вижу потенциальную проблему в том, что наш массив представляет собой массив типа

Array{Any}

Когда это должно быть

Array{Tuple}

Однако всякий раз, когда мы пытаемся нажать!() или добавить!() к типу Array{Tuple}, мы получаем:

На мой взгляд, есть два решения этой проблемы.

  • Я создаю новую функцию отправки для метода append!(), которая может обрабатывать массив кортежей.
  • Я нахожу другой тип, который может содержать кортежи и уже имеет свою отправку в базе.
  • Я мог бы преобразовать тип массива в массив кортежей после добавления своих данных.

Третий вариант показался мне самым простым, поэтому я его и выбрал. Для этого я сначала передал свои данные в тип Array{Any}:

tupearr = []
tupe = Tuple([5,10])
push!(tupearr,tupe)

А затем попытался утвердить наш тип Array{Tuple} для этого типа.

Это сработало!

Теперь давайте попробуем подключить это к нашей функции Line:

Поразительнй!

Теперь нам просто нужно включить эту концепцию обратно в нашу функцию.

Наконец-то это работает!

Ничего себе, это был doozee.

Масштабирование испорчено, но это только потому, что я, кажется, случайно переключил свою математику. Скорее, чем:

x = (i * topx /frame.width)
y = (w * topy / frame.height)

Должен быть:

x = (i / topx * frame.width)
y = (w / topy * frame.height)

Это выглядит намного лучше! Чтобы добавить последние штрихи, мы просто добавляем тег нашего объекта сетки и нашу ось в микс.

Текст

Одной определяющей особенностью, которая определенно отсутствует в хоне, является возможность рисовать текст, а точнее:

этикетки.

Это должно быть относительно просто и легко сделать, вот что я придумал:

function Text(label,x,y,stroke,size)
    color = string("\"",string(stroke),"\"")
    label = string("\"",string(label),"\"")
    tag = string("text(",x, ",", y, ",", label, ",hcenter, vcenter),",
        "stroke(", color,
        "), fontsize(", size, "),")
    expression = string("compose(context(), ",tag,")")
    exp = Meta.parse(expression)
    show() = eval(exp)
    update() = tag
    (var)->(show;expression;tag;exp)
end

Вывод

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