Однажды мне нужно было отрисовать пару графиков в консольной программе, написанной на
С++. Можно было решить эту проблему двумя способами:
- Сохранить график в файле и нарисовать его в экселе или другой софтине, м.б. даже в онлайн рисовалке
- Рисовать график прямиком из программы
Первый способ мне не подходил, так как я проводил тестирование алгоритмов, и лишней возней с копированием данных заниматься не хотелось. Второй способ имеет множество решений, но увы я не нашел быстрого решения, чтобы библиотека для рисования не требовала целую кучу зависимостей. Обычно библиотеки для рисования из
С++ программы хотят
OpenCV или питон с матлабом. Еще как вариант я знаю
SFML и
ImGUI. Вопрос — нафига столько всего нужно для обычного графика, если по сути нужен OpenGL и все. Решил исправить эту проблему и набросал
header-only С++ библиотеку, которая работает в отдельном потоке и способна рисовать графики зависимостей X от Y и тепловые карты. Из зависимостей библиотека требует FreeGLUT.
Как установить
Если использовать Code::Blocks и mingw, то нужно подключить библиотеку
freeglut или
freeglut_static (во втором случае нужно также установить макрос FREEGLUT_STATIC), а также
opengl32,
winmm,
gdi32. Подключить в проекте заголовочный файл
include/easy_plot.hpp, указать С++11 или выше.
Пример того, что может рисовать эта библиотека:
Пример кода
#include "easy_plot.hpp"
int main(int argc, char* argv[]) {
easy_plot::init(&argc, argv);
std::vector<double> x = {0,1,0,1,2,0,1};
easy_plot::plot("X", x);
// ставим красный цвет линии
std::vector<double> y = {0,2,3,4,2,0,1};
easy_plot::plot("Y", y, easy_plot::LineSpec(1,0,0));
std::vector<double> x2 = {0,2,6,7,8,10,12};
easy_plot::plot("XY", x2, y, easy_plot::LineSpec(1,1,0));
easy_plot::WindowSpec wstyle; // тут можно настроить стиль графика (цвет фона и пр.)
// выводим три графика в одном
easy_plot::plot<double>("Y1Y2Y3", wstyle, 3, x, easy_plot::LineSpec(1,0,0), x2, easy_plot::LineSpec(1,0,1), y, easy_plot::LineSpec(0,1,0));
while(true) {
std::this_thread::yield();
}
return 0;
}
Рисование тепловой карты
#include "easy_plot.hpp"
int main(int argc, char* argv[]) {
easy_plot::init(&argc, argv);
easy_plot::WindowSpec image_wstyle;
image_wstyle.is_grid = true;
image_wstyle.height = 320;
image_wstyle.width = 320;
float image_data[32][32] = {};
size_t image_ind = 0;
for(size_t x = 0 ; x < 32; ++x) {
for(size_t y = 0; y < 32; ++y, ++image_ind) {
image_data[x][y] = 1024 - std::sqrt((x - 18) * (x - 18) + (y - 18) * (y - 18));
}
}
image_wstyle.is_color_heatmap = true;
easy_plot::draw_heatmap("image_heatmap", image_wstyle, &image_data[0][0], 32, 32);
while(true) {
std::this_thread::yield();
}
return 0;
}
Особенности библиотеки:
- Функция plot может принимать такие параметры, как имя окна, стиль окна (различные настройки цвета и пр., см. WindowSpec), данные графиков и стиль линий.
- Функция draw_heatmap может принимать такие параметры, как имя окна, стиль окна (различные настройки цвета и пр., см. WindowSpec), данные массива тепловой карты типа float и размер тепловой карты.
- Если графиков несколько, изначально они будут расположены по всему экрану равномерно.
- Если навести курсор мыши на график, можно узнать номер линии и данные по осям X и Y.
- Рисование графиков и тепловой карты происходит в отдельном потоке.
- При повторном вызове функции с уже существующим именем окна график будет перерисован.
- Можно сохранить график
Репозиторий:
https://github.com/NewYaroslav/easy_plot_cpp
Например LineTo(Mem, x, y), ну и т.д.
Как всё сложно у С++'ников. серьёзный подход. даже сказал бы профессиональный. На пайтон такие вещи попроще делаются.
последний раз графику использовал на c++ когда учился и писал в borland C. там это было примерно также просто как и на turbo pascal/qbasic. цикл по X, вычисление координат из функции (y) с масштабированием, постановка точки через point (ну или можно и lineto, если точек недостаточно).
Gregori, C++ только для профессионалов.
К сожалению, там жесть полная, а не профессионализм.
elektroyar, первая жесть — это потенциально «размножающиеся» static-переменные в заголовочном файле, особенно mutex. Если я разобью свою программу на несколько модулей (файлов), каждый из которых включит заголовочный файл библиотеки, то у меня появится несколько mutex'ов, по одному на каждый модуль. Всё, защита от race condition сломана.
Добиться того, чтобы mutex все равно был только один, можно, вот пример с несколькими модулями, где показано, что из разных модулей в вашем коде получается разный адрес mutex'а, то есть, их там много.
А также дана правильная реализация, чтобы адрес был один на программу, то есть, чтобы mutex был единственным.
Вторая жесть — это то, что код не является exception-safe там, где это жизненно необходимо.
Вы защищаете mutex'ом изменения drawings. Перед изменением drawings соответствующий mutex lock'чится, после изменения — раз'lock'чивается назад. Как бы, всё хорошо. Но между этими двумя моментами вызываются функции push_back и make_shared, каждая из которых может выбросить исключение. Я, в коде своей программы, могу отловить исключения и продолжить работу дальше. Но, вот, только библиотечный код при этом не раз'lock'чит mutex, и при попытке повторно его за'lock'чить всё повиснет. Для избегания таких вещей в стандартной библиотеке специально имеется lock_guard, который раз'lock'чит mutex при любом развитии событий.
Третья жесть — вы вообще прогоняли свой же тест?
Он не падает где-то на 33-35 строке?
В функцию с переменным числом аргументов передаёте сами объекты, а в самой функции вычитываете их адреса. Но передали-то объекты, а не их адреса. Естественно, всё падает.
В функцию с переменным числом аргументов можно передавать только POD-типы, а вы аж вектор туда запихиваете. И LineSpec не является POD-типом. Чтобы он им стал, требуется оттуда выкинуть всё, что мешает скомпилировать его в pure C. Вам хочется использовать значения по умолчанию, но это можно сделать и с POD-типом, применив вспомогательную функцию. Вот набросок на эту тему. Значение последнего параметра берётся по умолчанию.
Вот здесь рекомендуют вместо функции с переменным числом аргументов использовать variadic templates или initializer_list. Но для этого, конечно, нужно владеть «шаблонной магией» хотя бы на среднем уровне.
Набросок решения с использованием variadic templates — здесь (общее количество пар заранее неизвестно).
Набросок решения с использованием initializer_list — здесь (общее количество пар в данном случае будет заранее известно).
В целом, остальное, навскидку, жестью не является, но очень много особенностей построения кода говорят о непонимании множества вещей.
Для написания библиотек, кстати, требуется более высокая квалификация, чем для написания обычных программ, потому что требуется учитывать множество вариантов, как пользователи библиотеки могут использовать её.
Да, в данном случае можно применить lock_guard, просто по привычке его не ставил, чаще нет смысла блокировать мьютекс на всю область видимости.
Данная функция у меня лично нормально работала, совсем недавно ее использовал. Даже сейчас решил проверить, все отлично работает.
Да, есть такой косяк. Но работает кстати в обоих случаях. Возможно дело в компиляторе mingw. Решил погуглить про особенности вариативных функций в С++. Вот что нашел: https://habr.com/ru/post/430064/
Так или иначе, для лучшей совместимости, лучше переделать.
elektroyar, здесь важно понимать механизм явления.
Mutex следует блокировать на минимально возможное время, а область видимости можно задать самому с помощью искусственного добавленного блока.
В той статье по вашей ссылке сказано, что MSVC передаёт их по ссылке, что, в данном случае, эквивалентно указателю. Возможно, mingw в этой части «мимикрирует» под MSVC. Поэтому у вас и работает.
Если передать не вектор, а его адрес, а LineSpec сделать POD-типом, поправив соответствующую обработку в самой функции, то после этого компилируется тремя компиляторами и правильно работает под Linux'ом.
Именно так, кстати, действуют профессионалы.
Unworldly, я бы не формудилировал так «для профи»- «не для профи». На менее профессиональном 1С сделали в РФ разработчики денег поболее ))
а c++ вижу смысл использовать там где нужна высокая производительность и/или низкоуровневые вещи (разработка ОС..)
И то с оговорками, например там где важна высокая надёжность(допустим embered системы самолётов и вооружения) пишутся зачастую на ada
Главное требование к языку- адекватности задачам.
++, кстати существенно java с c# подвинули за последнюю пару делителей.
Впрочем, поскольку человек пишет для себя- он вправе писать на том языке который лучше знает и который ему удобней и приятней использовать. Если ещё и на гитхаб выложил что бы с другими поделится- молодец ;-)
Деньги здесь совершенно не при чём.
Только при условии грамотного владения им.
Это как раз говорит о том, что C++ слишком сложен, вот люди и уходят на языки попроще.
Вправе-то он, вправе, но это не отменяет той жести, которая, в результате, получается.
Чтобы овладеть C++, надо потратить многие годы, а чтобы потом поддерживать уровень, необходимо постоянно им заниматься, потому что каждые три года теперь выходит новый стандарт.
Разве любитель может выделить столько времени?
Поэтому я и говорю, что С++ — только для профессионалов.
elektroyar, как показывает опыт, с C++ такой подход даёт плохие результаты.
elektroyar, совмещаю трейдинг с основной работой, где программирую, в том числе, и на С++.