diff --git a/Modules/Chart/include/QmitkChartWidget.h b/Modules/Chart/include/QmitkChartWidget.h index 21e5cb3d4a..4da0fc9c19 100644 --- a/Modules/Chart/include/QmitkChartWidget.h +++ b/Modules/Chart/include/QmitkChartWidget.h @@ -1,279 +1,291 @@ /*=================================================================== 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 plotly. * \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 * * bar chart * * spline chart * * pie chart * * scatter chart * * area chart * * area spline chart. * * Technical details: The javascript code is embedded in a QWebEngineView. The actual js code is implemented in resource\Chart.js. * \sa https://plot.ly/javascript/ for further information about the used javaScript library. * \ingroup Modules/Chart */ class MITKCHART_EXPORT QmitkChartWidget : public QWidget { Q_OBJECT public: /*! * \brief enum of diagram types. */ enum class ChartType { bar, /*!< bar chart, see https://plot.ly/javascript/bar-charts/ */ line, /*!< line chart, see https://plot.ly/javascript/line-charts/ */ spline, /*!< spline chart (smoothed line chart), see https://plot.ly/~jduelfer/23/spline/#/ */ pie, /*!< pie chart, see https://plot.ly/javascript/pie-charts/ */ area, /*!< area chart, see https://plot.ly/javascript/filled-area-plots/ */ area_spline, /*!< area-spline chart, similar to https://plot.ly/~jduelfer/23/spline/#/ */ scatter /*!< scatter chart, see https://plot.ly/javascript/line-and-scatter/ */ }; /*! * \brief enum of chart style (modifies background and line color). */ enum class ColorTheme { darkstyle, /*!< background color: dark gray, foreground color: white*/ lightstyle /*!< background color: white, foreground color: black */ }; enum class LineStyle { solid, dashed }; enum class AxisScale { linear, log }; /*! * \brief enum of legend position. * See https://plot.ly/javascript/legend/ */ enum class LegendPosition { bottomMiddle, bottomRight, topRight, topLeft, middleRight }; explicit QmitkChartWidget(QWidget* parent = nullptr); //for UnitTests explicit QmitkChartWidget(QWidget *parent, bool unitTest); ~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 Updates data of an existing label + * \param data1D the 1D data , \sa AddData1D + * \param label the (existing) label + * \note if the label does not exist, nothing happens + */ + void UpdateData1D(const std::vector &data1D, const std::string &label); + + /*! + * \sa UpdateData1D + * \sa AddData2D + */ + void UpdateData2D(const std::map &data2D, const std::string &label); + /*! * \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); + void UpdateLabel(const std::string& existingLabel, const std::string& newLabel); + /*! * \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, * plotly 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. * \sa https://plot.ly/javascript/log-plot/ */ void SetYAxisScale(AxisScale scale); void SetXAxisLabel(const std::string& label); void SetYAxisLabel(const std::string& label); /*! * \brief Sets labels for pie chart data. * \note in AddData1D, the label still has to be given that acts as a unique id. However, the label is omitted then. */ void SetPieLabels(const std::vector &pieLabels, 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 error bars for data in x direction * \note If only error plus is provided, the error bars are symmetrical * \param label the name of the data that is also used as identifier. * \param errorPlus the error in positive direction * \param errorMinus the error in negative direction. Same as error plus if omitted */ void SetXErrorBars(const std::string &label, const std::vector &errorPlus, const std::vector& errorMinus = std::vector()); /*! * \brief Sets error bars for data in y direction * \details for parameters, see SetXErrorBars * \note If only error plus is provided, the error bars are symmetrical */ void SetYErrorBars(const std::string &label, const std::vector &errorPlus, const std::vector &errorMinus = std::vector()); /*! * \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. * \note if no data has been provided, (\sa AddData1D AddData2D), an empty chart is displayed. */ 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: https://plot.ly/javascript/line-charts/#styling-line-plot * example for showing the points: https://plot.ly/javascript/pointcloud/ */ void SetShowDataPoints(bool showDataPoints); /*! * \brief Clears all data inside and resets the widget. */ void Clear(); /*! * \brief Sets the theme of the widget. * \details default is dark theme as in MITK. * \warning has to be called before Show() or Reload() to work */ void SetTheme(ColorTheme themeEnabled); /*! * \brief Sets whether the subchart shall be shown. * \details Changes the state of the current chart object. * \note Needs to be reloaded with Reload() to display changes. */ void SetShowSubchart(bool showSubChart); /*! * \brief Sets whether the error bars shall be shown. * \details Changes the state of the current chart object. * \note Needs to be reloaded with Reload() to display changes. * \param showErrorBars if error bars are displayed or not. */ void SetShowErrorBars(bool showErrorBars); /*! * \brief Sets the min and max x values of the chart * \details Zooms in to view the values between minValue and maxValue in x direction */ void SetMinMaxValueXView(double minValueX,double maxValueX); /*! * \brief Sets the min and max y values of the chart * \details Zooms in to view the values between minValue and maxValue in y direction */ void SetMinMaxValueYView(double minValueY, double maxValueY); /*! * \brief Reloads the chart in the widget * \details reloading may be needed to display added data in an existing chart */ void Reload(); QSize sizeHint() const override; public slots: void OnLoadFinished(bool isLoadSuccessful); void OnPageSuccessfullyLoaded(); signals: void PageSuccessfullyLoaded(); private: /*! source: https://stackoverflow.com/questions/29383/converting-bool-to-text-in-c*/ std::string convertBooleanValue(bool value) const; class Impl; std::unique_ptr m_Impl; }; #endif diff --git a/Modules/Chart/include/QmitkChartxyData.h b/Modules/Chart/include/QmitkChartxyData.h index 59843cf65e..21c0322e78 100644 --- a/Modules/Chart/include/QmitkChartxyData.h +++ b/Modules/Chart/include/QmitkChartxyData.h @@ -1,113 +1,163 @@ /*=================================================================== 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 QmitkC3xyData_h #define QmitkC3xyData_h #include /** /brief This class holds the actual data for the chart generation with C3. -* data can be loaded in constructor directly or with SetData -* It is derived from QObject, because we need Q_PROPERTIES to send Data via QWebChannel to JavaScript. -*/ + * data can be loaded in constructor directly or with SetData + * It is derived from QObject, because we need Q_PROPERTIES to send Data via QWebChannel to JavaScript. + */ class QmitkChartxyData : public QObject { Q_OBJECT - Q_PROPERTY(QList m_YData READ GetYData WRITE SetYData NOTIFY SignalYDataChanged); - Q_PROPERTY(QList m_XData READ GetXData WRITE SetXData NOTIFY SignalXDataChanged); - Q_PROPERTY(QList m_XErrorDataPlus READ GetXErrorDataPlus WRITE SetXErrorDataPlus NOTIFY SignalErrorDataChanged); - Q_PROPERTY(QList m_XErrorDataMinus READ GetXErrorDataMinus WRITE SetXErrorDataMinus NOTIFY SignalErrorDataChanged); - Q_PROPERTY(QList m_YErrorDataPlus READ GetYErrorDataPlus WRITE SetYErrorDataPlus NOTIFY SignalErrorDataChanged); - Q_PROPERTY(QList m_YErrorDataMinus READ GetYErrorDataMinus WRITE SetYErrorDataMinus NOTIFY SignalErrorDataChanged); + Q_PROPERTY(QVariant m_LabelCount READ GetLabelCount CONSTANT); + Q_PROPERTY(QList m_YData READ GetYData WRITE SetYData NOTIFY SignalDataChanged); + Q_PROPERTY(QList m_XData READ GetXData WRITE SetXData NOTIFY SignalDataChanged); + Q_PROPERTY( + QList m_XErrorDataPlus READ GetXErrorDataPlus WRITE SetXErrorDataPlus NOTIFY SignalErrorDataChanged); + Q_PROPERTY( + QList m_XErrorDataMinus READ GetXErrorDataMinus WRITE SetXErrorDataMinus NOTIFY SignalErrorDataChanged); + Q_PROPERTY( + QList m_YErrorDataPlus READ GetYErrorDataPlus WRITE SetYErrorDataPlus NOTIFY SignalErrorDataChanged); + Q_PROPERTY( + QList m_YErrorDataMinus READ GetYErrorDataMinus WRITE SetYErrorDataMinus NOTIFY SignalErrorDataChanged); Q_PROPERTY( QList m_PieLabels READ GetPieLabels WRITE SetPieLabels NOTIFY SignalPieLabelsChanged); Q_PROPERTY(QVariant m_ChartType READ GetChartType WRITE SetChartType NOTIFY SignalDiagramTypeChanged); Q_PROPERTY(QVariant m_Color READ GetColor WRITE SetColor NOTIFY SignalColorChanged); Q_PROPERTY(QVariant m_Label READ GetLabel WRITE SetLabel NOTIFY SignalLabelChanged); Q_PROPERTY(QVariant m_LineStyleName READ GetLineStyle WRITE SetLineStyle NOTIFY SignalLineStyleChanged); public: - explicit QmitkChartxyData(const QMap& data, const QVariant& label, const QVariant& diagramType); //Constructor for Data2D (x:y=1:2, 2:6, 3:7) + explicit QmitkChartxyData(const QMap &data, + const QVariant &label, + const QVariant &diagramType, + const QVariant &position); // Constructor for Data2D (x:y=1:2, 2:6, 3:7) + + void SetData(const QMap &data); - void SetData(const QMap& data); + Q_INVOKABLE QVariant GetLabelCount() const { return m_LabelCount; } Q_INVOKABLE QList GetYData() const { return m_YData; }; - Q_INVOKABLE void SetYData(const QList& yData) { m_YData =yData; }; + Q_INVOKABLE void SetYData(const QList &yData) + { + m_YData = yData; + emit SignalDataChanged(yData); + }; Q_INVOKABLE QList GetXData() const { return m_XData; }; - Q_INVOKABLE void SetXData(const QList& xData) { m_XData =xData; }; + Q_INVOKABLE void SetXData(const QList &xData) + { + m_XData = xData; + emit SignalDataChanged(xData); + }; Q_INVOKABLE QList GetXErrorDataPlus() const { return m_XErrorDataPlus; }; - Q_INVOKABLE void SetXErrorDataPlus(const QList &errorData) { m_XErrorDataPlus = errorData; }; + Q_INVOKABLE void SetXErrorDataPlus(const QList &errorData) + { + m_XErrorDataPlus = errorData; + emit SignalErrorDataChanged(errorData); + }; Q_INVOKABLE QList GetXErrorDataMinus() const { return m_XErrorDataMinus; }; - Q_INVOKABLE void SetXErrorDataMinus(const QList &errorData) { m_XErrorDataMinus = errorData; }; + Q_INVOKABLE void SetXErrorDataMinus(const QList &errorData) + { + m_XErrorDataMinus = errorData; + emit SignalErrorDataChanged(errorData); + }; Q_INVOKABLE QList GetYErrorDataPlus() const { return m_YErrorDataPlus; }; - Q_INVOKABLE void SetYErrorDataPlus(const QList &errorData) { m_YErrorDataPlus = errorData; }; + Q_INVOKABLE void SetYErrorDataPlus(const QList &errorData) + { + m_YErrorDataPlus = errorData; + emit SignalErrorDataChanged(errorData); + }; Q_INVOKABLE QList GetYErrorDataMinus() const { return m_YErrorDataMinus; }; - Q_INVOKABLE void SetYErrorDataMinus(const QList &errorData) { m_YErrorDataMinus = errorData; }; + Q_INVOKABLE void SetYErrorDataMinus(const QList &errorData) + { + m_YErrorDataMinus = errorData; + emit SignalErrorDataChanged(errorData); + }; Q_INVOKABLE QVariant GetChartType() const { return m_ChartType; }; - Q_INVOKABLE void SetChartType(const QVariant& chartType) { m_ChartType = chartType; }; + Q_INVOKABLE void SetChartType(const QVariant &chartType) + { + m_ChartType = chartType; + emit SignalDiagramTypeChanged(chartType); + }; Q_INVOKABLE QVariant GetLabel() const { return m_Label; }; - Q_INVOKABLE void SetLabel(const QVariant& label) { m_Label = label; }; + Q_INVOKABLE void SetLabel(const QVariant &label) + { + m_Label = label; + emit SignalLabelChanged(label); + }; Q_INVOKABLE QList GetPieLabels() const { return m_PieLabels; }; Q_INVOKABLE void SetPieLabels(const QList &pieLabels) { m_PieLabels = pieLabels; }; Q_INVOKABLE QVariant GetColor() const { return m_Color; }; - Q_INVOKABLE void SetColor(const QVariant& color) { m_Color = color; }; + Q_INVOKABLE void SetColor(const QVariant &color) + { + m_Color = color; + emit SignalColorChanged(color); + }; Q_INVOKABLE QVariant GetLineStyle() const { return m_LineStyleName; }; - Q_INVOKABLE void SetLineStyle(const QVariant& lineStyle) { m_LineStyleName = lineStyle; }; + Q_INVOKABLE void SetLineStyle(const QVariant &lineStyle) + { + m_LineStyleName = lineStyle; + emit SignalLineStyleChanged(lineStyle); + }; - /** - * \brief Clears the Data. - * - * This function clears the data. - */ + * \brief Clears the Data. + * + * This function clears the data (including error data). + */ void ClearData(); signals: - void SignalYDataChanged(const QList yData); - void SignalXDataChanged(const QList xData); + void SignalDataChanged(const QList data); void SignalErrorDataChanged(const QList errorData); void SignalDiagramTypeChanged(const QVariant diagramType); void SignalColorChanged(const QVariant color); void SignalLabelChanged(const QVariant label); void SignalPieLabelsChanged(const QList pieLabels); void SignalLineStyleChanged(const QVariant lineStyle); private: - QList m_YData; - QList m_XData; - QList m_XErrorDataPlus; - QList m_XErrorDataMinus; + /** js needs to know which label position in the list QmitkChartWidget::Impl::m_C3xyData it has for updating the values*/ + const QVariant m_LabelCount; + QList m_YData; + QList m_XData; + QList m_XErrorDataPlus; + QList m_XErrorDataMinus; QList m_YErrorDataPlus; QList m_YErrorDataMinus; - QVariant m_Label; + QVariant m_Label; QList m_PieLabels; - QVariant m_ChartType; - QVariant m_Color; - QVariant m_LineStyleName; + QVariant m_ChartType; + QVariant m_Color = ""; + QVariant m_LineStyleName = "solid"; }; -#endif //QmitkC3xyData_h +#endif // QmitkC3xyData_h diff --git a/Modules/Chart/resource/Chart.js b/Modules/Chart/resource/Chart.js index d6bdbc5e83..78ae2adf06 100644 --- a/Modules/Chart/resource/Chart.js +++ b/Modules/Chart/resource/Chart.js @@ -1,445 +1,611 @@ document.body.style.backgroundColor = 'rgb(240, 240, 240)'; const minHeight = 255; var chart; -var chartData; var xErrorValuesPlus=[]; var xErrorValuesMinus=[]; var yErrorValuesPlus=[]; var yErrorValuesMinus=[]; var xValues=[]; var yValues=[]; var dataLabels=[]; var pieDataLabels=[]; var xs = {}; var minValueX; var maxValueX; var minValueY; var maxValueY; var dataColors = {}; var chartTypes = {}; var lineStyle = {}; var backgroundColor = '#f0f0f0'; var foregroundColor = 'black'; var dataProperties = {}; +var chartData = null; + + // Important loading function. This will be executed at first in this whole script. // Fetching data from QWebChannel and storing them for display purposes. window.onload = function() { initHeight(); + + /** + * Adds handler for Qt signal emitted from QmitkChartxyData. + * Changes for single traces like value changes in a line plot are handled here. + */ + function handleDataChangeEvents(registeredChannelObject) + { + let position = registeredChannelObject.m_LabelCount; + registeredChannelObject.SignalDiagramTypeChanged.connect(function(newValue){ + let updateDiagramType = generateTraceByChartType(newValue); + Plotly.restyle('chart', updateDiagramType, position); + }); + + registeredChannelObject.SignalLineStyleChanged.connect(function(newValue){ + var dashValue; + if (newValue == "dashed"){ + dashValue = "dot"; + } + else { + dashValue = "solid"; + } + updateDash = { + line : { + "dash" : dashValue + } + } + + Plotly.restyle('chart', updateDash, position); + }); + + registeredChannelObject.SignalColorChanged.connect(function(newValue){ + var updateColor={ + marker:{ + "color" : newValue + }, + line:{ + "color" : newValue + } + } + Plotly.restyle('chart', updateColor, position); + }); + + registeredChannelObject.SignalDataChanged.connect(function(newValue){ + console.log("data changed for label " + registeredChannelObject.m_Label); + + let xDataTemp = registeredChannelObject.m_XData; + let yDataTemp = registeredChannelObject.m_YData; + + let trace = generateTraceByChartType(registeredChannelObject.m_ChartType); + + trace["x"] = [xDataTemp]; + trace["y"] = [yDataTemp]; + trace["name"] = registeredChannelObject.m_Label; + + Plotly.restyle('chart', trace, position); + }); + + registeredChannelObject.SignalLabelChanged.connect(function(newValue){ + let trace = { + name: newValue + }; + Plotly.restyle('chart', trace, position); + }); + } + + /** + * Adds handler for Qt signal emitted from QmitkChartData. + * Changes for the whole chart like title are handled here. + */ + function handleChartChangeEvents(registeredChannelObject) + { + registeredChannelObject.SignalXAxisLabelChanged.connect(function(newValue){ + var layout = { + xaxis: { + title: { + text: newValue + }, + color: foregroundColor + } + } + Plotly.relayout('chart', layout); + }); + + registeredChannelObject.SignalYAxisLabelChanged.connect(function(newValue){ + var layout = { + yaxis: { + title: { + text: newValue + }, + color: foregroundColor + } + } + Plotly.relayout('chart', layout); + }); + + registeredChannelObject.SignalTitleChanged.connect(function(newValue){ + var layout = { + title: { + text:newValue, + font: { + color: foregroundColor + } + } + } + Plotly.relayout('chart', layout); + }); + + registeredChannelObject.SignalLegendPositionChanged.connect(function(newValue){ + let legendPosition = generateLegendPosition(chartData.m_LegendPosition); + var layout = { + legend: legendPosition + } + Plotly.relayout('chart', layout); + }); + + registeredChannelObject.SignalYAxisScaleChanged.connect(function(newValue){ + var layout = { + yaxis : { + type : newValue + } + }; + Plotly.relayout('chart', layout); + }); + + registeredChannelObject.SignalShowLegendChanged.connect(function(newValue){ + var layout = { + showlegend : newValue + }; + Plotly.relayout('chart', layout); + }); + + registeredChannelObject.SignalShowSubchartChanged.connect(function(newValue){ + var layout = { + xaxis : {} + }; + if (newValue){ + layout.xaxis.rangeslider = {}; // adds range slider below x axis + } + Plotly.relayout('chart', layout); + }); + + } new QWebChannel(qt.webChannelTransport, function(channel) { chartData = channel.objects.chartData; + handleChartChangeEvents(chartData); + let count = 0; for(let propertyName in channel.objects) { if (propertyName != 'chartData') { - let xDataTemp = channel.objects[propertyName].m_XData; - let yDataTemp = channel.objects[propertyName].m_YData; - let xErrorsTempPlus = channel.objects[propertyName].m_XErrorDataPlus; - let xErrorsTempMinus = channel.objects[propertyName].m_XErrorDataMinus; - let yErrorsTempPlus = channel.objects[propertyName].m_YErrorDataPlus; - let yErrorsTempMinus = channel.objects[propertyName].m_YErrorDataMinus; - let pieDataLabelsTemp = channel.objects[propertyName].m_PieLabels; - - let dataLabel = channel.objects[propertyName].m_Label; + let chartXYData = channel.objects[propertyName]; + handleDataChangeEvents(chartXYData); + + let xDataTemp = chartXYData.m_XData; + let yDataTemp = chartXYData.m_YData; + let xErrorsTempPlus = chartXYData.m_XErrorDataPlus; + let pieDataLabelsTemp = chartXYData.m_PieLabels; + let xErrorsTempMinus = chartXYData.m_XErrorDataMinus; + let yErrorsTempPlus = chartXYData.m_YErrorDataPlus; + let yErrorsTempMinus = chartXYData.m_YErrorDataMinus; + let dataLabel = chartXYData.m_Label; dataLabels.push(dataLabel); console.log("loading datalabel: "+dataLabel); //add label to x array xDataTemp.unshift('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(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 xErrorValuesPlus[count] = xErrorsTempPlus; xErrorValuesMinus[count] = xErrorsTempMinus; yErrorValuesPlus[count] = yErrorsTempPlus; yErrorValuesMinus[count] = yErrorsTempMinus; pieDataLabels[count] = pieDataLabelsTemp; + var tempLineStyle = ''; - if (channel.objects[propertyName].m_LineStyleName == "solid") + if (chartXYData.m_LineStyleName == "solid") { tempLineStyle = '' } else { tempLineStyle = "dashed" } dataProperties[dataLabel] = { - "color" : channel.objects[propertyName].m_Color, - "chartType": channel.objects[propertyName].m_ChartType, + "color" : chartXYData.m_Color, + "chartType": chartXYData.m_ChartType, "style": tempLineStyle } count++; } } var theme = chartData.m_themeName; minValueX = chartData.m_MinValueXView; minValueY = chartData.m_MinValueYView; maxValueX = chartData.m_MaxValueXView; maxValueY = chartData.m_MaxValueYView; setThemeColors(theme); generateChart(chartData); }); } /** * Inits the height of the chart element to 90% of the full window height. */ function initHeight() { var size = window.innerHeight-(window.innerHeight/100*5); //subtract 10% of height to hide vertical scrool bar let chart = document.getElementById("chart"); chart.style.height = `${size}px`; } function getPlotlyChartType(inputType){ let plotlyType = inputType; if (inputType == "line"){ plotlyType = "scatter"; } else if (inputType == "scatter"){ plotlyType = "scatterOnly" } return plotlyType; } /** * Generate error bars object * * @param {array} errors - contains error bar values * @return error bar object */ function generateErrorBars(errors, visible){ let errorObject = { type: 'data', array: errors, visible: visible } return errorObject; } +/** + * Generate legend position object + * + * @param legendPosition the string of the legendPosition enum from QmitkChartWidget + * @return legend position object + */ +function generateLegendPosition(legendPosition){ + if (legendPosition == "bottomMiddle"){ + var legendX = 0.5; + var legendY = -0.75; + } + else if (legendPosition == "bottomRight"){ + var legendX = 1; + var legendY = 0; + } + else if (legendPosition == "topRight"){ + var legendX = 1; + var legendY = 1; + } + else if (legendPosition == "topLeft"){ + var legendX = 0; + var legendY = 1; + } + else if (legendPosition == "middleRight"){ + var legendX = 1; + var legendY = 0.5; + } + + let legendPositionObject = { + x: legendX, + y: legendY, + font : { + color: foregroundColor + } + } + + return legendPositionObject; +} + function generateErrorBarsAsymmetric(errorsPlus, errorsMinus, visible){ let errorObject = generateErrorBars(errorsPlus, visible); errorObject["arrayminus"] = errorsMinus; errorObject["symmetric"] = false; - + return errorObject; } function generateStackPlotData(){ let data = []; for (let index = 0; index < dataLabels.length; index++){ let inputType = dataProperties[dataLabels[index]]["chartType"]; let chartType = getPlotlyChartType(inputType); - + let trace = { x: xValues[index].slice(1), y: yValues[index].slice(1), stackgroup: 'one', name: dataLabels[index], type: chartType, marker:{ color: dataProperties[dataLabels[index]]["color"] } }; data.push(trace); } return data; } +function generateTraceByChartType(chartType){ + let plotlyChartType = getPlotlyChartType(chartType); + + let trace = { + type: plotlyChartType, + }; + trace["line"] = {} + if (plotlyChartType == "area"){ + trace["fill"] = 'tozeroy' + } else if (plotlyChartType == "spline"){ + trace["line"]["shape"] = 'spline' + } else if (plotlyChartType == "scatterOnly"){ + trace["mode"] = 'markers'; + } else if (plotlyChartType == "area-spline"){ + trace["fill"] = 'tozeroy' + trace["line"]["shape"] = 'spline' + } + + return trace; +} + function generatePlotData(){ let data = []; for (let index = 0; index < dataLabels.length; index++){ + let trace = generateTraceByChartType(dataProperties[dataLabels[index]]["chartType"]); - let inputType = dataProperties[dataLabels[index]]["chartType"]; - let chartType = getPlotlyChartType(inputType); - - let trace = { - x: xValues[index].slice(1), - y: yValues[index].slice(1), - type: chartType, - name: dataLabels[index], - }; - if (chartType=="pie"){ + trace["x"] = xValues[index].slice(1); + trace["y"] = yValues[index].slice(1); + trace["name"] = dataLabels[index]; + if (dataProperties[dataLabels[index]]["chartType"]=="pie"){ trace["values"] = yValues[index].slice(1); if (typeof pieDataLabels[index] !== 'undefined' && pieDataLabels[index].length > 0){ trace["labels"] = pieDataLabels[index]; } } if(typeof xErrorValuesPlus[index] !== 'undefined'){ if(typeof xErrorValuesMinus[index] !== 'undefined' && xErrorValuesMinus[index].length > 0) { trace["error_x"] = generateErrorBarsAsymmetric(xErrorValuesPlus[index], xErrorValuesMinus[index], chartData.m_ShowErrorBars); }else{ trace["error_x"] = generateErrorBars(xErrorValuesPlus[index], chartData.m_ShowErrorBars); } } - + if(typeof yErrorValuesPlus[index] !== 'undefined'){ if(typeof yErrorValuesMinus[index] !== 'undefined' && yErrorValuesMinus[index].length > 0) { trace["error_y"] = generateErrorBarsAsymmetric(yErrorValuesPlus[index], yErrorValuesMinus[index], chartData.m_ShowErrorBars); }else{ trace["error_y"] = generateErrorBars(yErrorValuesPlus[index], chartData.m_ShowErrorBars); } } // ===================== CHART TYPE OPTIONS HANDLING =========== - // initialize line object - trace["line"] = {} - trace["line"]["color"] = dataProperties[dataLabels[index]]["color"] - if (chartType == "scatter"){ - } else if (chartType == "area"){ - trace["fill"] = 'tozeroy' - } else if (chartType == "spline"){ - trace["line"]["shape"] = 'spline' - } else if (chartType == "scatterOnly"){ - trace["mode"] = 'markers'; - } else if (chartType == "area-spline"){ - trace["fill"] = 'tozeroy' - trace["line"]["shape"] = 'spline' - } // handle marker visibility/size/color trace["marker"] = {size: chartData.m_DataPointSize, color: dataProperties[dataLabels[index]]["color"]} if (chartData.m_DataPointSize == 0){ trace["mode"] = "lines"; } if (dataProperties[dataLabels[index]]["style"] == "dashed"){ trace["line"]["dash"] = "dot" } data.push(trace) } return data; } /** * Here, the chart magic takes place. Plot.ly is called. * * @param {object} chartData - containing the options for plotting, not the actual values */ function generateChart(chartData) { console.log("generate chart"); if (chartData == undefined) { chartData = {} } if (dataLabels == undefined) { dataLabels = [] } //=============================== DATA ======================== var data = []; if (chartData.m_StackedData){ data = generateStackPlotData(); } else { data = generatePlotData(); } //=============================== STYLE ======================== let marginTop = chartData.m_chartTitle == undefined ? 10 : 50; - if (chartData.m_LegendPosition == "bottomMiddle"){ - var legendX = 0.5; - var legendY = -0.75; - } - else if (chartData.m_LegendPosition == "bottomRight"){ - var legendX = 1; - var legendY = 0; - } - else if (chartData.m_LegendPosition == "topRight"){ - var legendX = 1; - var legendY = 1; - } - else if (chartData.m_LegendPosition == "topLeft"){ - var legendX = 0; - var legendY = 1; - } - else if (chartData.m_LegendPosition == "middleRight"){ - var legendX = 1; - var legendY = 0.5; - } + let legendPosition = generateLegendPosition(chartData.m_LegendPosition); + var layout = { paper_bgcolor : backgroundColor, plot_bgcolor : backgroundColor, title: { text:chartData.m_chartTitle, font: { color: foregroundColor } }, xaxis: { title: { text: chartData.m_xAxisLabel }, color: foregroundColor }, yaxis: { title: { text:chartData.m_yAxisLabel }, color: foregroundColor }, margin: { l: 50, r: 10, b: 40, t: marginTop, pad: 4 }, showlegend: chartData.m_ShowLegend, - legend: { - x: legendX, - y: legendY, - font : { - color: foregroundColor - } - } + legend: legendPosition }; - + if (chartData.m_StackedData){ layout["barmode"] = 'stack'; - } - + } + if (chartData.m_YAxisScale){ layout.yaxis["type"] = "log" } if (chartData.m_ShowSubchart){ layout.xaxis.rangeslider = {}; // adds range slider below x axis } - Plotly.newPlot('chart', data, layout, {displayModeBar: false, responsive: true}); + Plotly.plot('chart', data, layout, {displayModeBar: false, responsive: true}); if (minValueX !== null && maxValueX !== null){ UpdateMinMaxValueXView(minValueX, maxValueX); } if (minValueY !== null && maxValueY !== null){ UpdateMinMaxValueYView(minValueY, maxValueY); } } /** * Change theme of chart. * * @param {string} color - dark or not dark */ function changeTheme(color) { setThemeColors(color); link = document.getElementsByTagName("link")[0]; if (color == 'dark') { link.href = "Chart_dark.css"; } else { link.href = "Chart.css"; } }; /** * Reload the chart with the given arguments. * * This method is called by C++. Changes on signature with caution. */ function Reload(){ console.log("Reload chart"); generateChart(chartData); } function SetShowSubchart(showSubchart) { chartData.m_ShowSubchart = showSubchart; } function setThemeColors(theme){ if (theme == 'dark'){ backgroundColor = '#2d2d30'; foregroundColor = 'white'; } else { backgroundColor = '#f0f0f0'; foregroundColor = 'black'; } } function SetStackDataString(stackDataString) { chartData.m_StackedData = stackDataString; } function SetShowErrorBars(showErrorBars) { chartData.m_ShowErrorBars = showErrorBars; } /** * Zooms to the given x-axis min and max values. */ function UpdateMinMaxValueXView(minValueX, maxValueX) { //y-Axis can't be adapted for now. See https://github.com/plotly/plotly.js/issues/1876 let chart = document.getElementById("chart"); let update = { xaxis:{ range:[minValueX, maxValueX] } }; Plotly.relayout(chart, update); } /** * Zooms to the given y-axis min and max values. */ function UpdateMinMaxValueYView(minValueY, maxValueY) { //x-Axis can't be adapted for now. See https://github.com/plotly/plotly.js/issues/1876 let chart = document.getElementById("chart"); let update = { yaxis:{ range:[minValueY, maxValueY] } }; Plotly.relayout(chart, update); } /** * Transforms the view to another chart type. * * This method is called by C++. Changes on signature with caution. * @param {string} transformTo - 'line' or 'bar' */ function transformView(transformTo) { console.log("transform view"); console.log(transformTo); dataProperties[dataLabels[0]]["chartType"] = transformTo; // preserve chartType for later updates let plotlyType = getPlotlyChartType(transformTo); let chart = document.getElementById("chart"); let update = {type : plotlyType}; Plotly.restyle(chart, update, 0); // updates the given plotly trace at index 0 with an update object built of a standard trace object }; diff --git a/Modules/Chart/src/QmitkChartWidget.cpp b/Modules/Chart/src/QmitkChartWidget.cpp index 4d258b7083..c4c21ca825 100644 --- a/Modules/Chart/src/QmitkChartWidget.cpp +++ b/Modules/Chart/src/QmitkChartWidget.cpp @@ -1,732 +1,753 @@ /*=================================================================== 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 #include #include "mitkExceptionMacro.h" #include #include class CustomPage : public QWebEnginePage { public: CustomPage(QObject *parent = 0) : QWebEnginePage(parent) {} virtual void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel /*level*/, const QString &message, int lineNumber, const QString & /*sourceID*/) { MITK_INFO << "JS > " << lineNumber << ": " << message.toStdString(); } }; 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 UpdateData1D(const std::vector &data1D, const std::string &label); + void UpdateData2D(const std::map &data2D, const std::string &label); void RemoveData(const std::string &label); + void UpdateLabel(const std::string &existingLabel, const std::string &newLabel); + 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 SetPieLabels(const std::vector &pieLabels, const std::string &label); void SetTitle(const std::string &title); void SetXErrorBars(const std::string &label, const std::vector &errorPlus, const std::vector &errorMinus = std::vector()); void SetYErrorBars(const std::string &label, const std::vector &errorPlus, const std::vector &errorMinus = std::vector()); std::string GetThemeName() const; void SetThemeName(ColorTheme style); - void SetChartType(QmitkChartWidget::ChartType chartType); void SetLegendPosition(LegendPosition position); - void SetChartTypeByLabel(const std::string &label, QmitkChartWidget::ChartType chartType); void Show(bool showSubChart); void SetShowLegend(bool show); void SetShowErrorBars(bool show); void SetStackedData(bool stacked); void SetShowDataPoints(bool showDataPoints = false); + void SetShowSubchart(bool showSubChart); + void SetChartType(const std::string &label, QmitkChartWidget::ChartType chartType); void SetMinMaxValueXView(double minValueX, double maxValueX); void SetMinMaxValueYView(double minValueY, double maxValueY); QList ConvertErrorVectorToQList(const std::vector &error); QList ConvertVectorToQList(const std::vector &vec); std::string ConvertChartTypeToString(QmitkChartWidget::ChartType chartType) const; void ClearJavaScriptChart(); void InitializeJavaScriptChart(); void CallJavaScriptFuntion(const QString &command); QSize sizeHint() const; 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_ColorThemeToName; std::map m_LegendPositionToName; std::map m_LineStyleToName; std::map m_AxisScaleToName; }; std::string QmitkChartWidget::Impl::GetThemeName() const { return m_C3Data.GetThemeName().toString().toStdString(); } 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); m_WebEngineView->setPage(new CustomPage()); // Set the webengineview to an initial empty page. The actual chart will be loaded once the data is calculated. m_WebEngineView->load(QUrl(QStringLiteral("qrc:///Chart/empty.html"))); m_WebEngineView->page()->setWebChannel(m_WebChannel); m_WebEngineView->settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, false); connect(m_WebEngineView, SIGNAL(loadFinished(bool)), parent, SLOT(OnLoadFinished(bool))); auto layout = new QGridLayout(parent); layout->setMargin(0); layout->addWidget(m_WebEngineView); 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::bottomMiddle, "bottomMiddle"); m_LegendPositionToName.emplace(LegendPosition::bottomRight, "bottomRight"); m_LegendPositionToName.emplace(LegendPosition::topRight, "topRight"); m_LegendPositionToName.emplace(LegendPosition::topLeft, "topLeft"); m_LegendPositionToName.emplace(LegendPosition::middleRight, "middleRight"); m_LineStyleToName.emplace(LineStyle::solid, "solid"); m_LineStyleToName.emplace(LineStyle::dashed, "dashed"); m_AxisScaleToName.emplace(AxisScale::linear, ""); m_AxisScaleToName.emplace(AxisScale::log, "log"); m_ColorThemeToName.emplace(ColorTheme::lightstyle, "light"); m_ColorThemeToName.emplace(ColorTheme::darkstyle, "dark"); } QmitkChartWidget::Impl::~Impl() {} 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 type) { 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, type); } void QmitkChartWidget::Impl::AddData2D(const std::map &data2D, const std::string &label, QmitkChartWidget::ChartType type) { QMap data2DConverted; for (const auto &aValue : data2D) { data2DConverted.insert(aValue.first, aValue.second); } const std::string chartTypeName(m_ChartTypeToName.at(type)); auto definedLabels = GetDataLabels(m_C3xyData); auto uniqueLabel = GetUniqueLabelName(definedLabels, label); if (type == ChartType::scatter) { SetShowDataPoints(true); MITK_INFO << "Enabling data points for all because of scatter plot"; } + unsigned int sizeOfC3xyData = static_cast(m_C3xyData.size()); + m_C3xyData.push_back(std::make_unique(data2DConverted, + QVariant(QString::fromStdString(uniqueLabel)), + QVariant(QString::fromStdString(chartTypeName)), + QVariant(sizeOfC3xyData))); +} + +void QmitkChartWidget::Impl::UpdateData1D(const std::vector &data1D, const std::string &label) +{ + std::map transformedData2D; + unsigned int count = 0; + // transform the 1D data to 2D data + for (const auto &ele : data1D) + { + transformedData2D[count] = ele; + count++; + } + + UpdateData2D(transformedData2D, label); +} - m_C3xyData.push_back(std::make_unique( - data2DConverted, QVariant(QString::fromStdString(uniqueLabel)), QVariant(QString::fromStdString(chartTypeName)))); +void QmitkChartWidget::Impl::UpdateData2D(const std::map &data2D, const std::string &label) +{ + auto element = GetDataElementByLabel(label); + if (element) + { + QMap data2DConverted; + for (const auto &aValue : data2D) + { + data2DConverted.insert(aValue.first, aValue.second); + } + element->SetData(data2DConverted); + } } 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(); } +void QmitkChartWidget::Impl::UpdateLabel(const std::string &existingLabel, const std::string &newLabel) { + auto element = GetDataElementByLabel(existingLabel); + if (element) + { + auto definedLabels = GetDataLabels(m_C3xyData); + auto uniqueLabel = GetUniqueLabelName(definedLabels, newLabel); + element->SetLabel(QString::fromStdString(uniqueLabel)); + } +} + 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(colorChecked))); } } void QmitkChartWidget::Impl::SetLineStyle(const std::string &label, LineStyle style) { auto element = GetDataElementByLabel(label); 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::SetPieLabels(const std::vector &pieLabels, const std::string &label) { auto element = GetDataElementByLabel(label); if (element) { if (element->GetChartType() == QVariant("pie")) { auto dataY = element->GetYData(); element->SetPieLabels(ConvertVectorToQList(pieLabels)); if (static_cast(dataY.size()) != pieLabels.size()) { MITK_INFO << "data has " << dataY.size() << " entries whereas pie labels have " << pieLabels.size() << " entries. Unnamed pie labels automatically get a numerical label."; } } else { MITK_INFO << "label" << label << "has chart type " << element->GetChartType().toString().toStdString() << ", but pie is required"; } } } void QmitkChartWidget::Impl::SetTitle(const std::string &title) { m_C3Data.SetTitle(QString::fromStdString(title)); } void QmitkChartWidget::Impl::SetThemeName(QmitkChartWidget::ColorTheme style) { const std::string themeName(m_ColorThemeToName.at(style)); m_C3Data.SetThemeName(QString::fromStdString(themeName)); } -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::Show(bool showSubChart) { if (m_C3xyData.empty()) { MITK_WARN << "no data available for display in chart"; } else { m_C3Data.SetAppearance(showSubChart, m_C3xyData.front()->GetChartType() == QVariant("pie")); } InitializeJavaScriptChart(); } void QmitkChartWidget::Impl::SetShowLegend(bool show) { m_C3Data.SetShowLegend(show); } void QmitkChartWidget::Impl::SetStackedData(bool stacked) { m_C3Data.SetStackedData(stacked); } void QmitkChartWidget::Impl::SetShowErrorBars(bool show) { m_C3Data.SetShowErrorBars(show); } void QmitkChartWidget::Impl::SetShowDataPoints(bool showDataPoints) { if (showDataPoints == true) { m_C3Data.SetDataPointSize(6.5); } else { m_C3Data.SetDataPointSize(0); } } +void QmitkChartWidget::Impl::SetShowSubchart(bool showSubChart) { + m_C3Data.SetShowSubchart(showSubChart); +} + void QmitkChartWidget::Impl::SetChartType(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"; } const std::string chartTypeName(m_ChartTypeToName.at(chartType)); element->SetChartType(QVariant(QString::fromStdString(chartTypeName))); } } void QmitkChartWidget::Impl::SetMinMaxValueXView(double minValueX, double maxValueX) { m_C3Data.SetMinValueXView(minValueX); m_C3Data.SetMaxValueXView(maxValueX); } void QmitkChartWidget::Impl::SetMinMaxValueYView(double minValueY, double maxValueY) { m_C3Data.SetMinValueYView(minValueY); m_C3Data.SetMaxValueYView(maxValueY); } QList QmitkChartWidget::Impl::ConvertErrorVectorToQList(const std::vector &error) { QList errorConverted; for (const auto &aValue : error) { errorConverted.append(aValue); } return errorConverted; } QList QmitkChartWidget::Impl::ConvertVectorToQList(const std::vector &vec) { QList vecConverted; for (const auto &aValue : vec) { vecConverted.append(QString::fromStdString(aValue)); } return vecConverted; } void QmitkChartWidget::Impl::SetXErrorBars(const std::string &label, const std::vector &errorPlus, const std::vector &errorMinus) { auto element = GetDataElementByLabel(label); if (element) { auto errorConvertedPlus = ConvertErrorVectorToQList(errorPlus); auto errorConvertedMinus = ConvertErrorVectorToQList(errorMinus); element->SetXErrorDataPlus(errorConvertedPlus); element->SetXErrorDataMinus(errorConvertedMinus); } } void QmitkChartWidget::Impl::SetYErrorBars(const std::string &label, const std::vector &errorPlus, const std::vector &errorMinus) { auto element = GetDataElementByLabel(label); if (element) { auto errorConvertedPlus = ConvertErrorVectorToQList(errorPlus); auto errorConvertedMinus = ConvertErrorVectorToQList(errorMinus); element->SetYErrorDataPlus(errorConvertedPlus); element->SetYErrorDataMinus(errorConvertedMinus); } } std::string QmitkChartWidget::Impl::ConvertChartTypeToString(QmitkChartWidget::ChartType chartType) const { return m_ChartTypeToName.at(chartType); } QSize QmitkChartWidget::Impl::sizeHint() const { return QSize(400, 300); } void QmitkChartWidget::Impl::CallJavaScriptFuntion(const QString &command) { m_WebEngineView->page()->runJavaScript(command); } void QmitkChartWidget::Impl::ClearJavaScriptChart() { m_WebEngineView->load(QUrl(QStringLiteral("qrc:///Chart/empty.html"))); } void QmitkChartWidget::Impl::InitializeJavaScriptChart() { auto alreadyRegisteredObjects = m_WebChannel->registeredObjects(); auto alreadyRegisteredObjectsValues = alreadyRegisteredObjects.values(); // only register objects that have not been registered yet if (alreadyRegisteredObjectsValues.indexOf(&m_C3Data) == -1) { m_WebChannel->registerObject(QStringLiteral("chartData"), &m_C3Data); } unsigned count = 0; for (auto &xyData : m_C3xyData) { // only register objects that have not been registered yet if (alreadyRegisteredObjectsValues.indexOf(xyData.get()) == -1) { QString variableName = "xyData" + QString::number(count); m_WebChannel->registerObject(variableName, xyData.get()); } count++; } m_WebEngineView->load(QUrl(QStringLiteral("qrc:///Chart/QmitkChartWidget.html"))); } 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(); } QmitkChartWidget::QmitkChartWidget(QWidget *parent) : QWidget(parent), m_Impl(new Impl(this)) { connect(this, &QmitkChartWidget::PageSuccessfullyLoaded, this, &QmitkChartWidget::OnPageSuccessfullyLoaded); } QmitkChartWidget::~QmitkChartWidget() {} void QmitkChartWidget::AddData2D(const std::map &data2D, const std::string &label, ChartType type) { m_Impl->AddData2D(data2D, label, type); } 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::UpdateData1D(const std::vector &data1D, const std::string &label) +{ + m_Impl->UpdateData1D(data1D, label); +} + +void QmitkChartWidget::UpdateData2D(const std::map &data2D, const std::string &label) +{ + m_Impl->UpdateData2D(data2D, label); +} + void QmitkChartWidget::RemoveData(const std::string &label) { m_Impl->RemoveData(label); } +void QmitkChartWidget::UpdateLabel(const std::string &existingLabel, const std::string &newLabel) { + m_Impl->UpdateLabel(existingLabel, newLabel); +} + void QmitkChartWidget::SetXAxisLabel(const std::string &label) { m_Impl->SetXAxisLabel(label); } void QmitkChartWidget::SetYAxisLabel(const std::string &label) { m_Impl->SetYAxisLabel(label); } void QmitkChartWidget::SetPieLabels(const std::vector &pieLabels, const std::string &label) { m_Impl->SetPieLabels(pieLabels, label); } void QmitkChartWidget::SetTitle(const std::string &title) { m_Impl->SetTitle(title); } void QmitkChartWidget::SetShowDataPoints(bool showDataPoints) { m_Impl->SetShowDataPoints(showDataPoints); } void QmitkChartWidget::SetChartType(const std::string &label, ChartType type) { m_Impl->SetChartType(label, type); } void QmitkChartWidget::SetXErrorBars(const std::string &label, const std::vector &errorPlus, const std::vector &errorMinus) { m_Impl->SetXErrorBars(label, errorPlus, errorMinus); } void QmitkChartWidget::SetYErrorBars(const std::string &label, const std::vector &errorPlus, const std::vector &errorMinus) { m_Impl->SetYErrorBars(label, errorPlus, errorMinus); } 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::Clear() { m_Impl->ClearData(); m_Impl->ClearJavaScriptChart(); } void QmitkChartWidget::OnLoadFinished(bool isLoadSuccessful) { if (isLoadSuccessful) { emit PageSuccessfullyLoaded(); } } void QmitkChartWidget::OnPageSuccessfullyLoaded() { auto themeName = m_Impl->GetThemeName(); QString command; if (themeName == "dark") { command = QString("changeTheme('dark')"); } else { command = QString("changeTheme('light')"); } m_Impl->CallJavaScriptFuntion(command); } -std::string QmitkChartWidget::convertBooleanValue(bool value) const -{ - std::stringstream converter; - converter << std::boolalpha << value; - return converter.str(); -} - -void QmitkChartWidget::SetChartTypeForAllDataAndReload(ChartType type) -{ - m_Impl->SetChartType(type); -} - void QmitkChartWidget::SetTheme(ColorTheme themeEnabled) { m_Impl->SetThemeName(themeEnabled); } void QmitkChartWidget::SetShowSubchart(bool showSubChart) { - QString subChartString = QString::fromStdString(convertBooleanValue(showSubChart)); - const QString command = QString("SetShowSubchart(" + subChartString + ")"); - m_Impl->CallJavaScriptFuntion(command); + m_Impl->SetShowSubchart(showSubChart); } void QmitkChartWidget::SetShowErrorBars(bool showErrorBars) { m_Impl->SetShowErrorBars(showErrorBars); } void QmitkChartWidget::SetMinMaxValueXView(double minValueX, double maxValueX) { m_Impl->SetMinMaxValueXView(minValueX, maxValueX); } void QmitkChartWidget::SetMinMaxValueYView(double minValueY, double maxValueY) { m_Impl->SetMinMaxValueYView(minValueY, maxValueY); } void QmitkChartWidget::Reload() { const QString command = QString("Reload()"); m_Impl->CallJavaScriptFuntion(command); } QSize QmitkChartWidget::sizeHint() const { return m_Impl->sizeHint(); } diff --git a/Modules/Chart/src/QmitkChartxyData.cpp b/Modules/Chart/src/QmitkChartxyData.cpp index 8ccfd3a9e5..17d2755c38 100644 --- a/Modules/Chart/src/QmitkChartxyData.cpp +++ b/Modules/Chart/src/QmitkChartxyData.cpp @@ -1,36 +1,47 @@ /*=================================================================== 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 -QmitkChartxyData::QmitkChartxyData(const QMap& data, const QVariant& label, const QVariant& chartType) : m_Label(label), m_ChartType(chartType), m_Color(""), m_LineStyleName("solid") { +QmitkChartxyData::QmitkChartxyData(const QMap &data, + const QVariant &label, + const QVariant &chartType, + const QVariant &position) + : m_LabelCount(position), m_Label(label), m_ChartType(chartType) +{ SetData(data); } -void QmitkChartxyData::SetData(const QMap& data) +void QmitkChartxyData::SetData(const QMap &data) { - for (const auto& entry : data.toStdMap()) - { - m_XData.push_back(entry.first); - m_YData.push_back(entry.second); - } + ClearData(); + for (const auto &entry : data.toStdMap()) + { + m_XData.push_back(entry.first); + m_YData.push_back(entry.second); + } + emit SignalDataChanged(m_YData); } void QmitkChartxyData::ClearData() { this->m_YData.clear(); this->m_XData.clear(); + this->m_XErrorDataPlus.clear(); + this->m_XErrorDataMinus.clear(); + this->m_YErrorDataPlus.clear(); + this->m_YErrorDataMinus.clear(); } diff --git a/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.cpp b/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.cpp index 53facd3a92..599961e1f4 100644 --- a/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.cpp +++ b/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.cpp @@ -1,388 +1,455 @@ /*=================================================================== 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. ===================================================================*/ // Blueberry #include #include // Qmitk #include "ChartExample.h" // Qt #include const std::string ChartExample::VIEW_ID = "org.mitk.views.chartexample"; void ChartExample::SetFocus() { m_Controls.m_buttonCreateChart->setFocus(); } void ChartExample::CreateQtPartControl(QWidget *parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); - connect(m_Controls.m_buttonCreateChart, &QPushButton::clicked, this, &ChartExample::CreateChart); - connect(m_Controls.m_buttonClearChart, &QPushButton::clicked, this, &ChartExample::ClearChart); - connect(m_Controls.m_buttonAddData, &QPushButton::clicked, this, &ChartExample::AddData); - connect(m_Controls.m_checkBoxEnableDataX, &QCheckBox::toggled, this, &ChartExample::ShowXData); - connect(m_Controls.m_checkBoxEnableErrors, &QCheckBox::toggled, this, &ChartExample::ShowErrorOptions); - connect(m_Controls.m_checkBoxEnableXErrors, &QCheckBox::toggled, this, &ChartExample::ShowXErrorOptions); - connect(m_Controls.m_checkBoxEnableYErrors, &QCheckBox::toggled, this, &ChartExample::ShowYErrorOptions); - connect(m_Controls.m_doubleSpinBox_minZoomX, &QSpinBox::editingFinished, this, &ChartExample::AdaptZoomX); - connect(m_Controls.m_doubleSpinBox_maxZoomX, &QSpinBox::editingFinished, this, &ChartExample::AdaptZoomX); - connect(m_Controls.m_doubleSpinBox_minZoomY, &QSpinBox::editingFinished, this, &ChartExample::AdaptZoomY); - connect(m_Controls.m_doubleSpinBox_maxZoomY, &QSpinBox::editingFinished, this, &ChartExample::AdaptZoomY); + CreateConnectionsForGUIElements(); connect(m_Controls.m_comboBoxChartType, &QComboBox::currentTextChanged, this, &ChartExample::AdaptDataGUI); m_Controls.m_groupBoxErrors->setVisible(false); m_Controls.m_groupBoxXErrors->setVisible(false); m_Controls.m_groupBoxYErrors->setVisible(false); m_Controls.m_lineEditDataXVector->setVisible(false); m_Controls.m_lineEditDataXVector->setText("0;1;2;3;4;5;6;7;8;9"); ResetDataGUI(); m_Controls.m_doubleSpinBox_maxZoomX->setValue(10); m_Controls.m_doubleSpinBox_maxZoomY->setValue(10); FillRandomDataValues(); auto chartStyle = GetColorTheme(); m_Controls.m_Chart->SetTheme(chartStyle); m_Controls.m_lineEditXAxisLabel->setText("xLabel"); m_Controls.m_lineEditYAxisLabel->setText("yLabel"); m_ChartNameToChartType.emplace("bar", QmitkChartWidget::ChartType::bar); m_ChartNameToChartType.emplace("line", QmitkChartWidget::ChartType::line); m_ChartNameToChartType.emplace("spline", QmitkChartWidget::ChartType::spline); m_ChartNameToChartType.emplace("pie", QmitkChartWidget::ChartType::pie); m_ChartNameToChartType.emplace("area", QmitkChartWidget::ChartType::area); m_ChartNameToChartType.emplace("area-spline", QmitkChartWidget::ChartType::area_spline); m_ChartNameToChartType.emplace("scatter", QmitkChartWidget::ChartType::scatter); m_LineNameToLineType.emplace("solid", QmitkChartWidget::LineStyle::solid); m_LineNameToLineType.emplace("dashed", QmitkChartWidget::LineStyle::dashed); m_AxisScaleNameToAxisScaleType.emplace("linear", QmitkChartWidget::AxisScale::linear); m_AxisScaleNameToAxisScaleType.emplace("logarithmic", QmitkChartWidget::AxisScale::log); m_LegendPositionNameToLegendPositionType.emplace("bottom middle", QmitkChartWidget::LegendPosition::bottomMiddle); m_LegendPositionNameToLegendPositionType.emplace("bottom right", QmitkChartWidget::LegendPosition::bottomRight); m_LegendPositionNameToLegendPositionType.emplace("top right", QmitkChartWidget::LegendPosition::topRight); m_LegendPositionNameToLegendPositionType.emplace("top left", QmitkChartWidget::LegendPosition::topLeft); m_LegendPositionNameToLegendPositionType.emplace("middle right", QmitkChartWidget::LegendPosition::middleRight); } +void ChartExample::CreateConnectionsForGUIElements() +{ + connect(m_Controls.m_buttonCreateChart, &QPushButton::clicked, this, &ChartExample::CreateChart); + connect(m_Controls.m_buttonUpdateChart, &QPushButton::clicked, this, &ChartExample::UpdateChart); + connect(m_Controls.m_buttonClearChart, &QPushButton::clicked, this, &ChartExample::ClearChart); + connect(m_Controls.m_buttonAddData, &QPushButton::clicked, this, &ChartExample::AddData); + connect(m_Controls.m_checkBoxEnableDataX, &QCheckBox::toggled, this, &ChartExample::ShowXData); + connect(m_Controls.m_checkBoxEnableErrors, &QCheckBox::toggled, this, &ChartExample::ShowErrorOptions); + connect(m_Controls.m_checkBoxEnableXErrors, &QCheckBox::toggled, this, &ChartExample::ShowXErrorOptions); + connect(m_Controls.m_checkBoxEnableYErrors, &QCheckBox::toggled, this, &ChartExample::ShowYErrorOptions); + connect(m_Controls.m_doubleSpinBox_minZoomX, &QSpinBox::editingFinished, this, &ChartExample::AdaptZoomX); + connect(m_Controls.m_doubleSpinBox_maxZoomX, &QSpinBox::editingFinished, this, &ChartExample::AdaptZoomX); + connect(m_Controls.m_doubleSpinBox_minZoomY, &QSpinBox::editingFinished, this, &ChartExample::AdaptZoomY); + connect(m_Controls.m_doubleSpinBox_maxZoomY, &QSpinBox::editingFinished, this, &ChartExample::AdaptZoomY); + connect(m_Controls.m_comboBoxLegendPosition, &QComboBox::currentTextChanged, this, &ChartExample::OnLegendPositionChanged); + connect(m_Controls.m_lineEditTitle, &QLineEdit::editingFinished, this, &ChartExample::OnTitleChanged); + connect(m_Controls.m_lineEditXAxisLabel, &QLineEdit::editingFinished, this, &ChartExample::OnXAxisLabelChanged); + connect(m_Controls.m_lineEditYAxisLabel, &QLineEdit::editingFinished, this, &ChartExample::OnYAxisLabelChanged); + connect( + m_Controls.m_comboBoxYAxisScale, &QComboBox::currentTextChanged, this, &ChartExample::OnYAxisScaleChanged); + connect(m_Controls.m_checkBoxShowLegend, &QCheckBox::stateChanged, this, &ChartExample::OnShowLegendChanged); + connect(m_Controls.m_checkBoxStackedData, &QCheckBox::stateChanged, this, &ChartExample::OnStackedDataChanged); + connect(m_Controls.m_checkBoxShowDataPoints, &QCheckBox::stateChanged, this, &ChartExample::OnShowDataPointsChanged); + connect(m_Controls.m_checkBoxShowSubchart, &QCheckBox::stateChanged, this, &ChartExample::OnShowSubchartChanged); +} + void ChartExample::FillRandomDataValues() { std::vector numbers = GenerateRandomNumbers(10, 10.0); std::string text = ConvertToText(numbers); m_Controls.m_lineEditDataYVector->setText(QString::fromStdString(text)); m_Controls.m_lineEditDataLabel->setText("test" + QString::number(countForUID)); numbers = GenerateRandomNumbers(10, 10.0); text = ConvertToText(numbers); m_Controls.m_lineEditXErrorPlus->setText(QString::fromStdString(text)); numbers = GenerateRandomNumbers(10, 10.0); text = ConvertToText(numbers); m_Controls.m_lineEditXErrorMinus->setText(QString::fromStdString(text)); numbers = GenerateRandomNumbers(10, 10.0); text = ConvertToText(numbers); m_Controls.m_lineEditYErrorPlus->setText(QString::fromStdString(text)); numbers = GenerateRandomNumbers(10, 10.0); text = ConvertToText(numbers); m_Controls.m_lineEditYErrorMinus->setText(QString::fromStdString(text)); countForUID++; } void ChartExample::CreateChart() { auto dataYAxisScaleType = m_AxisScaleNameToAxisScaleType.at(m_Controls.m_comboBoxYAxisScale->currentText().toStdString()); auto xAxisLabel = m_Controls.m_lineEditXAxisLabel->text().toStdString(); auto yAxisLabel = m_Controls.m_lineEditYAxisLabel->text().toStdString(); auto showLegend = m_Controls.m_checkBoxShowLegend->isChecked(); auto legendPosition = m_LegendPositionNameToLegendPositionType.at(m_Controls.m_comboBoxLegendPosition->currentText().toStdString()); auto showDataPoints = m_Controls.m_checkBoxShowDataPoints->isChecked(); auto stackedData = m_Controls.m_checkBoxStackedData->isChecked(); auto showSubchart = m_Controls.m_checkBoxShowSubchart->isChecked(); - auto title = m_Controls.title->text().toStdString(); + auto title = m_Controls.m_lineEditTitle->text().toStdString(); m_Controls.m_Chart->SetTitle(title); m_Controls.m_Chart->SetYAxisScale(dataYAxisScaleType); m_Controls.m_Chart->SetXAxisLabel(xAxisLabel); m_Controls.m_Chart->SetYAxisLabel(yAxisLabel); m_Controls.m_Chart->SetShowLegend(showLegend); m_Controls.m_Chart->SetLegendPosition(legendPosition); m_Controls.m_Chart->SetShowErrorBars(true); m_Controls.m_Chart->SetShowDataPoints(showDataPoints); m_Controls.m_Chart->SetStackedData(stackedData); m_Controls.m_Chart->Show(showSubchart); } +void ChartExample::UpdateChart() { + // Test update mechanism + m_Controls.m_Chart->SetLineStyle("test0", QmitkChartWidget::LineStyle::dashed); + m_Controls.m_Chart->SetChartType("test0", QmitkChartWidget::ChartType::spline); + m_Controls.m_Chart->SetColor("test0", "violet"); + m_Controls.m_Chart->UpdateData2D({{0, 1}, {0.1, 2}, {0.2, 3}, {8, -2} }, "test0"); + m_Controls.m_Chart->UpdateLabel("test0", "newLabel"); +} + void ChartExample::ClearChart() { m_Controls.m_Chart->Clear(); m_Controls.m_plainTextEditDataView->clear(); } std::vector ChartExample::ConvertToDoubleVector(const QString &data, QChar delimiter) const { std::vector output; if (data.isEmpty()) { return output; } for (const QString entry : data.split(delimiter)) { output.push_back(entry.toDouble()); } return output; } std::vector ChartExample::ConvertToStringVector(const QString &data, QChar delimiter) const { std::vector output; if (data.isEmpty()) { return output; } for (const QString entry : data.split(delimiter)) { output.push_back(entry.toStdString()); } return output; } void ChartExample::AddData() { QString data = m_Controls.m_lineEditDataYVector->text(); auto dataY = ConvertToDoubleVector(data); auto chartType = m_ChartNameToChartType.at(m_Controls.m_comboBoxChartType->currentText().toStdString()); std::string dataLabel = m_Controls.m_lineEditDataLabel->text().toStdString(); std::string dataColor = m_Controls.m_lineEditColor->text().toStdString(); auto dataLineStyleType = m_LineNameToLineType.at(m_Controls.m_comboBoxLineStyle->currentText().toStdString()); if (m_Controls.m_checkBoxEnableDataX->isChecked()) { QString lineEditDataX = m_Controls.m_lineEditDataXVector->text(); auto dataX = ConvertToDoubleVector(lineEditDataX); if (dataX.size() != dataY.size()) { mitkThrow() << "data x and y size have to be equal"; } auto dataXandY = CreateMap(dataX, dataY); data = QString::fromStdString(ConvertToText(dataXandY)); m_Controls.m_Chart->AddData2D(dataXandY, dataLabel, chartType); } else { m_Controls.m_Chart->AddData1D(dataY, dataLabel, chartType); } if (!dataColor.empty()) { m_Controls.m_Chart->SetColor(dataLabel, dataColor); } if (chartType == QmitkChartWidget::ChartType::pie) { QString pieLabelsData = m_Controls.m_lineEditPieDataLabel->text(); if (!pieLabelsData.isEmpty()) { auto pieLabels = ConvertToStringVector(pieLabelsData); m_Controls.m_Chart->SetPieLabels(pieLabels, dataLabel); } } if (m_Controls.m_checkBoxEnableErrors->isChecked()) { if (m_Controls.m_checkBoxEnableXErrors->isChecked()) { auto errorsPlus = ConvertToDoubleVector(m_Controls.m_lineEditXErrorPlus->text()); auto errorsMinus = ConvertToDoubleVector(m_Controls.m_lineEditXErrorMinus->text()); m_Controls.m_Chart->SetXErrorBars(m_Controls.m_lineEditDataLabel->text().toStdString(), errorsPlus, errorsMinus); } if (m_Controls.m_checkBoxEnableYErrors->isChecked()) { auto errorsPlus = ConvertToDoubleVector(m_Controls.m_lineEditYErrorPlus->text()); auto errorsMinus = ConvertToDoubleVector(m_Controls.m_lineEditYErrorMinus->text()); m_Controls.m_Chart->SetYErrorBars(m_Controls.m_lineEditDataLabel->text().toStdString(), errorsPlus, errorsMinus); } } m_Controls.m_Chart->SetLineStyle(dataLabel, dataLineStyleType); QString dataOverview; dataOverview.append(m_Controls.m_lineEditDataLabel->text()); dataOverview.append("(").append(m_Controls.m_comboBoxChartType->currentText()); if (!dataColor.empty()) { dataOverview.append(", ").append(dataColor.c_str()); } dataOverview.append(", ").append(m_Controls.m_comboBoxLineStyle->currentText()); dataOverview.append(")"); dataOverview.append(":").append(data); m_Controls.m_plainTextEditDataView->appendPlainText(dataOverview); FillRandomDataValues(); } void ChartExample::ShowXData(bool show) { m_Controls.m_lineEditDataXVector->setVisible(show); } void ChartExample::ShowErrorOptions(bool show) { m_Controls.m_groupBoxErrors->setVisible(show); } void ChartExample::ShowXErrorOptions(bool show) { m_Controls.m_groupBoxXErrors->setVisible(show); } void ChartExample::ShowYErrorOptions(bool show) { m_Controls.m_groupBoxYErrors->setVisible(show); } void ChartExample::AdaptZoomX() { m_Controls.m_Chart->SetMinMaxValueXView(m_Controls.m_doubleSpinBox_minZoomX->value(), m_Controls.m_doubleSpinBox_maxZoomX->value()); m_Controls.m_Chart->Show(); } void ChartExample::AdaptZoomY() { m_Controls.m_Chart->SetMinMaxValueYView(m_Controls.m_doubleSpinBox_minZoomY->value(), m_Controls.m_doubleSpinBox_maxZoomY->value()); m_Controls.m_Chart->Show(); } void ChartExample::AdaptDataGUI(const QString &chartType) { ResetDataGUI(); auto chartTypeEnum = m_ChartNameToChartType.at(chartType.toStdString()); if (chartTypeEnum == QmitkChartWidget::ChartType::pie) { m_Controls.m_labelPieData->setVisible(true); m_Controls.m_lineEditPieDataLabel->setVisible(true); m_Controls.m_labelColor->setVisible(false); m_Controls.m_lineEditColor->setVisible(false); } else if (chartTypeEnum == QmitkChartWidget::ChartType::line || chartTypeEnum == QmitkChartWidget::ChartType::area || chartTypeEnum == QmitkChartWidget::ChartType::area_spline || chartTypeEnum == QmitkChartWidget::ChartType::spline) { m_Controls.m_labelLineStyle->setVisible(true); m_Controls.m_comboBoxLineStyle->setVisible(true); } } void ChartExample::ResetDataGUI() { m_Controls.m_labelPieData->setVisible(false); m_Controls.m_lineEditPieDataLabel->setVisible(false); m_Controls.m_labelColor->setVisible(true); m_Controls.m_lineEditColor->setVisible(true); m_Controls.m_labelLineStyle->setVisible(false); m_Controls.m_comboBoxLineStyle->setVisible(false); } std::vector ChartExample::GenerateRandomNumbers(unsigned int amount, double max) const { QRandomGenerator gen; gen.seed(time(NULL)); std::vector data; for (unsigned int i = 0; i < amount; i++) { data.push_back(gen.bounded(max)); } return data; } std::map ChartExample::CreateMap(std::vector keys, std::vector values) const { std::map aMap; std::transform(keys.begin(), keys.end(), values.begin(), std::inserter(aMap, aMap.end()), [](double a, double b) { return std::make_pair(a, b); }); return aMap; } std::string ChartExample::ConvertToText(std::vector numbers, std::string delimiter) const { std::ostringstream oss; oss.precision(3); if (!numbers.empty()) { for (auto number : numbers) { oss << number << delimiter; } } auto aString = oss.str(); aString.pop_back(); return aString; } std::string ChartExample::ConvertToText(std::map numbers, std::string delimiter) const { std::ostringstream oss; oss.precision(3); if (!numbers.empty()) { for (const auto keyValue : numbers) { oss << keyValue.first << ":" << keyValue.second << delimiter; } } auto aString = oss.str(); aString.pop_back(); return aString; } QmitkChartWidget::ColorTheme ChartExample::GetColorTheme() const { ctkPluginContext *context = berry::WorkbenchPlugin::GetDefault()->GetPluginContext(); ctkServiceReference styleManagerRef = context->getServiceReference(); if (styleManagerRef) { auto styleManager = context->getService(styleManagerRef); if (styleManager->GetStyle().name == "Dark") { return QmitkChartWidget::ColorTheme::darkstyle; } else { return QmitkChartWidget::ColorTheme::lightstyle; } } return QmitkChartWidget::ColorTheme::darkstyle; } + +void ChartExample::OnLegendPositionChanged(const QString &newText) +{ + auto legendPosition = m_LegendPositionNameToLegendPositionType.at(newText.toStdString()); + m_Controls.m_Chart->SetLegendPosition(legendPosition); +} + +void ChartExample::OnTitleChanged() { + auto newTitle = m_Controls.m_lineEditTitle->text(); + m_Controls.m_Chart->SetTitle(newTitle.toStdString()); +} + +void ChartExample::OnXAxisLabelChanged() { + auto newXAxisLabel = m_Controls.m_lineEditXAxisLabel->text(); + m_Controls.m_Chart->SetXAxisLabel(newXAxisLabel.toStdString()); +} + +void ChartExample::OnYAxisLabelChanged() { + auto newYAxisLabel = m_Controls.m_lineEditYAxisLabel->text(); + m_Controls.m_Chart->SetYAxisLabel(newYAxisLabel.toStdString()); +} + +void ChartExample::OnYAxisScaleChanged(const QString &newYAxisScale) { + auto yAxisScale = m_AxisScaleNameToAxisScaleType.at(newYAxisScale.toStdString()); + m_Controls.m_Chart->SetYAxisScale(yAxisScale); +} + +void ChartExample::OnShowLegendChanged(int newState) { + m_Controls.m_Chart->SetShowLegend(newState == Qt::Checked); +} + +void ChartExample::OnStackedDataChanged(int newState) { + m_Controls.m_Chart->SetStackedData(newState == Qt::Checked); +} + +void ChartExample::OnShowDataPointsChanged(int newState) { + m_Controls.m_Chart->SetShowDataPoints(newState == Qt::Checked); +} + +void ChartExample::OnShowSubchartChanged(int newState) { + m_Controls.m_Chart->SetShowSubchart(newState == Qt::Checked); +} diff --git a/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.h b/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.h index f07dc2f3c1..cb2671a574 100644 --- a/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.h +++ b/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.h @@ -1,79 +1,91 @@ /*=================================================================== 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 ChartExample_h #define ChartExample_h #include #include "ui_ChartExampleControls.h" /** \brief Basic example for use of module mitkChart \sa QmitkAbstractView \ingroup ${plugin_target}_internal */ class ChartExample : public QmitkAbstractView { // this is needed for all Qt objects that should have a Qt meta-object // (everything that derives from QObject and wants to have signal/slots) Q_OBJECT public: static const std::string VIEW_ID; protected: virtual void CreateQtPartControl(QWidget *parent) override; + void CreateConnectionsForGUIElements(); + virtual void SetFocus() override; void CreateChart(); + void UpdateChart(); void ClearChart(); void AddData(); void ShowXData(bool show); void ShowErrorOptions(bool show); void ShowXErrorOptions(bool show); void ShowYErrorOptions(bool show); void AdaptZoomX(); void AdaptZoomY(); void AdaptDataGUI(const QString &chartType); void ResetDataGUI(); private: void FillRandomDataValues(); std::vector GenerateRandomNumbers(unsigned int amount, double max) const; std::vector ConvertToDoubleVector(const QString &data, QChar delimiter = ';') const; std::vector ConvertToStringVector(const QString &data, QChar delimiter = ';') const; std::map CreateMap(std::vector keys, std::vector values) const; std::string ConvertToText(std::vector numbers, std::string delimiter = ";") const; std::string ConvertToText(std::map numbers, std::string delimiter = ";") const; QmitkChartWidget::ColorTheme GetColorTheme() const; + void OnLegendPositionChanged(const QString &newPosition); + void OnTitleChanged(); + void OnXAxisLabelChanged(); + void OnYAxisLabelChanged(); + void OnYAxisScaleChanged(const QString &newYAxisScale); + void OnShowLegendChanged(int newState); + void OnStackedDataChanged(int newState); + void OnShowDataPointsChanged(int newState); + void OnShowSubchartChanged(int newState); std::map m_ChartNameToChartType; std::map m_LineNameToLineType; std::map m_AxisScaleNameToAxisScaleType; std::map m_LegendPositionNameToLegendPositionType; unsigned int countForUID = 0; Ui::ChartExampleControls m_Controls; }; #endif // ChartExample_h diff --git a/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExampleControls.ui b/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExampleControls.ui index 1b2ad58200..e42d3e837d 100644 --- a/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExampleControls.ui +++ b/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExampleControls.ui @@ -1,678 +1,688 @@ ChartExampleControls 0 0 455 1054 0 0 QmitkTemplate 0 0 0 0 0 420 436 0 0 Data data entry 0 0 x y: error 0 0 Error values y error x error 0 0 GroupBox plus error minus error 0 0 GroupBox plus error 0 0 0 0 minus error data label 0 0 0 0 Chart type 0 0 bar line area pie spline area-spline scatter Color 0 0 Line style 0 0 solid dashed pie data label Add data 0 0 437 236 0 0 Global options QLayout::SetDefaultConstraint QFormLayout::AllNonFixedFieldsGrow Title - + XAxis label YAxis label Y Axis scale linear logarithmic Show legend true Stacked data Show data points true Show Subchart Legend position 2 bottom middle bottom right top right top left middle right 0 0 437 187 Zoom 0 0 431 121 x: y: Do image processing Create chart + + + + Do image processing + + + Update chart + + + Clear chart Qt::Vertical 0 0 0 250 0 0 0 0 0 16777215 150 Qt::Vertical QSizePolicy::Expanding 20 0 QmitkChartWidget QWidget
QmitkChartWidget.h