Skip to content

Commit

Permalink
plotting: add a simple mechanism to visualize data
Browse files Browse the repository at this point in the history
added a plotting dock, where you can select x and y axis
and it will draw a chart out of it
  • Loading branch information
rp- committed Apr 19, 2014
1 parent abeafa2 commit 88ccd48
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 9 deletions.
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
)

Expand Down Expand Up @@ -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)
Expand Down
165 changes: 165 additions & 0 deletions src/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -308,6 +314,9 @@ void MainWindow::populateTable( const QString & tablename)
if(editWin)
editWin->reset();

// update plot
updatePlot(m_browseTableModel);

QApplication::restoreOverrideCursor();
}

Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
}
4 changes: 4 additions & 0 deletions src/MainWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class SqliteTableModel;
class DbStructureModel;
class QNetworkReply;
class QNetworkAccessManager;
class QTreeWidgetItem;

namespace Ui {
class MainWindow;
Expand Down Expand Up @@ -58,6 +59,7 @@ class MainWindow : public QMainWindow
Ui::MainWindow* ui;

SqliteTableModel* m_browseTableModel;
SqliteTableModel* m_currentPlotModel;
QMenu *popupTableMenu;
QMenu *recentFilesMenu;

Expand Down Expand Up @@ -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
81 changes: 76 additions & 5 deletions src/MainWindow.ui
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down Expand Up @@ -275,7 +275,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>296</width>
<width>452</width>
<height>494</height>
</rect>
</property>
Expand Down Expand Up @@ -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">
Expand Down Expand Up @@ -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">
Expand Down Expand Up @@ -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">
Expand Down Expand Up @@ -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">
Expand All @@ -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>
Expand Down
Loading

0 comments on commit 88ccd48

Please sign in to comment.