diff --git a/Modules/Chart/include/QmitkChartData.h b/Modules/Chart/include/QmitkChartData.h index 66c9d2d3b3..350e6604c1 100644 --- a/Modules/Chart/include/QmitkChartData.h +++ b/Modules/Chart/include/QmitkChartData.h @@ -1,98 +1,104 @@ /*=================================================================== 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. ===================================================================*/ #ifndef QmitkC3Data_h #define QmitkC3Data_h #include #include /** \brief This class holds the relevant properties for the chart generation with C3 such as labels and diagram type. * It is derived from QObject, because we need Q_PROPERTIES to send Data via QWebChannel to JavaScript. * \sa The actual data for the chart generation is in QmitkC3xyData! */ class QmitkChartData : public QObject { Q_OBJECT Q_PROPERTY(QVariant m_xAxisLabel READ GetXAxisLabel WRITE SetXAxisLabel NOTIFY SignalXAxisLabelChanged); Q_PROPERTY(QVariant m_yAxisLabel READ GetYAxisLabel WRITE SetYAxisLabel NOTIFY SignalYAxisLabelChanged); Q_PROPERTY(QVariant m_chartTitle READ GetTitle WRITE SetTitle NOTIFY SignalTitleChanged); Q_PROPERTY(QVariant m_LegendPosition READ GetLegendPosition WRITE SetLegendPosition NOTIFY SignalLegendPositionChanged); Q_PROPERTY(QVariant m_ShowLegend READ GetShowLegend WRITE SetShowLegend NOTIFY SignalShowLegendChanged); Q_PROPERTY(QVariant m_YAxisScale READ GetYAxisScale WRITE SetYAxisScale NOTIFY SignalYAxisScaleChanged); Q_PROPERTY(QVariant m_ShowSubchart READ GetShowSubchart WRITE SetShowSubchart NOTIFY SignalShowSubchartChanged); Q_PROPERTY(QVariant m_UsePercentageInPieChart READ GetUsePercentageInPieChart WRITE SetUsePercentageInPieChart NOTIFY SignalUsePercentageInPieChartChanged); Q_PROPERTY(QVariant m_DataPointSize READ GetDataPointSize WRITE SetDataPointSize NOTIFY SignalDataPointSizeChanged); + Q_PROPERTY(QVariant m_StackedData READ GetStackedData WRITE SetStackedData NOTIFY SignalStackedDataChanged); public: QmitkChartData(); void SetAppearance(bool showSubChart = true, bool usePercentageInPieChart = false); Q_INVOKABLE QVariant GetXAxisLabel() const { return m_xAxisLabel; }; Q_INVOKABLE void SetXAxisLabel(const QVariant& label) { m_xAxisLabel = label; emit SignalXAxisLabelChanged(label); }; Q_INVOKABLE QVariant GetYAxisLabel() const { return m_yAxisLabel; }; Q_INVOKABLE void SetYAxisLabel(const QVariant& label) { m_yAxisLabel = label; emit SignalYAxisLabelChanged(label); }; Q_INVOKABLE QVariant GetTitle() const { return m_chartTitle; }; Q_INVOKABLE void SetTitle(const QVariant& title) { m_chartTitle = title; emit SignalTitleChanged(title); }; Q_INVOKABLE QVariant GetLegendPosition() const { return m_LegendPosition; }; Q_INVOKABLE void SetLegendPosition(const QVariant& legendPosition) { m_LegendPosition = legendPosition; emit SignalLegendPositionChanged(legendPosition); }; Q_INVOKABLE QVariant GetShowLegend() const { return m_ShowLegend; }; Q_INVOKABLE void SetShowLegend(const QVariant& show) { m_ShowLegend = show; emit SignalShowLegendChanged(show); }; Q_INVOKABLE QVariant GetYAxisScale() const { return m_YAxisScale; }; Q_INVOKABLE void SetYAxisScale(const QVariant& YAxisScale) { m_YAxisScale = YAxisScale; emit SignalYAxisScaleChanged(YAxisScale); }; Q_INVOKABLE QVariant GetShowSubchart() const { return m_ShowSubchart; }; Q_INVOKABLE void SetShowSubchart(const QVariant& showSubchart) { m_ShowSubchart = showSubchart; emit SignalShowSubchartChanged(showSubchart); }; Q_INVOKABLE QVariant GetUsePercentageInPieChart() const { return m_UsePercentageInPieChart; }; Q_INVOKABLE void SetUsePercentageInPieChart(const QVariant& usePercentageInPieChart) { m_UsePercentageInPieChart = usePercentageInPieChart; emit SignalUsePercentageInPieChartChanged(usePercentageInPieChart); }; Q_INVOKABLE QVariant GetDataPointSize() const { return m_DataPointSize; }; - Q_INVOKABLE void SetDataPointSize(const QVariant& showDataPoints) {if (showDataPoints > 0 ) { m_DataPointSize = 3; } else { m_DataPointSize = 0; } emit SignalDataPointSizeChanged(showDataPoints); }; + Q_INVOKABLE void SetDataPointSize(const QVariant& showDataPoints) { if (showDataPoints > 0) { m_DataPointSize = 3; } else { m_DataPointSize = 0; } emit SignalDataPointSizeChanged(showDataPoints); }; + + Q_INVOKABLE QVariant GetStackedData() const { return m_StackedData; }; + Q_INVOKABLE void SetStackedData(const QVariant& stackedData) { m_StackedData = stackedData; emit SignalStackedDataChanged(m_StackedData); }; signals: void SignalYAxisLabelChanged(const QVariant label); void SignalXAxisLabelChanged(const QVariant label); void SignalLegendPositionChanged(const QVariant legendPosition); void SignalShowLegendChanged(const QVariant show); void SignalYAxisScaleChanged(const QVariant YAxisScale); void SignalTitleChanged(const QVariant title); void SignalShowSubchartChanged(const QVariant showSubchart); void SignalUsePercentageInPieChartChanged(const QVariant usePercentageInPieChart); void SignalDataPointSizeChanged(const QVariant showDataPoints); + void SignalStackedDataChanged(const QVariant stackedData); private: QVariant m_xAxisLabel; QVariant m_yAxisLabel; QVariant m_chartTitle; QVariant m_ShowLegend = true; QVariant m_LegendPosition; QVariant m_ShowSubchart; QVariant m_YAxisScale; QVariant m_UsePercentageInPieChart; QVariant m_numberDatasets; QVariant m_DataPointSize = 0; + QVariant m_StackedData; }; #endif //QmitkC3Data_h diff --git a/Modules/Chart/include/QmitkChartWidget.h b/Modules/Chart/include/QmitkChartWidget.h index 6eed0726a3..1de030388a 100644 --- a/Modules/Chart/include/QmitkChartWidget.h +++ b/Modules/Chart/include/QmitkChartWidget.h @@ -1,219 +1,221 @@ /*=================================================================== 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. ===================================================================*/ #ifndef QmitkC3jsWidget_h #define QmitkC3jsWidget_h #include #include #include #include /*! \brief QmitkChartWidget is a widget to display various charts based on the javascript chart library C3js. * \details Data is added via AddData1D() or AddData2D().\n * There can be multiple charts (of different types with different properties) created by calling AddData1D or AddData2D multiple times.\n\n * The following chart types are supported: * * line chart: http://c3js.org/samples/simple_multiple.html * * bar chart: http://c3js.org/samples/chart_bar.html * * spline chart: http://c3js.org/samples/chart_spline.html * * pie chart: http://c3js.org/samples/chart_pie.html * * scatter chart: http://c3js.org/samples/chart_scatter.html * * area chart: http://c3js.org/samples/chart_area.html * * area spline chart: http://c3js.org/samples/chart_area.html * * Technical details: The javascript code is embedded in a QWebEngineView. The actual js code is implemented in resource\Chart.js. * \sa http://c3js.org for further information about the used javaScript library. * \warning We had issues with Qt versions <5.10. So we highly encourage to use Qt 5.10 or newer if this module is used. * \ingroup Modules/Chart */ class MITKCHART_EXPORT QmitkChartWidget : public QWidget { Q_OBJECT public: /*! * \brief enum of diagram types. */ enum class ChartType { bar, /*!< bar chart, see http://c3js.org/samples/chart_bar.html */ line, /*!< line chart, see http://c3js.org/samples/simple_multiple.html */ spline, /*!< spline chart (smoothed line chart), see http://c3js.org/samples/chart_spline.html */ pie, /*!< pie chart, see http://c3js.org/samples/chart_pie.html*/ area, /*!< area chart, see http://c3js.org/samples/chart_area.html*/ area_spline, /*!< area-spline chart, see http://c3js.org/samples/chart_area.html*/ scatter /*!< scatter chart, see http://c3js.org/samples/chart_scatter.html*/ }; /*! * \brief enum of chart style (modifies background and line color). */ enum class ChartStyle { darkstyle, /*!< background color: dark gray, line color: blue */ lightstyle /*!< background color: white, line color: blue */ }; enum class LineStyle { solid, dashed }; enum class AxisScale { linear, log }; /*! * \brief enum of legend position. * See http://c3js.org/reference.html#legend-position */ enum class LegendPosition { bottom, right, inset }; explicit QmitkChartWidget(QWidget* parent = nullptr); ~QmitkChartWidget() override; /*! * \brief Adds 1D data to the widget * \details internally, the list is converted to a map with increasing integers keys starting at 0. * \param label the name of the data that is also used as identifier. * \param chartType the chart type that should be used for this data entry * \note the data can be cleared with ClearDiagram() * \note If the label name already exists, the name is replaced with a unique one by concatenating numbers to it. * \warning Pie chart is significantly different than the other chart types. Here, the data given by AddData1D is summed. Each entry represents a different category. */ void AddData1D(const std::vector& data1D, const std::string& label, ChartType chartType = ChartType::bar); /*! * \brief Adds 2D data to the widget. Call repeatedly for displaying multiple charts. * \details each entry represents a data point: key: value --> x-value: y-value. * \param label the name of the data that is also used as identifier. * \param chartType the chart type that should be used for this data entry * \note the data can be cleared with ClearDiagram() * \note If the label name already exists, the name is replaced with a unique one by concatenating numbers to it. * \warning Pie chart is significantly different than the other chart types. Here, the data given by AddData1D is summed. Each entry represents a different category. */ void AddData2D(const std::map& data2D, const std::string& label, ChartType chartType = ChartType::bar); /*! * \brief Removes data from the widget, works for 1D and 2D Data * \param label the name of the data that is also used as identifier. * \note All data can be cleared with ClearDiagram() * \throws Invalid Argument Exception when the label cannot be found */ void RemoveData(const std::string& label); /*! * \brief Sets the color of one data entry (identifier is previously assigned label) * \details the color name can be "red" or a hex number (#FF0000). * \warning Either define all data entries with a color or no data entry. If a mixed approach is used, * C3 choses the color of the data entry (that could be the same as a user defined color). * \note If an unknown label is given, nothing happens. * \sa https://www.w3schools.com/cssref/css_colors.asp */ void SetColor(const std::string& label, const std::string& colorName); /*! * \brief Sets the line style of one data entry (identifier is previously assigned label) * \details two line styles are possible: LineStyle::solid and LineStyle::dashed. * The default line style is solid. * \note If an unknown label is given, nothing happens. * \warning only sets the line style if the current chart type is ChartType::line. * However, the line style remains also if the chart changes (e.g. new chart type) */ void SetLineStyle(const std::string& label, LineStyle style); /*! * \brief Sets the axis scale to either linear (default) or logarithmic. */ void SetYAxisScale(AxisScale scale); void SetXAxisLabel(const std::string& label); void SetYAxisLabel(const std::string& label); /*! * \brief Sets a title for the chart. */ void SetTitle(const std::string &title); /*! * \brief Changes the chart type for all data entries and reloads the chart */ void SetChartTypeForAllDataAndReload(ChartType type); /*! * \brief Sets the chart type for a data entry * \details for available types, see ChartType * \note If an unknown label is given, nothing happens. * \warning Pie chart is significantly different than the other chart types. Here, the data given by AddData1D is summed. Each entry represents a different category. * \sa DiagramType for available types */ void SetChartType(const std::string& label, ChartType type); /*! * \brief Sets the legend position. * \details Default position is bottom. * \sa LegendPosition for available types */ void SetLegendPosition(LegendPosition position); void SetShowLegend(bool show); + void SetStackedData(bool stacked); + /*! * \brief Displays the chart in the widget * \param showSubChart if a subchart is displayed inside the widget or not (see http://c3js.org/samples/options_subchart.html). * \exception if no data has been provided (\sa AddData1D AddData2D) */ void Show(bool showSubChart=false); /*! * \brief Either displays the dataPoints or not * \param showDataPoints if dataPoints are displayed inside the widget or not. * \details: example for not showing points: http://c3js.org/samples/point_show.html * example for showing the points: http://c3js.org/samples/simple_multiple.html */ void SetShowDataPoints(bool showDataPoints); /*! * \brief Clears all data inside and resets the widget. */ void Clear(); /*! * \brief Changes the theme of the widget. */ void SetTheme(ChartStyle themeEnabled); /*! * \brief Reloads the chart in the widget * \details reloading may be needed to display added data in an existing chart * \param showSubChart if a subchart is displayed inside the widget or not. */ void Reload(bool showSubChart); public slots: void OnLoadFinished(bool isLoadSuccessful); signals: void PageSuccessfullyLoaded(); private: class Impl; std::unique_ptr m_Impl; }; #endif diff --git a/Modules/Chart/resource/Chart.js b/Modules/Chart/resource/Chart.js index 437c439153..61eaa967dd 100644 --- a/Modules/Chart/resource/Chart.js +++ b/Modules/Chart/resource/Chart.js @@ -1,236 +1,260 @@ //Based on C3.js (http://c3js.org). See Website for examples! document.body.style.backgroundColor = 'rgb(240, 240, 240)'; var minHeight = 255; var chart; GenerateChart() var chartData; var xValues=[]; var yValues=[]; +var dataLabels=[]; var xs = {}; var dataColors = {}; var chartTypes = {}; var lineStyle = {}; //Is executed when js is loaded first. //Extracts relevant information from chartData in variables window.onload = function() { new QWebChannel(qt.webChannelTransport, function(channel) { chartData = channel.objects.chartData; - var count = 0; + var count = 0; for(var propertyName in channel.objects) { - if (propertyName != 'chartData'){ + if (propertyName != 'chartData') + { var xDataTemp = channel.objects[propertyName].m_XData var yDataTemp = channel.objects[propertyName].m_YData - var dataLabelsTemp; + var dataLabel = channel.objects[propertyName].m_Label + dataLabels.push(dataLabel) + //add label to x array xDataTemp.unshift('x'+count.toString()) - dataLabelsTemp = channel.objects[propertyName].m_Label - - xs[dataLabelsTemp] = 'x'+count.toString() + xs[dataLabel] = 'x' + count.toString() xDataTemp.push(null); //append null value, to make sure the last tick on x-axis is displayed correctly - yDataTemp.unshift(dataLabelsTemp) + yDataTemp.unshift(dataLabel) yDataTemp.push(null); //append null value, to make sure the last tick on y-axis is displayed correctly xValues[count] = xDataTemp yValues[count] = yDataTemp - dataColors[dataLabelsTemp] = channel.objects[propertyName].m_Color - chartTypes[dataLabelsTemp] = channel.objects[propertyName].m_ChartType + dataColors[dataLabel] = channel.objects[propertyName].m_Color + chartTypes[dataLabel] = channel.objects[propertyName].m_ChartType - if (channel.objects[propertyName].m_LineStyleName=="solid"){ - lineStyle[dataLabelsTemp]= '' + if (channel.objects[propertyName].m_LineStyleName == "solid") + { + lineStyle[dataLabel] = '' } - else { - lineStyle[dataLabelsTemp]= [{'style':'dashed'}] + else + { + lineStyle[dataLabel] = [{ 'style': 'dashed' }] } count++; } } - setupChart(chartData) + + setupChart(chartData) }); } //This is necessary to resize the chart, after the size of the parent changed window.onresize = function () { var size = window.innerHeight-(window.innerHeight/100*10); //subtract 5% of height to hide vertical scrool bar if (size < minHeight) { size = minHeight; } chart.resize({ height: size, }); } -function ReloadChart(showSubchart) +function ReloadChart(showSubchart, stackDataString) { chartData.m_ShowSubchart = showSubchart; + chartData.m_StackedData = stackDataString; setupChart(chartData); } function setupChart(chartData) { window.onresize(); GenerateChart(chartData) chart.unload(); //unload data before loading new data //for multiple xy line chart, see http://c3js.org/samples/simple_xy_multiple.html var columns = []; for (var i in xValues){ columns.push(xValues[i]) } for (var i in yValues){ columns.push(yValues[i]) } chart.load({ xs: xs, - columns: columns + columns: columns }); } //Transformation between different chart types //takes the name of the chart type as a parameter //called by QmitkC3jsWidget function transformView(TransformTo) { chart.transform(TransformTo); }; function changeTheme(color) { link = document.getElementsByTagName("link")[0]; if (color == 'dark') { link.href = "Chart_dark.css"; } else { link.href = "Chart.css"; } }; //Here, the chart magic takes place. C3js is called function GenerateChart(chartData) { - if (chartData === undefined) + if (chartData == undefined) { chartData = {} } - //adaption for bar ratio indepenend of amount of data points + + if (dataLabels == undefined) + { + dataLabels = [] + } + + var groupLabels = []; + if (chartData.m_StackedData == true) + { + groupLabels = dataLabels + } + + //adaption for bar ratio independent of amount of data points //otherwise, bars could be covered. var barRatio; - try { + try + { barRatio = 0.8*Math.exp(-0.015*xValues[0].length); } - catch (err){ + catch (err) + { barRatio=0.42 } var formatCharacter; - if (chartData.m_UsePercentageInPieChart==true){ + if (chartData.m_UsePercentageInPieChart == true) + { formatCharacter = '%' } - else{ + else + { formatCharacter = '.1f' } + chart = c3.generate({ title:{ text: chartData.m_diagramTitle, position: 'top-center' }, data: { xs: {}, //use first "column" as x axis values columns: [], //initialize empty. Data will be loaded in function setupChart(chartData) types: chartTypes, selection: { enabled: false, multiple: false, }, colors: dataColors, regions: lineStyle, + groups: [groupLabels] }, legend: { position: chartData.m_LegendPosition, show: chartData.m_ShowLegend }, grid: { y: { lines: [{value:0}] //Draws a horizontal line at y=0 } }, bar: { width: { ratio: barRatio } }, pie:{ label: { format: function (value, ratio, id) { if (chartData.m_UsePercentageInPieChart==true){ return d3.format('%') (ratio); } else{ return value; } } } }, zoom: { enabled: true, }, subchart: { show: chartData.m_ShowSubchart //Shows a subchart that shows the region the primary chart is zoomed in to by overlay. }, axis: { x: { tick: { multiline: false, fit: false, //to make more x labels appear on zoom centered: true, format: d3.format(".1f"), }, label: { text: chartData.m_xAxisLabel, position: 'outer-center' } }, y: { tick: { format: d3.format(formatCharacter) }, label: { text: chartData.m_yAxisLabel, position: 'outer-middle' }, scale: { name: chartData.m_YAxisScale } } }, tooltip: { format: { title: function (x) { return chartData.m_xAxisLabel + ': ' + d3.format(".2f")(x)} }, }, //Style data points in linegraph point: { r: chartData.m_DataPointSize, focus: { expand: { r: chartData.m_DataPointSize + 2 } } }, }); } diff --git a/Modules/Chart/src/QmitkChartData.cpp b/Modules/Chart/src/QmitkChartData.cpp index 2dabbc46aa..975a2cf157 100644 --- a/Modules/Chart/src/QmitkChartData.cpp +++ b/Modules/Chart/src/QmitkChartData.cpp @@ -1,27 +1,30 @@ /*=================================================================== 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 -QmitkChartData::QmitkChartData() : m_ShowSubchart(true), m_YAxisScale("") +QmitkChartData::QmitkChartData() + : m_ShowSubchart(true) + , m_YAxisScale("") + , m_StackedData(false) { } void QmitkChartData::SetAppearance(bool showSubChart, bool usePercentageInPieChart) { m_ShowSubchart = showSubChart; m_UsePercentageInPieChart = usePercentageInPieChart; } \ No newline at end of file diff --git a/Modules/Chart/src/QmitkChartWidget.cpp b/Modules/Chart/src/QmitkChartWidget.cpp index 40372dc85b..9749461c0f 100644 --- a/Modules/Chart/src/QmitkChartWidget.cpp +++ b/Modules/Chart/src/QmitkChartWidget.cpp @@ -1,498 +1,513 @@ /*=================================================================== 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 #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) #include #endif #include #include #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& data1D, const std::string& label, QmitkChartWidget::ChartType chartType); void AddData2D(const std::map& 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 SetLegendPosition(LegendPosition position); + void SetChartType(QmitkChartWidget::ChartType chartType); + void SetChartTypeByLabel(const std::string& label, QmitkChartWidget::ChartType chartType); + void SetLegendPosition(LegendPosition position); void SetShowLegend(bool show); - void SetShowDataPoints(bool showDataPoints = false); + void SetStackedData(bool stacked); void Show(bool showSubChart); - void SetChartType(QmitkChartWidget::ChartType chartType); - void SetChartTypeByLabel(const std::string& label, QmitkChartWidget::ChartType chartType); + void SetShowDataPoints(bool showDataPoints = false); std::string ConvertChartTypeToString(QmitkChartWidget::ChartType chartType) const; void ClearJavaScriptChart(); void InitializeJavaScriptChart(); void CallJavaScriptFuntion(const QString& command); private: using ChartxyDataVector = std::vector>; std::string GetUniqueLabelName(const QList& labelList, const std::string& label) const; QmitkChartxyData* GetDataElementByLabel(const std::string& label) const; QList GetDataLabels(const ChartxyDataVector& c3xyData) const; QWebChannel* m_WebChannel; QWebEngineView* m_WebEngineView; QmitkChartData m_C3Data; ChartxyDataVector m_C3xyData; std::map m_ChartTypeToName; std::map m_LegendPositionToName; std::map m_LineStyleToName; std::map 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() { } -void QmitkChartWidget::Impl::AddData1D(const std::vector& data1D, const std::string& label, QmitkChartWidget::ChartType chartType) { +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)) + { + return "#" + colorName; + } + else + { + return colorName; + } +} + +void QmitkChartWidget::Impl::AddData1D(const std::vector& data1D, const std::string& label, QmitkChartWidget::ChartType chartType) +{ std::map 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); } void QmitkChartWidget::Impl::AddData2D(const std::map& data2D, const std::string& label, QmitkChartWidget::ChartType chartType) { QMap data2DConverted; for (const auto& aValue : data2D) { data2DConverted.insert(aValue.first, aValue.second); } const std::string chartTypeName(m_ChartTypeToName.at(chartType)); auto definedLabels = GetDataLabels(m_C3xyData); auto uniqueLabel = GetUniqueLabelName(definedLabels, label); if (chartType == ChartType::scatter) { SetShowDataPoints(true); MITK_INFO << "Enabling data points for all because of scatter plot"; } m_C3xyData.push_back(std::make_unique(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) { if ((*iter)->GetLabel().toString().toStdString() == label) { m_C3xyData.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) { m_WebChannel->deregisterObject(xyData.get()); } m_C3xyData.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)) - { - return "#" + colorName; - } - else { - return colorName; - } -} - void QmitkChartWidget::Impl::SetColor(const std::string& label, const std::string& colorName) { auto element = GetDataElementByLabel(label); if (element) { auto colorChecked = CheckForCorrectHex(colorName); element->SetColor(QVariant(QString::fromStdString(colorName))); } } void QmitkChartWidget::Impl::SetLineStyle(const std::string& label, LineStyle style) { auto element = GetDataElementByLabel(label); //only has effect with chart type 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)); } -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 QmitkChartWidget::Impl::GetDataLabels(const ChartxyDataVector& c3xyData) const -{ - QList 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)); } void QmitkChartWidget::Impl::SetYAxisLabel(const std::string& label) { m_C3Data.SetYAxisLabel(QString::fromStdString(label)); } void QmitkChartWidget::Impl::SetTitle(const std::string& title) { m_C3Data.SetTitle(QString::fromStdString(title)); } +void QmitkChartWidget::Impl::SetChartType(QmitkChartWidget::ChartType chartType) +{ + for (auto iterator = m_C3xyData.begin(); iterator != m_C3xyData.end(); ++iterator) + { + SetChartTypeByLabel((*iterator)->GetLabel().toString().toStdString(), chartType); + } + + auto chartTypeName = ConvertChartTypeToString(chartType); + const QString command = QString::fromStdString("transformView('" + chartTypeName + "')"); + CallJavaScriptFuntion(command); +} + +void QmitkChartWidget::Impl::SetChartTypeByLabel(const std::string& label, QmitkChartWidget::ChartType chartType) +{ + auto element = GetDataElementByLabel(label); + if (element) + { + if (chartType == ChartType::scatter) + { + SetShowDataPoints(true); + MITK_INFO << "Enabling data points for all because of scatter plot"; + } + auto chartTypeName = ConvertChartTypeToString(chartType); + element->SetChartType(QVariant(QString::fromStdString(chartTypeName))); + } +} + void QmitkChartWidget::Impl::SetLegendPosition(QmitkChartWidget::LegendPosition legendPosition) { const std::string legendPositionName(m_LegendPositionToName.at(legendPosition)); m_C3Data.SetLegendPosition(QString::fromStdString(legendPositionName)); } void QmitkChartWidget::Impl::SetShowLegend(bool show) { m_C3Data.SetShowLegend(show); } -void QmitkChartWidget::Impl::SetShowDataPoints(bool showDataPoints) +void QmitkChartWidget::Impl::SetStackedData(bool stacked) { - if (showDataPoints == true) { - m_C3Data.SetDataPointSize(3); - } - else { - m_C3Data.SetDataPointSize(0); - } + m_C3Data.SetStackedData(stacked); } void QmitkChartWidget::Impl::Show(bool showSubChart) { 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::SetChartType(QmitkChartWidget::ChartType chartType) +void QmitkChartWidget::Impl::SetShowDataPoints(bool showDataPoints) { - for (auto iterator = m_C3xyData.begin(); iterator != m_C3xyData.end(); ++iterator) + if (showDataPoints == true) { - SetChartTypeByLabel((*iterator)->GetLabel().toString().toStdString(), chartType); + m_C3Data.SetDataPointSize(3); } - - auto chartTypeName = ConvertChartTypeToString(chartType); - const QString command = QString::fromStdString("transformView('" + chartTypeName + "')"); - CallJavaScriptFuntion(command); -} - -void QmitkChartWidget::Impl::SetChartTypeByLabel(const std::string& label, QmitkChartWidget::ChartType chartType) -{ - auto element = GetDataElementByLabel(label); - if (element) + else { - if (chartType == ChartType::scatter) - { - SetShowDataPoints(true); - MITK_INFO << "Enabling data points for all because of scatter plot"; - } - auto chartTypeName = ConvertChartTypeToString(chartType); - element->SetChartType(QVariant(QString::fromStdString(chartTypeName))); + m_C3Data.SetDataPointSize(0); } } std::string QmitkChartWidget::Impl::ConvertChartTypeToString(QmitkChartWidget::ChartType chartType) const { return m_ChartTypeToName.at(chartType); } -void QmitkChartWidget::Impl::CallJavaScriptFuntion(const QString& command) -{ - 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); unsigned count = 0; for (auto& xyData : m_C3xyData) { QString variableName = "xyData" + QString::number(count); m_WebChannel->registerObject(variableName, xyData.get()); count++; } m_WebEngineView->load(QUrl(QStringLiteral("qrc:///C3js/QmitkChartWidget.html"))); } +void QmitkChartWidget::Impl::CallJavaScriptFuntion(const QString& command) +{ + m_WebEngineView->page()->runJavaScript(command); +} + std::string QmitkChartWidget::Impl::GetUniqueLabelName(const QList& 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 QmitkChartWidget::Impl::GetDataLabels(const ChartxyDataVector& c3xyData) const +{ + QList 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& data1D, const std::string& label, ChartType type) +{ + m_Impl->AddData1D(data1D, label, type); +} + void QmitkChartWidget::AddData2D(const std::map& 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& 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::SetShowDataPoints(bool showDataPoints) -{ - m_Impl->SetShowDataPoints(showDataPoints); -} - void QmitkChartWidget::SetChartTypeForAllDataAndReload(ChartType type) { m_Impl->SetChartType(type); } void QmitkChartWidget::SetChartType(const std::string& label, ChartType type) { m_Impl->SetChartTypeByLabel(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); } +void QmitkChartWidget::SetShowDataPoints(bool showDataPoints) +{ + m_Impl->SetShowDataPoints(showDataPoints); +} + void QmitkChartWidget::Clear() { m_Impl->ClearData(); m_Impl->ClearJavaScriptChart(); } void QmitkChartWidget::OnLoadFinished(bool isLoadSuccessful) { if(isLoadSuccessful) { emit PageSuccessfullyLoaded(); } } void QmitkChartWidget::SetTheme(ChartStyle themeEnabled) { QString command; if (themeEnabled == ChartStyle::darkstyle) { command = QString("changeTheme('dark')"); } 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); }