Рязанов Илья, ryasanov@gmail.com
Совсем недавно появилась новость, что компания Digia открывает исходный код своей проприетарной библиотеки QtCharts (QtCharts). Библиотека QtCharts, является дополнением к Qt и представляет, мощное средство для рисования всяких красивых графиков. Теперь последняя версия этой замечательной библиотеки, доступна под лицензией GPL. Компания Digia обещает включить QtCharts в следующий релиз Qt. Пока она доступна в виде исходных текстов.
В этой статье мы соберем эту библиотеку из исходных кодов и напишем простой виджет для рисования осциллограмм с нашего прибора LESO4.
Сборку библиотеки, я буду делать в Windows, хотя для Linux она практически ничем не отличается.
На компьютер нужно будет установить:
Собирать библиотеку будем в директории где установлен Qt. У меня это C:\Qt.
И так, запускаем консоль сборки Qt (Пуск -> Все программы -> Qt -> 5.4 -> MinGW 4.9(32-bit) -> Qt 5.4 for Desktop MinGW 4.9(32-bit)) и перейдем в директорию где соберем библиотеку:
Получаем исходный код библиотеки с github и переходим в директорию с исходниками:
git clone https://github.com/qtproject/qtcharts.git
Чтобы git был доступен из консоли сборки Qt в винде, его следует добавить в системную переменную PATH.
Теперь можно приступить к сборке библиотеки:
qmake CONFIG+=release
mingw32-make
В процессе сборки запустятся скрипты perl, которые сгенерируют нужные файлы mkspec, чтобы библиотеку подключать к проекту как модуль Qt.
Компиляция может занять определенное время, так что можно пойти выпить чашку кофе.
После того как процесс завершен можно приступить к написанию нашего виджета.
Запускаем QtCreator и создаем новый проект (Файл -> Создать файл или проект и в появившемся меню Приложение -> Приложение Qt Widgets).
В следующем окне задаем название и местоположение проекта, например я назвал проект LESO4Plot:
Выбираем все доступные комплекты:
Класс я назвал LESOPlotWidget, а базовый класс выбрал QWidget. Также не забываем снять галочку - Создать форму:
Теперь для подключения к проекту, библиотеки QtCharts, в файле проекта LESO4PlotWidget.pro нужно добавить следующие строки
include(C:\Qt\qtcharts\mkspecs\modules\qt_lib_charts.pri)
Qt += charts
В файле qt_lib_charts.pri находятся определения модуля Qt с названием charts. Модуль добавляется к проекту, Qt += charts в файле .pro
Для взаимодействием с устройством LESO4 понадобится библиотека libLESO4, ее тоже необходимо собрать и подключить к проекту. В консоле Qt переходим в директорию проекта и клонируем репозиторий с исходный кодом библиотеки:
git clone https://iRyaz@bitbucket.org/iRyaz/leso4_api.git
Теперь компилим библиотеку:
cd leso4_api
mingw32-make
В файл LESO4PlotWidget.pro добавляем путь к заголовочному файлу leso4.h и файлу собранной библиотеки libLESO4.dll:
INCLUDEPATH += ./leso4_api/
LIBS += -L./leso4_api/ -lLESO4
Одна тонкость, чтобы линковщик нашел libLESO4.dll, по заданному пути, нужно в QtCreator отключить теневую сборку, снять галочку в настройках
Окей проект настроили, теперь можно приступить к кодингу.
Виджет работает следующим образом.
В конструкторе виджет, открывается и настраивается устройтство LESO4 с помощью функций библиотеки libLESO4. Если устройство не получилось открыть, то выводится сообщение об ошибки, и программа завершается. Если все хорошо, то инициализируется основной компонент QChartView, для отображения графиков. Затем сигнал программного таймера QTimer связывается со слотом readDataDevice() и запускается. Это значит, что когда таймер досчитывает до определенного интервала, будет вызываться функция readDataDevice(). Сигналы и слоты - относяться чисто к Qt, о них можно почитать в официальной документации. В функции readDataDevice() данные считываются с устройства и выводятся на график.
Настройка устройства производится в конструкторе виджета. Первым шагом открывается устройство, и если устройство не открылось, то выводится сообщение об ошибки и программа закрывается. Если устройство успешно открылось, то устанавливается количество точек, частота дискретизации, включаются все каналы и на каналах устанавливается максимальное напряжение. Ниже в листинге показан отрывок из файла lesoplotwidget.cpp
if(leso4Open(DEVICE_DESCRIPTOR) != LESO4_OK) // Открытие устройства
DEVICE_NOT_CONNECT_MESSAGE // Если устройство не удалось открыть, то выдаем сообщение и завершаем программу
leso4SetSamplesNum(SAMPLE_NUM); // Количество точек для одного канала
leso4SetSamplFreq(sampe_frequency_10MHz); // Частота дискретизации 10 МГц
leso4EnableChannel(CHANNEL_A); // Включить канал A
leso4SetAmplitudeScan(CHANNEL_A, max_voltage_20V); // Максимальная амплитуда для канала A 20 В
leso4EnableChannel(CHANNEL_B); // Включить канал B
leso4SetAmplitudeScan(CHANNEL_B, max_voltage_20V); // Максимальная амплитуда для канала B 20 В
leso4EnableChannel(CHANNEL_C); // Включить канал C
leso4SetAmplitudeScan(CHANNEL_C, max_voltage_20V); // Максимальная амплитуда для канала C 20 В
leso4EnableChannel(CHANNEL_D); // Включить канал D
leso4SetAmplitudeScan(CHANNEL_D, max_voltage_20V); // Максимальная амплитуда для канала D 20 В
Рисование графика в библиотеке QtCharts приведено в документации и сводится к заполнению некоторого класса производного от QXYSeries, координатами точек и передачей указателя функции addSeries класса QChart. Ниже приведен пример как построить график из двух точек
QLineSeries* series = new QLineSeries(); // Создать класс QLineSeries производный от QXYSeries
series->add(0, 6); // Добавить точку с координатами X = 0, Y = 6
series->add(2, 4); // Добавить точку с координатами X = 2, Y = 4
chartView->chart()->addSeries(series); // Передать указатель на функции addSeries класса QChart
chartView->chart()->createDefaultAxes(); // Автоматический создать оси на графике
Заметим, что функции addSeries передается только указатель на структуру в которой хранятся координаты точек, поэтому его можно передать только один раз, и дальше обновлять саму структуру Series, а отображение виджета будет обновляться автоматически.
Функция createDefaultAxes() создает на графике оси и автоматически задает их диапазон в зависимости от точек на графике.
Помимо QLineSeries в библиотеке есть еще и другие производные классы для предназначенные для разных графиков и имеющие разные свойства. Например QSplineSeries при добавлении новых точек автоматически производится сплайн интерполяция на графике. В своем виджете я использую QLineSeries, ибо он не использует интерполяци и отрисовка графика происходит быстрее, использование QSplineSeries сильно тормозило бы программу.
Виджет будет показывать осциллограммы для четырех каналов. Из объявления в заголовочном файле lesoplotwidget.h
QValueAxis *xAxis; // Ось X
QValueAxis *yAxis; // Ось Y
QLineSeries *diagramA; // Точки для осциллограммы канала A
QLineSeries *diagramB; // Точки для осциллограммы канала B
QLineSeries *diagramC; // Точки для осциллограммы канала C
QLineSeries *diagramD; // Точки для осциллограммы канала D
Эти указатели инициализируются в конструкторе нашего виджета в файле lesoplotwidget.cpp.
Ниже в листинге показана инициализация для канала А, для всех остальных каналов все точно также
xAxis = new QValueAxis; // Ось X
xAxis->setRange(0, SAMPLE_NUM*TIME_STEP); // Диапазон от 0 до времени которое соответстует SAMPLE_NUM точек
xAxis->setTitleText(tr("Time")); // Название оси X
xAxis->setTitleBrush(Qt::magenta); // Цвет названия
xAxis->setLabelsColor(Qt::magenta); // Цвет элементов оси
yAxis = new QValueAxis; // Ось Y
yAxis->setRange(-20, 20); // Диапазон от -20 до +20 Вольт
yAxis->setTitleText(tr("Amplitude")); // Название оси Y
yAxis->setTitleBrush(Qt::yellow); // Цвет названия
yAxis->setLabelsColor(Qt::yellow); // Цвет элементов оси
channelAPen.setWidthF(PEN_WIDTH); // Установить ширину линии осциллограммы для канала A
channelAPen.setBrush(Qt::yellow); // Цвет осциллограммы канала А
diagramA = new QLineSeries;
diagramA->setName(tr("Channel A")); // Имя Осциллограммы
diagramA->setPen(channelAPen);
diagramA->setUseOpenGL(true); // Включить поддержку OpenGL
view->chart()->addSeries(diagramA); // Добавить осциллограмму на отображение
view->chart()->setAxisX(xAxis, diagramA); // Назначить ось xAxis, осью X для diagramA
view->chart()->setAxisY(yAxis, diagramA); // Назначить ось yAxis, осью Y для diagramA
Чтение и обработка данных с устройства задается макросом DEVICE_TIMEOUT в милисекундах. Через этот промежуток времени вызывается функция readDataDevice()
В функции readDataDevice(), данные считываются с устройства и проверяется, было ли соединение с устройством. Затем производится удаление всех точек в структуре QLineSerial каждого канала. Затем QLineSerial заполняется новыми точками на основе отсчетов сигнала и осциллограмма на экране обновляется.
void LESOPlotWidget::readDataDevice()
{
leso4ReadFIFO(); // Чтение отсчетов с устройства
if(!leso4IsOpen()) // Есть ли связь с устройством?
DEVICE_NOT_CONNECT_MESSAGE
diagramA->clear(); // Удаление точек на осциллограмме канала A
diagramB->clear(); // Удаление точек на осциллограмме канала B
diagramC->clear(); // Удаление точек на осциллограмме канала C
diagramD->clear(); // Удаление точек на осциллограмме канала D
double *dataA = (double*)leso4GetData(NULL, CHANNEL_A); // Получение указателя на массив отсчетов с канала A
double *dataB = (double*)leso4GetData(NULL, CHANNEL_B); // Получение указателя на массив отсчетов с канала B
double *dataC = (double*)leso4GetData(NULL, CHANNEL_C); // Получение указателя на массив отсчетов с канала C
double *dataD = (double*)leso4GetData(NULL, CHANNEL_D); // Получение указателя на массив отсчетов с канала D
paintGraph(dataA, diagramA); // Заполнение структуры QLineSerial для канала A отсчетами
paintGraph(dataB, diagramB); // Заполнение структуры QLineSerial для канала B отсчетами
paintGraph(dataC, diagramC); // Заполнение структуры QLineSerial для канала C отсчетами
paintGraph(dataD, diagramD); // Заполнение структуры QLineSerial для канала D отсчетами
}
Функция paintGraph производит заполнение точками переданной структуры QLineSerial. Принимает указатель на буфер отсчетов и структуру QLineSerial
void LESOPlotWidget::paintGraph(double *samplesPtr, QLineSeries *series)
{
for(int i(0); i < SAMPLE_NUM; i++)
series->append(i*TIME_STEP, samplesPtr[i]);
}
Макросом TIME_STEP задается шаг дискретизации по времени. Он обратно пропорционален частоте дискретизации.
#define TIME_STEP 1.0f/10000000.0f // Период дискретизации
Из функции видно, что координата точки графика по X - это шаг дискретизации умноженный на номер отсчета, а координата по Y это сам отсчет считанный с устройства.
Вот в общем-то основные моменты. Ниже привоже полные листинги файлов lesoplotwidget.h и lesoplotwidget.h
В итоге должен получиться вот такой виджет. (На канал B прибора LESO4, подается меандр с генератора сигналов)
Обновление графика происходит непрерывно, что сильно грузит систему. Если на виджет добавить еще и другие элементы управления, например ползунок регулирования частоты дискретизации, то графический интерфейс будет очень медленно работать. Более правильно было сделать обновления графика не по таймеру, а в отдельном потоке. Этот виджет написан только, чтобы разобраться с работой с библиотекой QtCharts, а также для демонстрации работы прибора.
С помощью библиотеки QtCharts можно строить и другие типы графиков, в статье описана лишь малая ее часть. Также в библиотеку можно применять вместе с QML. Об этом хорошо написано в официальной документации.
Комментарии:
пт, 02/01/2019 - 15:14
Постоянная ссылка (Permalink)
Спасибо!!!