mirror of
https://github.com/sqlitebrowser/sqlitebrowser.git
synced 2026-01-19 18:40:13 -06:00
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:
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>&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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user