plotting: add a simple mechanism to visualize data

added a plotting dock, where you can select x and y axis
and it will draw a chart out of it
This commit is contained in:
Peinthor Rene
2014-04-19 22:19:32 +02:00
parent abeafa2786
commit 88ccd48173
5 changed files with 253 additions and 9 deletions

View File

@@ -49,6 +49,7 @@ set(SQLB_MOC_HDR
src/sqlitetablemodel.h
src/sqltextedit.h
src/DbStructureModel.h
libs/qcustomplot-source/qcustomplot.h
)
set(SQLB_SRC
@@ -72,6 +73,7 @@ set(SQLB_SRC
src/DbStructureModel.cpp
src/grammar/Sqlite3Lexer.cpp
src/grammar/Sqlite3Parser.cpp
libs/qcustomplot-source/qcustomplot.cpp
src/main.cpp
)
@@ -141,7 +143,7 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR} ${ANTLR_DIR} ${QHEXEDIT_DIR} src
add_executable(${PROJECT_NAME} ${SQLB_HDR} ${SQLB_SRC} ${SQLB_FORM_HDR} ${SQLB_MOC} ${SQLB_RESOURCES_RCC})
if(USE_QT5)
qt5_use_modules(${PROJECT_NAME} Gui Widgets Network Test)
qt5_use_modules(${PROJECT_NAME} Gui Widgets PrintSupport Network Test)
set(QT_LIBRARIES "")
endif()
add_dependencies(${PROJECT_NAME} antlr qhexedit)

View File

@@ -105,6 +105,11 @@ void MainWindow::init()
ui->viewMenu->actions().at(0)->setIcon(QIcon(":/icons/log_dock"));
ui->viewDBToolbarAction->setChecked(!ui->toolbarDB->isHidden());
// Plot view menu
ui->viewMenu->insertAction(ui->viewDBToolbarAction, ui->dockPlot->toggleViewAction());
ui->viewMenu->actions().at(1)->setShortcut(QKeySequence(tr("Ctrl+P")));
ui->viewMenu->actions().at(1)->setIcon(QIcon(":/icons/log_dock"));
// Set statusbar fields
statusEncodingLabel = new QLabel(ui->statusbar);
statusEncodingLabel->setEnabled(false);
@@ -123,6 +128,7 @@ void MainWindow::init()
restoreGeometry(PreferencesDialog::getSettingsValue("MainWindow", "geometry").toByteArray());
restoreState(PreferencesDialog::getSettingsValue("MainWindow", "windowState").toByteArray());
ui->comboLogSubmittedBy->setCurrentIndex(ui->comboLogSubmittedBy->findText(PreferencesDialog::getSettingsValue("SQLLogDock", "Log").toString()));
ui->splitterForPlot->restoreState(PreferencesDialog::getSettingsValue("PlotDock", "splitterSize").toByteArray());
// Set other window settings
setAcceptDrops(true);
@@ -308,6 +314,9 @@ void MainWindow::populateTable( const QString & tablename)
if(editWin)
editWin->reset();
// update plot
updatePlot(m_browseTableModel);
QApplication::restoreOverrideCursor();
}
@@ -363,6 +372,7 @@ void MainWindow::closeEvent( QCloseEvent* event )
PreferencesDialog::setSettingsValue("MainWindow", "geometry", saveGeometry());
PreferencesDialog::setSettingsValue("MainWindow", "windowState", saveState());
PreferencesDialog::setSettingsValue("SQLLogDock", "Log", ui->comboLogSubmittedBy->currentText());
PreferencesDialog::setSettingsValue("PlotDock", "splitterSize", ui->splitterForPlot->saveState());
clearCompleterModelsFields();
QMainWindow::closeEvent(event);
}
@@ -699,6 +709,7 @@ void MainWindow::executeQuery()
}
} while( tail && *tail != 0 && (sql3status == SQLITE_OK || sql3status == SQLITE_DONE));
sqlWidget->finishExecution(statusMessage);
updatePlot(sqlWidget->getModel());
if(!modified && !wasdirty)
db.revert(); // better rollback, if the logic is not enough we can tune it.
@@ -1308,3 +1319,157 @@ void MainWindow::httpresponse(QNetworkReply *reply)
}
}
}
namespace {
/*!
* \brief guessdatatype try to parse the first 10 rows and decide the datatype
* \param model model to check the data
* \param column index of the column to check
* \return the guessed datatype
*/
QVariant::Type guessdatatype(SqliteTableModel* model, int column)
{
QVariant::Type type = QVariant::Invalid;
for(int i = 0; i < std::min(10, model->rowCount()) && type != QVariant::String; ++i)
{
QVariant data = model->data(model->index(i, column));
if(data.convert(QVariant::Double))
{
type = QVariant::Double;
}
else
{
QString s = model->data(model->index(i, column)).toString();
QDate d = QDate::fromString(s, Qt::ISODate);
if(d.isValid())
type = QVariant::DateTime;
else
type = QVariant::String;
}
}
return type;
}
}
void MainWindow::updatePlot(SqliteTableModel *model, bool update)
{
// add columns to x/y seleciton tree widget
if(update)
{
disconnect(ui->treePlotColumns, SIGNAL(itemChanged(QTreeWidgetItem*,int)),
this,SLOT(on_treePlotColumns_itemChanged(QTreeWidgetItem*,int)));
m_currentPlotModel = model;
ui->treePlotColumns->clear();
for(int i = 0; i < model->columnCount(); ++i)
{
QVariant::Type columntype = guessdatatype(model, i);
if(columntype != QVariant::String && columntype != QVariant::Invalid)
{
QTreeWidgetItem* columnitem = new QTreeWidgetItem(ui->treePlotColumns);
// maybe i make this more complicated than i should
// but store the model column index in the first 16 bit and the type
// in the other 16 bits
uint itemdata = 0;
itemdata = i << 16;
itemdata |= columntype;
columnitem->setData(0, Qt::UserRole, itemdata);
columnitem->setText(0, model->headerData(i, Qt::Horizontal).toString());
columnitem->setCheckState(1, Qt::Unchecked);
columnitem->setCheckState(2, Qt::Unchecked);
ui->treePlotColumns->addTopLevelItem(columnitem);
}
}
ui->plotWidget->yAxis->setLabel("Y");
ui->plotWidget->xAxis->setLabel("X");
connect(ui->treePlotColumns, SIGNAL(itemChanged(QTreeWidgetItem*,int)),this,SLOT(on_treePlotColumns_itemChanged(QTreeWidgetItem*,int)));
}
// search for the x axis select
QTreeWidgetItem* xitem = 0;
for(int i = 0; i < ui->treePlotColumns->topLevelItemCount(); ++i)
{
xitem = ui->treePlotColumns->topLevelItem(i);
if(xitem->checkState(2) == Qt::Checked)
break;
xitem = 0;
}
QStringList yAxisLabels;
QVector<QColor> colors;
colors << Qt::blue << Qt::red << Qt::green << Qt::darkYellow << Qt::darkCyan << Qt::darkGray;
ui->plotWidget->clearGraphs();
if(xitem)
{
uint xitemdata = xitem->data(0, Qt::UserRole).toUInt();
int x = xitemdata >> 16;
int xtype = xitemdata & (uint)0xFF;
// check if we have a x axis with datetime data
if(xtype == QVariant::DateTime)
{
ui->plotWidget->xAxis->setTickLabelType(QCPAxis::ltDateTime);
ui->plotWidget->xAxis->setDateTimeFormat("yyyy-mm-dd");
}
else
{
ui->plotWidget->xAxis->setTickLabelType(QCPAxis::ltNumber);
}
// add graph for each selected y axis
for(int i = 0; i < ui->treePlotColumns->topLevelItemCount(); ++i)
{
QTreeWidgetItem* item = ui->treePlotColumns->topLevelItem(i);
if(item->checkState((1)) == Qt::Checked && ui->plotWidget->graphCount() < colors.size())
{
uint itemdata = item->data(0, Qt::UserRole).toUInt();
int column = itemdata >> 16;
QCPGraph* graph = ui->plotWidget->addGraph();
int y = column;
graph->setPen(QPen(colors[y]));
QVector<double> xdata(model->rowCount()), ydata(model->rowCount());
for(int i = 0; i < model->rowCount(); ++i)
{
// convert x type axis if it's datetime
if(xtype == QVariant::DateTime)
{
QString s = model->data(model->index(i, x)).toString();
QDateTime d = QDateTime::fromString(s, Qt::ISODate);
xdata[i] = d.toTime_t();
}
else
{
xdata[i] = model->data(model->index(i, x)).toDouble();
}
ydata[i] = model->data(model->index(i, y)).toDouble();
}
graph->setData(xdata, ydata);
graph->setLineStyle(QCPGraph::lsLine);
graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, 5));
yAxisLabels << model->headerData(y, Qt::Horizontal).toString();
graph->rescaleAxes();
}
}
// set axis labels
ui->plotWidget->xAxis->setLabel(model->headerData(x, Qt::Horizontal).toString());
ui->plotWidget->yAxis->setLabel(yAxisLabels.join("|"));
}
ui->plotWidget->replot();
}
void MainWindow::on_treePlotColumns_itemChanged(QTreeWidgetItem *item, int column)
{
updatePlot(m_currentPlotModel, false);
}

View File

@@ -18,6 +18,7 @@ class SqliteTableModel;
class DbStructureModel;
class QNetworkReply;
class QNetworkAccessManager;
class QTreeWidgetItem;
namespace Ui {
class MainWindow;
@@ -58,6 +59,7 @@ private:
Ui::MainWindow* ui;
SqliteTableModel* m_browseTableModel;
SqliteTableModel* m_currentPlotModel;
QMenu *popupTableMenu;
QMenu *recentFilesMenu;
@@ -154,6 +156,8 @@ private slots:
virtual void loadExtension();
virtual void reloadSettings();
virtual void httpresponse(QNetworkReply* reply);
virtual void updatePlot(SqliteTableModel* model, bool update = true);
void on_treePlotColumns_itemChanged(QTreeWidgetItem *item, int column);
};
#endif

View File

@@ -18,7 +18,7 @@
<normaloff>:/oldimages/icon16</normaloff>:/oldimages/icon16</iconset>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayoutmain">
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QTabWidget" name="mainTab">
<property name="currentIndex">
@@ -275,7 +275,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>296</width>
<width>452</width>
<height>494</height>
</rect>
</property>
@@ -761,7 +761,7 @@
<x>0</x>
<y>0</y>
<width>800</width>
<height>20</height>
<height>21</height>
</rect>
</property>
<widget class="QMenu" name="fileMenu">
@@ -938,6 +938,71 @@
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="dockPlot">
<property name="features">
<set>QDockWidget::AllDockWidgetFeatures</set>
</property>
<property name="windowTitle">
<string>Plot</string>
</property>
<attribute name="dockWidgetArea">
<number>2</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_2">
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QWidget" name="widget_2" native="true">
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QSplitter" name="splitterForPlot">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QTreeWidget" name="treePlotColumns">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>2</verstretch>
</sizepolicy>
</property>
<property name="columnCount">
<number>3</number>
</property>
<attribute name="headerDefaultSectionSize">
<number>100</number>
</attribute>
<column>
<property name="text">
<string>Columns</string>
</property>
</column>
<column>
<property name="text">
<string>Y</string>
</property>
</column>
<column>
<property name="text">
<string>X</string>
</property>
</column>
</widget>
<widget class="QCustomPlot" name="plotWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>8</verstretch>
</sizepolicy>
</property>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
<action name="fileNewAction">
<property name="icon">
<iconset resource="icons/icons.qrc">
@@ -1228,7 +1293,7 @@
<string>&amp;Execute SQL</string>
</property>
<property name="shortcut">
<string>F5</string>
<string>F5, Ctrl+Return</string>
</property>
</action>
<action name="actionSqlOpenFile">
@@ -1270,7 +1335,7 @@
<string>Execute current line</string>
</property>
<property name="shortcut">
<string>Ctrl+F5</string>
<string>Ctrl+E</string>
</property>
</action>
<action name="actionExportCsvPopup">
@@ -1291,6 +1356,12 @@
<extends>QTableWidget</extends>
<header>ExtendedTableWidget.h</header>
</customwidget>
<customwidget>
<class>QCustomPlot</class>
<extends>QWidget</extends>
<header>libs/qcustomplot-source/qcustomplot.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>dbTreeWidget</tabstop>

View File

@@ -1,7 +1,7 @@
TEMPLATE = app
QT += core gui network
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets printsupport
TARGET = sqlitebrowser
@@ -40,7 +40,8 @@ HEADERS += \
gen_version.h \
SqlExecutionArea.h \
VacuumDialog.h \
DbStructureModel.h
DbStructureModel.h \
../../libs/qcustomplot-source/qcustomplot.h
SOURCES += \
sqlitedb.cpp \
@@ -62,7 +63,8 @@ SOURCES += \
FilterTableHeader.cpp \
SqlExecutionArea.cpp \
VacuumDialog.cpp \
DbStructureModel.cpp
DbStructureModel.cpp \
../../libs/qcustomplot-source/qcustomplot.cpp
RESOURCES += icons/icons.qrc