diff --git a/Modules/Chart/src/QmitkChartWidget.cpp b/Modules/Chart/src/QmitkChartWidget.cpp index ff614477d0..66a2af6157 100644 --- a/Modules/Chart/src/QmitkChartWidget.cpp +++ b/Modules/Chart/src/QmitkChartWidget.cpp @@ -1,525 +1,528 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include <regex> #include <QmitkChartWidget.h> #include <QGridLayout> #include <QWebChannel> #include <QWebEngineView> #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) #include <QWebEngineSettings> #endif #include <QmitkChartData.h> #include <QmitkChartxyData.h> #include "mitkExceptionMacro.h" class QmitkChartWidget::Impl final { public: explicit Impl(QWidget* parent); ~Impl(); Impl(const Impl&) = delete; Impl& operator=(const Impl&) = delete; void AddData1D(const std::vector<double>& data1D, const std::string& label, QmitkChartWidget::ChartType chartType); void AddData2D(const std::map<double, double>& data2D, const std::string& label, QmitkChartWidget::ChartType chartType); void RemoveData(const std::string& label); void ClearData(); void SetColor(const std::string& label, const std::string& colorName); void SetLineStyle(const std::string& label, LineStyle style); void SetYAxisScale(AxisScale scale); + void SetXAxisLabel(const std::string& label); + void SetYAxisLabel(const std::string& label); void SetTitle(const std::string &title); - void SetChartType(QmitkChartWidget::ChartType chartType); - void SetChartTypeByLabel(const std::string& label, QmitkChartWidget::ChartType chartType); - void SetLegendPosition(LegendPosition position); - void SetShowLegend(bool show); - - void SetStackedData(bool stacked); - void Show(bool showSubChart); + void SetShowLegend(bool show); void SetShowDataPoints(bool showDataPoints = false); + void SetChartType(const std::string& label, QmitkChartWidget::ChartType chartType); + std::string ConvertChartTypeToString(QmitkChartWidget::ChartType chartType) const; void ClearJavaScriptChart(); void InitializeJavaScriptChart(); void CallJavaScriptFuntion(const QString& command); -private: + QmitkChartData* GetC3Data() const; + std::vector<QmitkChartxyData*>* GetC3xyData() const; QSize sizeHint() const; private: std::string GetUniqueLabelName(const QList<QVariant>& labelList, const std::string& label) const; - QmitkChartxyData* GetDataElementByLabel(const std::string& label) const; - QList<QVariant> GetDataLabels(const ChartxyDataVector& c3xyData) const; + QmitkChartxyData* GetDataElementByLabel(const std::vector<QmitkChartxyData*>* c3xyData, const std::string& label) const; + QList<QVariant> GetDataLabels(const std::vector<QmitkChartxyData*>* c3xyData) const; QWebChannel* m_WebChannel; QWebEngineView* m_WebEngineView; - QmitkChartData m_C3Data; - ChartxyDataVector m_C3xyData; + QmitkChartData * m_C3Data; + std::vector<QmitkChartxyData*> * m_C3xyData; std::map<QmitkChartWidget::ChartType, std::string> m_ChartTypeToName; std::map<QmitkChartWidget::LegendPosition, std::string> m_LegendPositionToName; std::map<QmitkChartWidget::LineStyle, std::string> m_LineStyleToName; std::map<QmitkChartWidget::AxisScale, std::string> m_AxisScaleToName; }; QmitkChartWidget::Impl::Impl(QWidget* parent) : m_WebChannel(new QWebChannel(parent)) , m_WebEngineView(new QWebEngineView(parent)) { //disable context menu for QWebEngineView m_WebEngineView->setContextMenuPolicy(Qt::NoContextMenu); //Set the webengineview to an initial empty page. The actual chart will be loaded once the data is calculated. m_WebEngineView->setUrl(QUrl(QStringLiteral("qrc:///C3js/empty.html"))); m_WebEngineView->page()->setWebChannel(m_WebChannel); #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) m_WebEngineView->settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, false); #endif connect(m_WebEngineView, SIGNAL(loadFinished(bool)), parent, SLOT(OnLoadFinished(bool))); auto layout = new QGridLayout(parent); layout->setMargin(0); layout->addWidget(m_WebEngineView); parent->setLayout(layout); m_ChartTypeToName.emplace(ChartType::bar, "bar"); m_ChartTypeToName.emplace(ChartType::line, "line"); m_ChartTypeToName.emplace(ChartType::spline, "spline"); m_ChartTypeToName.emplace(ChartType::pie, "pie"); m_ChartTypeToName.emplace(ChartType::area, "area"); m_ChartTypeToName.emplace(ChartType::area_spline, "area-spline"); m_ChartTypeToName.emplace(ChartType::scatter, "scatter"); m_LegendPositionToName.emplace(LegendPosition::bottom, "bottom"); m_LegendPositionToName.emplace(LegendPosition::right, "right"); m_LegendPositionToName.emplace(LegendPosition::inset, "inset"); m_LineStyleToName.emplace(LineStyle::solid, "solid"); m_LineStyleToName.emplace(LineStyle::dashed, "dashed"); m_AxisScaleToName.emplace(AxisScale::linear, ""); m_AxisScaleToName.emplace(AxisScale::log, "log"); -} -QmitkChartWidget::Impl::~Impl() -{ + m_C3Data = new QmitkChartData(); + m_C3xyData = new std::vector<QmitkChartxyData*>(); } -std::string CheckForCorrectHex(const std::string& colorName) +QmitkChartWidget::Impl::~Impl() { - std::regex rgx("([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})"); - std::smatch match; + delete m_C3Data; + qDeleteAll(*m_C3xyData); - if (!colorName.empty() && colorName.at(0) != '#' && std::regex_search(colorName.begin(), colorName.end(), match, rgx)) - { - return "#" + colorName; - } - else - { - return colorName; - } + delete m_C3xyData; } -void QmitkChartWidget::Impl::AddData1D(const std::vector<double>& data1D, const std::string& label, QmitkChartWidget::ChartType chartType) +void QmitkChartWidget::Impl::AddData1D(const std::vector<double>& data1D, const std::string& label, QmitkChartWidget::ChartType type) { std::map<double, double> transformedData2D; unsigned int count = 0; //transform the 1D data to 2D data for (const auto& ele : data1D) { transformedData2D[count] = ele; count++; } - AddData2D(transformedData2D, label, chartType); + AddData2D(transformedData2D, label, type); } -void QmitkChartWidget::Impl::AddData2D(const std::map<double, double>& data2D, const std::string& label, QmitkChartWidget::ChartType chartType) +void QmitkChartWidget::Impl::AddData2D(const std::map<double, double>& data2D, const std::string& label, QmitkChartWidget::ChartType type) { QMap<QVariant, QVariant> data2DConverted; for (const auto& aValue : data2D) { data2DConverted.insert(aValue.first, aValue.second); } - const std::string chartTypeName(m_ChartTypeToName.at(chartType)); + const std::string chartTypeName(m_ChartTypeToName.at(type)); - auto definedLabels = GetDataLabels(m_C3xyData); + auto definedLabels = GetDataLabels(GetC3xyData()); auto uniqueLabel = GetUniqueLabelName(definedLabels, label); - if (chartType == ChartType::scatter) + if (type == ChartType::scatter) { SetShowDataPoints(true); MITK_INFO << "Enabling data points for all because of scatter plot"; } - m_C3xyData.push_back(std::make_unique<QmitkChartxyData>(data2DConverted, QVariant(QString::fromStdString(uniqueLabel)), QVariant(QString::fromStdString(chartTypeName)))); + GetC3xyData()->push_back(new QmitkChartxyData(data2DConverted, QVariant(QString::fromStdString(uniqueLabel)), QVariant(QString::fromStdString(chartTypeName)))); } void QmitkChartWidget::Impl::RemoveData(const std::string& label) { - for (ChartxyDataVector::iterator iter = m_C3xyData.begin(); iter != m_C3xyData.end(); ++iter) + for (std::vector<QmitkChartxyData*>::iterator iter = GetC3xyData()->begin(); iter != GetC3xyData()->end(); ++iter) { - if ((*iter)->GetLabel().toString().toStdString() == label) + const auto &temp = *iter; + if (temp->GetLabel().toString().toStdString() == label) { - m_C3xyData.erase(iter); + delete temp; + GetC3xyData()->erase(iter); return; } } throw std::invalid_argument("Cannot Remove Data because the label does not exist."); } void QmitkChartWidget::Impl::ClearData() { - for (auto& xyData : m_C3xyData) + for (auto& xyData : *m_C3xyData) + { + m_WebChannel->deregisterObject(xyData); + delete xyData; + xyData = nullptr; + } + GetC3xyData()->clear(); +} + +std::string CheckForCorrectHex(const std::string& colorName) +{ + std::regex rgx("([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})"); + std::smatch match; + + if (!colorName.empty() && colorName.at(0)!='#' && std::regex_search(colorName.begin(), colorName.end(), match, rgx)) { - m_WebChannel->deregisterObject(xyData.get()); + return "#" + colorName; + } + else + { + return colorName; } - m_C3xyData.clear(); } void QmitkChartWidget::Impl::SetColor(const std::string& label, const std::string& colorName) { - auto element = GetDataElementByLabel(label); + auto element = GetDataElementByLabel(GetC3xyData(), label); if (element) { auto colorChecked = CheckForCorrectHex(colorName); - element->SetColor(QVariant(QString::fromStdString(colorName))); + element->SetColor(QVariant(QString::fromStdString(colorChecked))); } } void QmitkChartWidget::Impl::SetLineStyle(const std::string& label, LineStyle style) { - auto element = GetDataElementByLabel(label); + auto element = GetDataElementByLabel(GetC3xyData(), label); //only has effect with chart type line - if (element && element->GetChartType() == QVariant(QString::fromStdString(ConvertChartTypeToString(ChartType::line)))) + if (element && element->GetChartType()==QVariant(QString::fromStdString(ConvertChartTypeToString(ChartType::line)))) { const std::string lineStyleName(m_LineStyleToName.at(style)); element->SetLineStyle(QVariant(QString::fromStdString(lineStyleName))); } } void QmitkChartWidget::Impl::SetYAxisScale(AxisScale scale) { const std::string axisScaleName(m_AxisScaleToName.at(scale)); - m_C3Data.SetYAxisScale(QString::fromStdString(axisScaleName)); + GetC3Data()->SetYAxisScale(QString::fromStdString(axisScaleName)); +} + +QmitkChartxyData* QmitkChartWidget::Impl::GetDataElementByLabel(const std::vector<QmitkChartxyData*>* c3xyData, const std::string& label) const +{ + for (auto element = c3xyData->begin(); element != c3xyData->end(); ++element) + { + if ((*element)->GetLabel().toString().toStdString() == label) + { + return *element; + } + } + MITK_WARN << "label " << label << " not found in QmitkChartWidget"; + return nullptr; +} + +QList<QVariant> QmitkChartWidget::Impl::GetDataLabels(const std::vector<QmitkChartxyData*>* c3xyData) const +{ + QList<QVariant> dataLabels; + for (auto element = c3xyData->begin(); element != c3xyData->end(); ++element) + { + dataLabels.push_back((*element)->GetLabel()); + } + return dataLabels; } void QmitkChartWidget::Impl::SetXAxisLabel(const std::string& label) { - m_C3Data.SetXAxisLabel(QString::fromStdString(label)); + GetC3Data()->SetXAxisLabel(QString::fromStdString(label)); } void QmitkChartWidget::Impl::SetYAxisLabel(const std::string& label) { - m_C3Data.SetYAxisLabel(QString::fromStdString(label)); + GetC3Data()->SetYAxisLabel(QString::fromStdString(label)); } void QmitkChartWidget::Impl::SetTitle(const std::string& title) { - m_C3Data.SetTitle(QString::fromStdString(title)); + GetC3Data()->SetTitle(QString::fromStdString(title)); +} + +void QmitkChartWidget::Impl::SetLegendPosition(QmitkChartWidget::LegendPosition legendPosition) +{ + const std::string legendPositionName(m_LegendPositionToName.at(legendPosition)); + GetC3Data()->SetLegendPosition(QString::fromStdString(legendPositionName)); } -void QmitkChartWidget::Impl::SetChartType(QmitkChartWidget::ChartType chartType) +void QmitkChartWidget::Impl::SetShowLegend(bool show) { - for (auto iterator = m_C3xyData.begin(); iterator != m_C3xyData.end(); ++iterator) + GetC3Data()->SetShowLegend(show); +} + +void QmitkChartWidget::Impl::SetShowDataPoints(bool showDataPoints) +{ + if (showDataPoints == true) { - SetChartTypeByLabel((*iterator)->GetLabel().toString().toStdString(), chartType); + GetC3Data()->SetDataPointSize(3); + } + else + { + GetC3Data()->SetDataPointSize(0); } - - auto chartTypeName = ConvertChartTypeToString(chartType); - const QString command = QString::fromStdString("transformView('" + chartTypeName + "')"); - CallJavaScriptFuntion(command); } -void QmitkChartWidget::Impl::SetChartTypeByLabel(const std::string& label, QmitkChartWidget::ChartType chartType) +void QmitkChartWidget::Impl::SetChartType(const std::string& label, QmitkChartWidget::ChartType chartType) { - auto element = GetDataElementByLabel(label); + auto element = GetDataElementByLabel(GetC3xyData(), label); if (element) { if (chartType == ChartType::scatter) { SetShowDataPoints(true); MITK_INFO << "Enabling data points for all because of scatter plot"; } - auto chartTypeName = ConvertChartTypeToString(chartType); + const std::string chartTypeName(m_ChartTypeToName.at(chartType)); element->SetChartType(QVariant(QString::fromStdString(chartTypeName))); } } -void QmitkChartWidget::Impl::SetLegendPosition(QmitkChartWidget::LegendPosition legendPosition) +std::string QmitkChartWidget::Impl::ConvertChartTypeToString(QmitkChartWidget::ChartType chartType) const { - const std::string legendPositionName(m_LegendPositionToName.at(legendPosition)); - m_C3Data.SetLegendPosition(QString::fromStdString(legendPositionName)); + return m_ChartTypeToName.at(chartType); } -void QmitkChartWidget::Impl::SetShowLegend(bool show) +QmitkChartData* QmitkChartWidget::Impl::GetC3Data() const { - m_C3Data.SetShowLegend(show); + return m_C3Data; } -void QmitkChartWidget::Impl::SetStackedData(bool stacked) +std::vector<QmitkChartxyData*>* QmitkChartWidget::Impl::GetC3xyData() const { - m_C3Data.SetStackedData(stacked); + return m_C3xyData; } QSize QmitkChartWidget::Impl::sizeHint() const { return QSize(400, 300); } void QmitkChartWidget::Impl::CallJavaScriptFuntion(const QString& command) { - if (m_C3xyData.empty()) - { - mitkThrow() << "no data available for display in chart"; - } - - m_C3Data.SetAppearance(showSubChart, m_C3xyData.front()->GetChartType() == QVariant("pie")); - InitializeJavaScriptChart(); -} - -void QmitkChartWidget::Impl::SetShowDataPoints(bool showDataPoints) -{ - if (showDataPoints == true) - { - m_C3Data.SetDataPointSize(3); - } - else - { - m_C3Data.SetDataPointSize(0); - } -} - -std::string QmitkChartWidget::Impl::ConvertChartTypeToString(QmitkChartWidget::ChartType chartType) const -{ - return m_ChartTypeToName.at(chartType); + m_WebEngineView->page()->runJavaScript(command); } void QmitkChartWidget::Impl::ClearJavaScriptChart() { m_WebEngineView->setUrl(QUrl(QStringLiteral("qrc:///C3js/empty.html"))); } void QmitkChartWidget::Impl::InitializeJavaScriptChart() { - m_WebChannel->registerObject(QStringLiteral("chartData"), &m_C3Data); + m_WebChannel->registerObject(QStringLiteral("chartData"), m_C3Data); unsigned count = 0; - for (auto& xyData : m_C3xyData) + for (auto& xyData : *m_C3xyData) { - QString variableName = "xyData" + QString::number(count); - m_WebChannel->registerObject(variableName, xyData.get()); + QString variableName = "xyData" + QString::number(count); + m_WebChannel->registerObject(variableName, xyData); count++; } - m_WebEngineView->load(QUrl(QStringLiteral("qrc:///C3js/QmitkChartWidget.html"))); -} -void QmitkChartWidget::Impl::CallJavaScriptFuntion(const QString& command) -{ - m_WebEngineView->page()->runJavaScript(command); + m_WebEngineView->load(QUrl(QStringLiteral("qrc:///C3js/QmitkChartWidget.html"))); } std::string QmitkChartWidget::Impl::GetUniqueLabelName(const QList<QVariant>& labelList, const std::string& label) const { QString currentLabel = QString::fromStdString(label); int counter = 0; while (labelList.contains(currentLabel)) { currentLabel = QString::fromStdString(label + std::to_string(counter)); counter++; } return currentLabel.toStdString(); } -QmitkChartxyData* QmitkChartWidget::Impl::GetDataElementByLabel(const std::string& label) const -{ - for (const auto& qmitkChartxyData : m_C3xyData) - { - if (qmitkChartxyData->GetLabel().toString() == label.c_str()) - { - return qmitkChartxyData.get(); - } - } - - MITK_WARN << "label " << label << " not found in QmitkChartWidget"; - return nullptr; -} - -QList<QVariant> QmitkChartWidget::Impl::GetDataLabels(const ChartxyDataVector& c3xyData) const -{ - QList<QVariant> dataLabels; - for (auto element = c3xyData.begin(); element != c3xyData.end(); ++element) - { - dataLabels.push_back((*element)->GetLabel()); - } - return dataLabels; -} - QmitkChartWidget::QmitkChartWidget(QWidget* parent) : QWidget(parent) , m_Impl(new Impl(this)) { } QmitkChartWidget::~QmitkChartWidget() { -} - -void QmitkChartWidget::AddData1D(const std::vector<double>& data1D, const std::string& label, ChartType type) -{ - m_Impl->AddData1D(data1D, label, type); + delete m_Impl; } void QmitkChartWidget::AddData2D(const std::map<double, double>& data2D, const std::string& label, ChartType type) { m_Impl->AddData2D(data2D, label, type); } -void QmitkChartWidget::RemoveData(const std::string& label) -{ - m_Impl->RemoveData(label); -} - void QmitkChartWidget::SetColor(const std::string& label, const std::string& colorName) { m_Impl->SetColor(label, colorName); } void QmitkChartWidget::SetLineStyle(const std::string& label, LineStyle style) { m_Impl->SetLineStyle(label, style); } void QmitkChartWidget::SetYAxisScale(AxisScale scale) { m_Impl->SetYAxisScale(scale); } +void QmitkChartWidget::AddData1D(const std::vector<double>& data1D, const std::string& label, ChartType type) +{ + m_Impl->AddData1D(data1D, label, type); +} + +void QmitkChartWidget::RemoveData(const std::string& label) +{ + m_Impl->RemoveData(label); +} + void QmitkChartWidget::SetXAxisLabel(const std::string & label) { m_Impl->SetXAxisLabel(label); } void QmitkChartWidget::SetYAxisLabel(const std::string & label) { m_Impl->SetYAxisLabel(label); } void QmitkChartWidget::SetTitle(const std::string & title) { m_Impl->SetTitle(title); } -void QmitkChartWidget::SetChartTypeForAllDataAndReload(ChartType type) +void QmitkChartWidget::SetShowDataPoints(bool showDataPoints) { - m_Impl->SetChartType(type); + m_Impl->SetShowDataPoints(showDataPoints); } void QmitkChartWidget::SetChartType(const std::string& label, ChartType type) { - m_Impl->SetChartTypeByLabel(label, type); + m_Impl->SetChartType(label, type); } void QmitkChartWidget::SetLegendPosition(LegendPosition position) { m_Impl->SetLegendPosition(position); } void QmitkChartWidget::SetShowLegend(bool show) { m_Impl->SetShowLegend(show); } -void QmitkChartWidget::SetStackedData(bool stacked) -{ - m_Impl->SetStackedData(stacked); -} - void QmitkChartWidget::Show(bool showSubChart) { - m_Impl->Show(showSubChart); -} + if (m_Impl->GetC3xyData()->empty()) + { + mitkThrow() << "no data available for display in chart"; + } -void QmitkChartWidget::SetShowDataPoints(bool showDataPoints) -{ - m_Impl->SetShowDataPoints(showDataPoints); + m_Impl->GetC3Data()->SetAppearance(showSubChart, m_Impl->GetC3xyData()->front()->GetChartType() == QVariant("pie")); + m_Impl->InitializeJavaScriptChart(); } void QmitkChartWidget::Clear() { m_Impl->ClearData(); m_Impl->ClearJavaScriptChart(); } -void QmitkChartWidget::OnLoadFinished(bool isLoadSuccessful) +void QmitkChartWidget::OnLoadFinished(bool isLoadSuccessfull) { - if(isLoadSuccessful) + if(isLoadSuccessfull) { emit PageSuccessfullyLoaded(); } } +void QmitkChartWidget::SetChartTypeForAllDataAndReload(ChartType type) +{ + auto allData = m_Impl->GetC3xyData(); + for (auto iterator = allData->begin(); iterator != allData->end(); ++iterator) + { + SetChartType((*iterator)->GetLabel().toString().toStdString(), type); + } + + auto chartTypeName = m_Impl->ConvertChartTypeToString(type); + const QString command = QString::fromStdString("transformView('" + chartTypeName + "')"); + m_Impl->CallJavaScriptFuntion(command); +} + void QmitkChartWidget::SetTheme(ChartStyle themeEnabled) { QString command; if (themeEnabled == ChartStyle::darkstyle) { command = QString("changeTheme('dark')"); } - else { + else + { command = QString("changeTheme('light')"); } + m_Impl->CallJavaScriptFuntion(command); } void QmitkChartWidget::Reload(bool showSubChart) { QString subChartString; - if (showSubChart) { subChartString = "true"; } else { subChartString = "false"; } const QString command = QString("ReloadChart(" + subChartString + ")"); m_Impl->CallJavaScriptFuntion(command); } QSize QmitkChartWidget::sizeHint() const { return m_Impl->sizeHint(); }