Алексей Балакин (
профиль на ЛОРе) давно уже разрабатывает
проект MathGL, предназначенный для построения разнообразных графиков: как простых, так и очень сложных. Недавно он выпустил
вторую версию этой библиотеки.
Библиотека предназначена для работы с разными ЯП, но для меня прежде всего интересен порт ее в Octave (чтобы можно было "по-быстрому нашлепать" красивых графиков для отчетов/презентаций и т.п.). О нем я и расскажу.
В репозитории моего арча самый свежий mathgl имеет версию 1.11, поэтому версию 2.0 я скачал с
оф. сайта проекта и, не парясь со всякими makepkg, просто собрал его и установил.
Внимание! По умолчанию поддержки Octave в mathgl нет, поэтому cmake надо запускать с ключом -Denable-octave=1, или же не париться и указать ключ -Denable-all=1 (однако, в этом случае должны быть установлены все прочие зависимости). У меня всех зависимостей не было (да и не нужны мне все опции), а писать уйму -D… мне было лень, поэтому я просто подправил файл CMakeLists.txt:
option(enable-double "Enable double precision in MathGL library")
option(enable-all "Enable all features")
option(enable-langall "Enable all language interfaces")
option(enable-lgpl "Enable only LGPL part of MathGL" OFF)
option(enable-ltdl "Enable loading modules support" ON)
option(enable-pthread "Enable POSIX threads support" ON)
option(enable-gsl "Enable gsl support" ON)
option(enable-jpeg "Enable jpeg support" ON)
#option(enable-u3d "Enable u3d support")
option(enable-pdf "Enable pdf support")
option(enable-gif "Enable gif support" ON)
option(enable-hdf4 "Enable hdf4 support")
option(enable-hdf5 "Enable hdf5 1.6 support")
option(enable-hdf5_18 "Enable hdf5 1.8 support")
option(enable-opengl "Enable OpenGL support" ON)
option(enable-glut "Enable glut support" ON)
option(enable-fltk "Enable fltk widget")
option(enable-wx "Enable wxWidget widget")
option(enable-qt "Enable Qt4 widget")
option(enable-python "Enable python interface")
option(enable-octave "Enable octave interface" ON)
option(enable-doc "Enable documentation building")
Далее запускаем сборку и установку
cmake .
make
su -c "make install"
Компилируется библиотека довольно-таки долго.
Одним только make install'ом мы не отделаемся: устанавливаются только файлы библиотеки, а порт для октавы надо поставить вручную. Первый вариант - в директории с проектом от имени рута запустить octave, а в нем (как написано в мануалах) - дать команду
pkg install lang/mathgl.tar.gz
Да, устанавливается библиотека по умолчанию черт знает куда в совершенно "левую" директорию: /usr/local/. Поэтому для нормальной работы с ней нужно установить в ~/.bashrc или ~/.bash_profile переменную
export LD_LIBRARY_PATH="/usr/local/lib"
(это же надо сделать руту перед установкой порта для октавы), а для разработки с подключением этой библиотеки придется написать свой конфигурационный файл для pkg-config'а (иначе надо будет вручную все эти нестандартные директории указывать).
Второй вариант установки порта для октавы - вручную распаковать файл lang/mathgl.tar.gz и поместить его содержимое в общее дерево октавовских пакетов.
После установки пакета запустим octave. Команда whos покажет нам, что у нас появилась уйма переменных, имена которых начинаются с mgl. Удалять их командой clear (или модифицировать) нельзя - лишимся mathgl'я.
Главной переменной является структура mathgl, без которой у нас ни один скрипт не заработает и которую мы вынуждены будем передавать в каждый скрипт, использующий MathGL, как параметр.
По идее, mathGl позволяет работать напрямую с векторами данных, однако, в порте для Octave это не так. Ну, а т.к. графики рисуются с использованием внутренних структур mathGL, прежде всего нам стоит написать скрипт, который наши матрицы будет конвертировать в матрицы mathGL'я:
function D = newMglData(m, X)
%
% создаем структуру mglData размером, равным размеру матрицы X,
% и заполняем ее данными
% X может иметь размерность от 1 до 3
%
% m - объект mathgl
%
[ny nx nz] = size(X);
D = m.mglData(nx, ny, nz);
for zz = 1:nz
for xx = 1:nx
for yy = 1:ny
D.SetVal(X(yy, xx, zz), xx-1, yy-1, zz-1);
endfor
endfor
endfor
X(1) = 1;
endfunction
Этот скриптик принимает одно, двух- или трехмерную матрицу X и по ее размеру заполняет структуру mglData, которую и возвращает.
Для построения простейших одномерных графиков (правда, здесь и mathGL не нужен - гнуплота за глаза хватит) можно использовать вот такую функцию:
function plot_MGL1D(X, Y, m, Text, name)
% рисуем график при помощи mathGL, если name != null - сохраняем
% X, Y - массивы координат точек графика
% m - структура mathgl, переданная пользователем (без нее не работает)
% Text - необязательное поле - структура вида
% Text.Title - заголовок графика
% Text.xLabel - подпись оси X
% Text.yLabel - подпись оси Y
% ... что-нибудь еще можно будет воткнуть
if(size(X) != size(Y) || size(X,1) != 1 || size(Y,1) != 1)
printf("Error! X and Y have different or bad sizes!\n");
return;
endif
L = size(X, 2);
g = m.mglGraph();
x = m.mglData(L);
y = m.mglData(L);
for i = 1:L
x.SetVal(X(i), i-1);
y.SetVal(Y(i), i-1);
endfor
Title = "Sample plot";
yLabel = "axis Y"; xLabel = "axis X";
if(nargin > 3)
if(isfield(Text, 'Title')) Title = Text.Title; endif
if(isfield(Text, 'xLabel')) xLabel = Text.xLabel; endif
if(isfield(Text, 'yLabel')) yLabel = Text.yLabel; endif
endif
g.Title(Title);
g.Label('y', yLabel, 0);
g.Label('x', xLabel, 0);
Xr = m.mglPoint(); Yr = m.mglPoint();
m.mglPoint_x_set(Xr, min(X)); m.mglPoint_y_set(Xr, min(Y));
m.mglPoint_x_set(Yr, max(X)); m.mglPoint_y_set(Yr, max(Y));
g.SetRanges(Xr, Yr);
g.Axis();
g.Box();
g.Plot(x, y);
g.ShowImage('feh');
if(nargin == 5)
g.WritePNG(name);
endif
endfunction
Нарисуем простенький график:
X = log([1:100]);
Y = cos(X);
plot_MGL1D(X,Y, mathgl, struct('Title', "Sample MathGL graph", 'xLabel', "X axis", 'yLabel', "yAxis"), "/tmp/sample.png")
На экране отобразится наш график, а после того, как мы закроем его (функция ShowImage "подвисает", пока график отображается на экране), сохранит вот такой png-файл:
В документации mathGL полным-полно примеров. Для 1D-графиков приведу только лишь еще один (т.к. он используется довольно часто): построение графиков с ошибками.
Итак, открываем
пример и строим два варианта графиков: с отметкой ошибок по Y отрезком и с отметкой доверительных интервалов прямоугольником.
Первый график:
x0 = newMglData(mathgl, [1:10]);
y0 = newMglData(mathgl, log([1:10])+rand(1,10)*0.3);
e0 = newMglData(mathgl, rand(1,10)*0.3+0.1);
x = newMglData(mathgl, [1:0.1:10]);
y = newMglData(mathgl, log([1:0.1:10]));
gr = mglGraph();
gr.Title("Error plot (default)");
gr.Label('x', 'X',0); gr.Label('y', 'Y = \ln x', 0);
gr.SetRanges(1,10,0,3);
gr.Axis();
gr.Box();
gr.Plot(x,y);
gr.Error(x0,y0,e0,"ko");
gr.ShowImage('feh');
И второй график (структуры x, y, x0, y0, e0 используем из предыдущего):
ex0 = newMglData(mathgl, rand(1,10)*0.15+0.05);
gr.Clf();
gr.Title("'\\@' style");
gr.Label('x', 'X',0); gr.Label('y', 'Y = \ln x', 0);
gr.SetRanges(0,11,-0.5,3);
gr.Axis();
gr.Plot(x,y);
gr.Error(x0,y0,ex0,e0,"@","alpha 0.5");
gr.ShowImage('feh');
Итак, примеры из документации легко реализовать в Octave при помощи макроса-посредника, формирующего структуры данных.
Теперь перейдем к
примерам двумерных графиков.
Подчистим ненужные данные и подготовим массивы для построения 2D-поверхностей:
clear x0 y0 ex0 e0 x y;
[X Y] = meshgrid([-5:5], [-5:5]);
a = newMglData(mathgl , sin(0.1*pi*X).*cos(0.2*pi*Y)+0.4*cos(0.2*pi*X.*Y));
b = newMglData(mathgl , cos(0.1*pi*X).*sin(0.2*pi*Y)+0.4*sin(0.2*pi*X.*Y));
v = newMglData(mathgl, [-1.5:0.2:1.5]);
И начинаем строить графики. Начнем с
простой поверхности:
gr.Clf();
gr.Title("Surf plot (default)");
gr.Light(true);
gr.Rotate(50,60);
gr.Box(); gr.Surf(a);
gr.ShowImage('feh');
Теперь построим контурную карту поверхности (если не писать gr.Surf(a);, будут отображены только изолинии):
gr.Clf();
gr.SetSize(1000,700);
gr.Title("manual levels");
gr.Rotate(50,60); gr.Box();
gr.Surf(a);
gr.Cont(v,a);
gr.ShowImage('feh');
Вариантов поверхностей довольно много. Вот еще один, в котором прозрачность изменяется, исходя из пользовательской матрицы:
clear gr
gr = mglGraph();
gr.Alpha(true);
gr.Light(true);
gr.Title("Manual alpha");
gr.Rotate(50,60); gr.Box();
gr.SurfA(a,b);
gr.ShowImage('feh');
Я не рассказал еще о 3D-графиках и векторных полях, но это оставлю на потом.