Posted at
QtDay 20

Qtでグラフを書いてみよう!

More than 1 year has passed since last update.

こんにちは、Qt Advent Calendar 2017の20日目の記事です。

このところ、仕事が忙しく、20日を過ぎてしまい、申し訳ありません…


はじめに

Qtには、Qt ChartsがあるのですがGPLライセンスのため、今回は使用を諦め、自前でグラフ機能を実装することにしました。


グラフの描画

今回は、QWidgetをサブクラス化してグラフ機能を実装してみました。また、グラフ表示する場合、テーブルでも、表示するケースが多いので、モデル/ビュー方式にしました。その場合QAbstractItemViewをサブクラス化する方がいいのかもしれませんが、グラフ上で編集することは想定していなかったので単純なQWidgetのサブクラス化とsetModel()を実装するようにしています。

テーブルデータの0列がX軸、1列以降がY軸データになります。

    ...

m_model = new QStandardItemModel(0, 3, this);
m_model->setHeaderData(0, Qt::Horizontal, tr("X Axis"));
m_model->setHeaderData(1, Qt::Horizontal, tr("Plot 1"));
m_model->setHeaderData(2, Qt::Horizontal, tr("Plot 2"));
...


グラフを書いてみる

グラフの生成


// table
ui->tableView->setModel(m_model);

// Graph
// モデルをセットすると、column 0が、X軸/column 1.. がY軸としてPlotが生成されます。
ui->graph->setModel(m_model);

// 軸の生成
m_axis1 = new Axis(this);
m_axis2 = new Axis(this);

m_axis1->setLineColor(Qt::red);
m_axis2->setLineColor(Qt::green);

Plot* plot1 = ui->graph->plot(1); // column 1
Plot* plot2 = ui->graph->plot(2); // column 2
plot1->setLineColor(Qt::red);
plot2->setLineColor(Qt::green);
plot1->setLineWidth(1);
plot2->setLineWidth(1);

// プロットと軸の関連付け
ui->graph->setPlot(plot1, m_axis1);
ui->graph->setPlot(plot2, m_axis2);

サンプルの説明

グラフのデータによって、適切な軸のスケールが表示されます。

Screenshot from 2017-12-21 22_13_46.png

データを追加していくと、軸のスケールが変わっています。

Screenshot from 2017-12-21 22_14_14.png

試しに、テーブルデータを編集すると、グラフも変わります。

Screenshot from 2017-12-21 22_14_54.png

ソースコード


graph.h

#pragma once


#include <QMap>
#include <QPixmap>
#include <QVector>
#include <QWidget>
#include <QObject>
#include <QAbstractItemModel>

class Plot;
class Axis;
class Axes;

class Graph : public QWidget
{
Q_OBJECT

public:
Graph(QWidget *parent = 0);

void setPlot(Plot* plot, Axis* yAxis);
void setPlot(int column, Axis* yAxis);

QMap<Axis*, Plot*>& plots();
Plot* plot(int column) const { return m_plots.value(column, nullptr); }
Axis* xAxis();

// Model
void setModel(QAbstractItemModel *model);
void append(const QModelIndex &topLeft, const QModelIndex &bottomRight);

protected:
void paintEvent(QPaintEvent *event);
void resizeEvent(QResizeEvent *event);

private slots:
void onRefresh();
void onAutoScaleUpdate();
void onDataUpdate();
void onYAxesVisbleChange(bool visble);
void onXAxesVisbleChange(bool visible);

// Model
void onDataChange(const QModelIndex &topLeft, const QModelIndex &bottomRight,
const QVector<int> &roles = QVector<int>());
void onRowsInsert(const QModelIndex &parent, int first, int last);
void onRowsAboutToBeRemove(const QModelIndex &parent, int first, int last);
void onRowsRemove(const QModelIndex &parent, int first, int last);
void onResetModel();

private:
void refreshPixmap();
void drawGrid(QPainter *painter);
void drawCurves(QPainter *painter);

enum { Margin = 10,
TickMarksWidth = 5,
};

QMultiMap<Axis*, Plot*> m_plotMap;
Axes* m_axes;
QPixmap m_pixmap;
int m_visbleYAxesCount;
QRect m_rect;
QAbstractItemModel *m_model;
QMap<int, Plot*> m_plots;
};

class Plot : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(Plot)

public:
explicit Plot(int section, QAbstractItemModel *model = 0, QObject *parent = 0);
virtual ~Plot(){}

QPointF minData() const { return m_minData; }
QPointF maxData() const { return m_maxData; }

bool visble() const;
void setVisble(bool visble);

QColor lineColor() const;
void setLineColor(const QColor &lineColor);

qreal lineWidth() const;
void setLineWidth(qreal lineWidth);

int count() const { return m_model->rowCount(); }
void clear();
double yData(int index) const { return m_model->index(index, m_section).data().toDouble(); }
double xData(int index) const { return m_model->index(index, 0).data().toDouble(); }

inline int plottedPoint() const;
inline void setPlottedPoint(int plottedCount);
void clearPlottedPoint() {m_plottedCount = 0;}

bool calculateMinMaxData(const QModelIndex &yIndex, const QModelIndex &xIndex = QModelIndex());
void checkMinMaxDeleteData(const QModelIndex &yIndex, const QModelIndex &xIndex = QModelIndex());
bool recalculateMinMaxAllData(bool isForce = false);

signals:
void refreshed();
void autoScaleUpdated();
void dataUpdated();

private:
int m_section;
QAbstractItemModel *m_model;
bool m_visble;
QColor m_lineColor;
qreal m_lineWidth;
QPointF m_minData;
QPointF m_maxData;
int m_plottedCount;
bool m_isRecalculateMinMax;
};

class Axis : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(Axis)

public:
explicit Axis(QObject* parent = 0);
virtual ~Axis() {}

bool visble() const;
void setVisble(bool visble);

qreal min() const;
void setMin(qreal value);
qreal max() const;
void setMax(qreal value);
bool autoScale() const;
void setAutoScale(bool autoScale);

QColor lineColor() const;
void setLineColor(const QColor &lineColor);

enum UpdateAdjust { AutoScale, Forced };

void adjust(qreal min, qreal max,
UpdateAdjust updateAdjust = AutoScale) {
adjustAxis(min, max, updateAdjust);
}
bool autoScaleAdjustX(QList<Plot*> axes);
bool autoScaleAdjustY(QList<Plot*> axes);

int numTicks() const { return m_adjustSettings.numTicks; }
qreal span() const { return m_adjustSettings.max - m_adjustSettings.min; }

void clearMaxTickLabelWidth() { m_maxTickLabelWidth = 0; }
void setMaxTickLabelWidth(int width) {
if (m_maxTickLabelWidth < width)
m_maxTickLabelWidth = width;
}
int maxTickLabelWidth() const { return m_maxTickLabelWidth; }

signals:
void visbleChanged(bool visble);
void autoScaleChanged(bool autoScale);
void minMaxChanged();

private:
bool adjustAxis(qreal min, qreal max, UpdateAdjust updateAdjust);

struct AdjustSettings {
qreal min;
qreal max;
int numTicks;
} m_adjustSettings;

bool m_visble;
bool m_autoScale;
QString m_caption;
QColor m_lineColor;
int m_maxTickLabelWidth;
};

class Axes
{

public:
explicit Axes(QObject* parent = 0);
explicit Axes(int numAxes, QObject* parent = 0);
~Axes();

Axis* xAxis() { return m_xAxis; }
QVector<Axis* >& yAxes() { return m_yAxes; }
Axis* yAxes(int key) { return m_yAxes[key]; }

private:
Axis* m_xAxis;
QVector<Axis* > m_yAxes;
};



graph.cpp

#include <QtGui>

#if QT_VERSION >= 0x050000
#include <QtWidgets>
#endif
#include <cmath>

#include "siPrefixes.h"
#include "graph.h"

using namespace std;

Graph::Graph(QWidget *parent)
: QWidget(parent),
m_visbleYAxesCount(1)
{
setBackgroundRole(QPalette::Dark);
setAutoFillBackground(true);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

this->disconnect();

m_axes = new Axes(0, this);

connect(xAxis(), &Axis::visbleChanged, this, &Graph::onXAxesVisbleChange);
connect(xAxis(), &Axis::autoScaleChanged, this, &Graph::onAutoScaleUpdate);
connect(xAxis(), &Axis::minMaxChanged, this, &Graph::onRefresh);
}

void Graph::setPlot(Plot* plot, Axis* yAxis)
{
m_plotMap.insert(yAxis, plot);
m_axes->yAxes().append(yAxis);

// Plot
connect(plot, &Plot::refreshed, this, &Graph::onRefresh);
connect(plot, &Plot::autoScaleUpdated, this, &Graph::onAutoScaleUpdate);
connect(plot, &Plot::dataUpdated, this, &Graph::onDataUpdate);

// Y Axis
connect(yAxis, &Axis::visbleChanged, this, &Graph::onYAxesVisbleChange);
connect(yAxis, &Axis::autoScaleChanged, this, &Graph::onAutoScaleUpdate);
connect(yAxis, &Axis::minMaxChanged, this, &Graph::onRefresh);

m_visbleYAxesCount = 0;
foreach (Axis* yAxis, m_axes->yAxes()) {
if (yAxis->visble())
m_visbleYAxesCount++;
}

refreshPixmap();
}

void Graph::setPlot(int column, Axis *yAxis)
{
setPlot(plot(column), yAxis);
}

Axis *Graph::xAxis()
{
return m_axes->xAxis();
}

void Graph::paintEvent(QPaintEvent * /* event */)
{
QStylePainter painter(this);
painter.drawPixmap(0, 0, m_pixmap);

if (hasFocus()) {
QStyleOptionFocusRect option;
option.initFrom(this);
option.backgroundColor = palette().dark().color();
painter.drawPrimitive(QStyle::PE_FrameFocusRect, option);
}
}

void Graph::resizeEvent(QResizeEvent * /* event */)
{
refreshPixmap();
}

void Graph::onRefresh()
{
refreshPixmap();
}

void Graph::onAutoScaleUpdate()
{
bool updateGrid = false;
Axis* xAxis = m_axes->xAxis();
if (xAxis->autoScale()) {
if (xAxis->autoScaleAdjustX(m_plotMap.values()))
updateGrid = true;
}

foreach(Axis* yAxes, m_axes->yAxes()) {
if (yAxes->autoScale()) {
if (yAxes->autoScaleAdjustY(m_plotMap.values(yAxes)))
updateGrid = true;
}
}

if (updateGrid)
onRefresh();
else
onDataUpdate();
}

void Graph::onDataUpdate()
{
QPainter painter(&m_pixmap);
drawCurves(&painter);
update();
}

void Graph::onYAxesVisbleChange(bool visble)
{
visble ? m_visbleYAxesCount++ : m_visbleYAxesCount--;
refreshPixmap();
}

void Graph::onXAxesVisbleChange(bool /*visible*/)
{
refreshPixmap();
}

void Graph::onDataChange(const QModelIndex &topLeft, const QModelIndex &bottomRight,
const QVector<int> &/*roles*/)
{
for (int column = topLeft.column(); column <= bottomRight.column(); column++) {
if (column != 0) { // Y Axis
for (int row = topLeft.row(); row <= bottomRight.row(); row++) {
m_plots.value(column)->recalculateMinMaxAllData(true);
}
}
}

onAutoScaleUpdate();
onRefresh();
}

void Graph::onRowsInsert(const QModelIndex &parent, int first, int last)
{
bool minmaxChange = false;
bool isAppendMode = true;

QMapIterator<int, Plot*> i(m_plots);
while (i.hasNext()) {
i.next();
int column = i.key();
if (column != 0) { // Y Axis
if ((first +1) != i.value()->count())
isAppendMode = false;

for (int row = first; row <= last; row++) {
QModelIndex yIndex = m_model->index(row, column, parent);
QModelIndex xIndex = m_model->index(row, 0, parent);
minmaxChange += i.value()->calculateMinMaxData(yIndex, xIndex);
}
}
}

if (isAppendMode) {
if (minmaxChange)
onAutoScaleUpdate();
else
onDataUpdate();
}
else {
if (minmaxChange)
onAutoScaleUpdate();
onRefresh();
}

}

void Graph::onRowsAboutToBeRemove(const QModelIndex &parent, int first, int last)
{
QMapIterator<int, Plot*> i(m_plots);
while (i.hasNext()) {
i.next();
int column = i.key();
if (column != 0) { // Y Axis
for (int row = first; row <= last; row++) {
QModelIndex yIndex = m_model->index(row, column, parent);
QModelIndex xIndex = m_model->index(row, 0, parent);
i.value()->checkMinMaxDeleteData(yIndex, xIndex);
}
}
}
}

void Graph::onRowsRemove(const QModelIndex &/*parent*/, int /*first*/, int /*last*/)
{
bool minmaxChange = false;

QMapIterator<int, Plot*> i(m_plots);
while (i.hasNext()) {
i.next();
int column = i.key();
if (column != 0) { // Y Axis
minmaxChange +=i.value()->recalculateMinMaxAllData();
}
}

if (minmaxChange)
onAutoScaleUpdate();

onRefresh();
}

void Graph::onResetModel()
{
QMapIterator<int, Plot*> i(m_plots);
while (i.hasNext()) {
i.next();
i.value()->clear();
}

onAutoScaleUpdate();
onRefresh();
}

void Graph::refreshPixmap()
{
m_pixmap = QPixmap(size());
m_pixmap.fill(Qt::black);

QPainter painter(&m_pixmap);
painter.initFrom(this);
painter.setRenderHint(QPainter::Antialiasing, true);
drawGrid(&painter);
drawCurves(&painter);
update();
}

void Graph::drawGrid(QPainter *painter)
{
QFontMetrics fm(font());

QVector<QString> labels;
SiPrefixes siLabel;
int yAxisMargin = 0;
foreach (Axis* yAxis, m_axes->yAxes()) {
yAxis->clearMaxTickLabelWidth();
if (yAxis->visble()) {
for (int j = 0; j <= yAxis->numTicks(); ++j) {
double labelVal = yAxis->min() + (j * yAxis->span() / yAxis->numTicks());
siLabel.setValue(labelVal);
labels.append(siLabel.text());
yAxis->setMaxTickLabelWidth(fm.width(siLabel.text()) + TickMarksWidth +5);
}
yAxisMargin += yAxis->maxTickLabelWidth();
}
}

QRect rect(yAxisMargin + Margin,
Margin,
width() - (yAxisMargin + ( 2 * Margin)),
height() - (TickMarksWidth + (xAxis()->visble() * fm.height()) + (2 * Margin)));
m_rect = rect;

if (!rect.isValid())
return;

QPen quiteDark = QPen(palette().dark().color().light(), 1, Qt::DotLine);
QPen light = QPen(palette().light().color(), 0.4);

if (m_axes->xAxis()->visble()) {
int previousTextXEndPoint = numeric_limits<int>::min();
for (int i = 0; i <= m_axes->xAxis()->numTicks(); ++i) {
int x = rect.left() + (i * (rect.width() - 1) / m_axes->xAxis()->numTicks());
double labelVal = m_axes->xAxis()->min() + (i * m_axes->xAxis()->span() / m_axes->xAxis()->numTicks());
siLabel.setValue(labelVal);
int textWidth = fm.width(siLabel.text());
int textXPoint = x - textWidth/2;

// X Ticks
painter->setPen(light);
painter->drawLine(x, rect.top(), x, rect.bottom());
painter->drawLine(x, rect.bottom(), x, rect.bottom());

if (previousTextXEndPoint < textXPoint) { // ラベルが重なる場合は表示しない
// X Ticks
painter->drawLine(x, rect.bottom(), x, rect.bottom());
painter->drawLine(x, rect.bottom(), x, rect.bottom() + TickMarksWidth);

// X Label
painter->drawText(textXPoint,
rect.bottom() + TickMarksWidth,
textWidth,
fm.height(),
Qt::AlignTop,
siLabel.text());
previousTextXEndPoint = x + textWidth/2 + 5;
}
}
}

int yAxesNum = 0;
int yAxisLabelsOffset = 0;
int yAxisTicksOffset = 0;
foreach (const Axis* yAxis, m_axes->yAxes()) {
if (yAxis->visble()) {
int previousTextYTopPoint = numeric_limits<int>::max();
yAxisLabelsOffset += yAxis->maxTickLabelWidth();
for (int j = 0; j <= yAxis->numTicks(); ++j) {
int y = rect.bottom() - (j * (rect.height() - 1) / yAxis->numTicks());

if (yAxesNum == 0) { // first
// Grid Line
painter->setPen(light);
painter->drawLine(rect.left(), y, rect.right(), y);
}

// Ticks
QPen pen(yAxis->lineColor(), 1.0);
painter->setPen(pen);
painter->drawLine(rect.left() - yAxisTicksOffset - TickMarksWidth, y,
rect.left() - yAxisTicksOffset, y);
painter->drawLine(rect.left() - yAxisTicksOffset, rect.top(),
rect.left() - yAxisTicksOffset, rect.bottom());

// Label
painter->setPen(light);
QString label = labels.takeFirst();
if ((y + fm.height()/2) < previousTextYTopPoint) {
int textYPoint = y - fm.height()/2;
painter->drawText(rect.left() - yAxisLabelsOffset,
textYPoint,
yAxis->maxTickLabelWidth() - TickMarksWidth,
fm.height(),
Qt::AlignRight,
label);
previousTextYTopPoint = textYPoint;
}
}
yAxisTicksOffset = yAxisLabelsOffset;
yAxesNum++;
}
}
painter->drawRect(rect.adjusted(0, 0, -1, -1));

QMapIterator<Axis*, Plot*> i(m_plotMap);
while (i.hasNext()) {
i.next();
Plot* plot = i.value();
plot->clearPlottedPoint();
}

}

void Graph::drawCurves(QPainter *painter)
{
if (!m_rect.isValid())
return;

painter->setClipRect(m_rect.adjusted(+1, +1, -1, -1));

QMapIterator<Axis*, Plot*> i(m_plotMap);
while (i.hasNext()) {
i.next();

Plot* plot = i.value();
if (plot->visble()) {
Axis* yAxis = i.key();
int plotEndPoint = plot->count();
QPolygonF polyline(plotEndPoint - plot->plottedPoint());

int n = 0;
for (int j = plot->plottedPoint(); j < plotEndPoint; ++j) {
double dx = plot->xData(j) - m_axes->xAxis()->min();
double dy = plot->yData(j) - yAxis->min();
double x = m_rect.left() + (dx * (m_rect.width() - 1)
/ m_axes->xAxis()->span());
double y = m_rect.bottom() - (dy * (m_rect.height() - 1)
/ yAxis->span());
polyline[n++] = QPointF(x, y);
}
plot->setPlottedPoint(plotEndPoint-1);

QPen pen(plot->lineColor(), plot->lineWidth());
painter->setPen(pen);
painter->drawPolyline(polyline);
}
}
}

QMap<Axis*, Plot*> &Graph::plots()
{
return m_plotMap;
}

void Graph::setModel(QAbstractItemModel *model)
{
m_plots.clear();
if (model) {
int section = 1;
QVariant header;
do {
header = model->headerData(section, Qt::Horizontal);
if (header.toString().size()) {
m_plots.insert(section, new Plot(section, model, this));
}
section++;

} while(header.toString().size());
}
m_model = model;

connect(model, &QAbstractItemModel::modelReset, this, &Graph::onResetModel);
connect(model, &QAbstractItemModel::rowsInserted, this, &Graph::onRowsInsert);
connect(model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &Graph::onRowsAboutToBeRemove);
connect(model, &QAbstractItemModel::rowsRemoved, this, &Graph::onRowsRemove);
connect(model, &QAbstractItemModel::dataChanged, this, &Graph::onDataChange);
}

void Graph::append(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
bool minmaxChange = false;
for (int column = topLeft.column(); column <= bottomRight.column(); column++) {
if (column != 0) { // Y Axis
for (int row = topLeft.row(); row <= bottomRight.row(); row++) {
QModelIndex yIndex = m_model->index(row, column);
QModelIndex xIndex = m_model->index(row, 0);
minmaxChange += m_plots.value(column)->calculateMinMaxData(yIndex, xIndex);
}
}
}

if (minmaxChange)
emit m_plots.value(bottomRight.column())->autoScaleUpdated();
else
emit m_plots.value(bottomRight.column())->dataUpdated();
}

Axes::Axes(QObject* parent)
{
m_xAxis = new Axis(parent);
m_yAxes.insert(0, new Axis(parent));
}

Axes::Axes(int numAxes, QObject* parent)
{
m_xAxis = new Axis(parent);
for (int n = 0; n < numAxes; n++)
m_yAxes.append(new Axis(parent));
}

Axes::~Axes()
{
}

Axis::Axis(QObject *parent)
: QObject(parent),
m_adjustSettings({0.0, 10.0, 5}),
m_visble(true),
m_autoScale(true),
m_caption(QString()),
m_lineColor(Qt::white),
m_maxTickLabelWidth(0)
{
}

bool Axis::autoScaleAdjustX(QList<Plot *> axes)
{
qreal min = numeric_limits<qreal>::max();
qreal max = numeric_limits<qreal>::min();

foreach (Plot* plot, axes) {
if (min > plot->minData().x())
min = plot->minData().x();
if (max < plot->maxData().x())
max = plot->maxData().x();
}

return adjustAxis(min, max, AutoScale);
}

bool Axis::autoScaleAdjustY(QList<Plot *> axes)
{
qreal min = numeric_limits<qreal>::max();
qreal max = numeric_limits<qreal>::min();

foreach (Plot* plot, axes) {
if (min > plot->minData().y())
min = plot->minData().y();
if (max < plot->maxData().y())
max = plot->maxData().y();
}

return adjustAxis(min, max, AutoScale);
}

bool Axis::adjustAxis(qreal min, qreal max, UpdateAdjust updateAdjust)
{
bool updateGrid = false;
if (updateAdjust == Forced || m_autoScale) {
const int MinTicks = 4;
qreal grossStep = (max - min) / MinTicks;
qreal step = pow(10.0, floor(log10(grossStep)));

if (qIsNaN(step) || step == 0.0)
return false;

if ((5 * step) < grossStep)
step *= 5;
else if ((2 * step) < grossStep)
step *= 2;

m_adjustSettings.numTicks = int(ceil(max / step) - floor(min / step));
if (m_adjustSettings.numTicks < MinTicks)
m_adjustSettings.numTicks = MinTicks;
min = floor(min / step) * step;
max = ceil(max / step) * step;

if (m_adjustSettings.min != min ||
m_adjustSettings.max != max) {
updateGrid = true;

m_adjustSettings.min = min;
m_adjustSettings.max = max;
}
}

return updateGrid;
}

bool Axis::autoScale() const
{
return m_autoScale;
}

void Axis::setAutoScale(bool autoScale)
{
if (m_autoScale != autoScale) {
m_autoScale = autoScale;

emit autoScaleChanged(autoScale);
}
}

QColor Axis::lineColor() const
{
return m_lineColor;
}

void Axis::setLineColor(const QColor &lineColor)
{
if (m_lineColor != lineColor) {
m_lineColor = lineColor;
emit minMaxChanged();
}
}

bool Axis::visble() const
{
return m_visble;
}

void Axis::setVisble(bool visble)
{
if (m_visble != visble) {
m_visble = visble;

emit visbleChanged(visble);
}
}

qreal Axis::min() const
{
return m_adjustSettings.min;
}

void Axis::setMin(qreal value)
{
if (m_adjustSettings.min != value && !m_autoScale) {
if (adjustAxis(value, m_adjustSettings.max, Forced))
emit minMaxChanged();
}
}

qreal Axis::max() const
{
return m_adjustSettings.max;
}

void Axis::setMax(qreal value)
{
if (m_adjustSettings.max != value && !m_autoScale) {
if (adjustAxis(m_adjustSettings.min, value, Forced))
emit minMaxChanged();
}
}

Plot::Plot(int section, QAbstractItemModel *model, QObject *parent)
: QObject(parent),
m_section(section),
m_model(model),
m_visble(true),
m_lineColor(Qt::red),
m_lineWidth(1.0),
m_minData(numeric_limits<qreal>::max(), numeric_limits<qreal>::max()),
m_maxData(numeric_limits<qreal>::min(), numeric_limits<qreal>::min()),
m_plottedCount(0),
m_isRecalculateMinMax(false)
{
}

bool Plot::calculateMinMaxData(const QModelIndex &yIndex, const QModelIndex &xIndex)
{
bool minmaxChange = false;

// X Axis
double xData = xIndex.data().toDouble();
if (m_maxData.x() < xData) {
m_maxData.setX(xData);
minmaxChange = true;
}
if (m_minData.x() > xData) {
m_minData.setX(xData);
minmaxChange = true;
}
// Y Axis
double yData = yIndex.data().toDouble();
if (m_maxData.y() < yData) {
m_maxData.setY(yData);
minmaxChange = true;
}
if (m_minData.y() > yData) {
m_minData.setY(yData);
minmaxChange = true;
}

return minmaxChange;
}

void Plot::checkMinMaxDeleteData(const QModelIndex &yIndex, const QModelIndex &xIndex)
{
bool recalculateX = true;
bool recalculateY = true;

// X Axis
double xData = xIndex.data().toDouble();
if (m_minData.x() < xData && xData < m_maxData.x()) {
recalculateX = false;
}

// Y Axis
double yData = yIndex.data().toDouble();
if (m_minData.y() < yData && yData < m_maxData.y()) {
recalculateY = false;
}

m_isRecalculateMinMax = (recalculateX || recalculateY);
}

bool Plot::recalculateMinMaxAllData(bool isForce)
{
if (m_isRecalculateMinMax || isForce) {
clear();
double data;
for (int row = 0; row < count(); row++) {
// Y Axis
data = m_model->index(row, m_section).data().toDouble();
if (m_maxData.y() < data)
m_maxData.setY(data);
if (m_minData.y() > data)
m_minData.setY(data);

// X Axis
data = m_model->index(row, 0).data().toDouble();
if (m_maxData.x() < data)
m_maxData.setX(data);
if (m_minData.x() > data)
m_minData.setX(data);
}
m_isRecalculateMinMax = false;
return true;
}

return false;
}

bool Plot::visble() const
{
return m_visble;
}

void Plot::setVisble(bool visble)
{
if (m_visble != visble) {
m_visble = visble;
emit refreshed();
}
}

QColor Plot::lineColor() const
{
return m_lineColor;
}

void Plot::setLineColor(const QColor &lineColor)
{
if (m_lineColor != lineColor) {
m_lineColor = lineColor;
emit refreshed();
}
}

qreal Plot::lineWidth() const
{
return m_lineWidth;
}

void Plot::setLineWidth(qreal lineWidth)
{
if (m_lineWidth != lineWidth) {
m_lineWidth = lineWidth;
emit refreshed();
}
}

void Plot::clear()
{
m_plottedCount = 0;
m_minData.setX(numeric_limits<double>::max());
m_minData.setY(numeric_limits<double>::max());
m_maxData.setX(numeric_limits<double>::min());
m_maxData.setY(numeric_limits<double>::min());
}

int Plot::plottedPoint() const
{
return m_plottedCount;
}

void Plot::setPlottedPoint(int plottedCount)
{
if (plottedCount < 0)
m_plottedCount = 0;
else
m_plottedCount = plottedCount;
}



まとめ

時間なく、かなり手抜きに記事になってしまった…

ソースコードはGitHubにあげる予定です。

ツッコミどころ多くてすいません。