diff --git a/Modules/Chart/include/QmitkChartWidget.h b/Modules/Chart/include/QmitkChartWidget.h index aac14907fd..7d136d3561 100644 --- a/Modules/Chart/include/QmitkChartWidget.h +++ b/Modules/Chart/include/QmitkChartWidget.h @@ -1,276 +1,287 @@ /*=================================================================== 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); /*! * \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 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 Updates the min and max x values of the chart * \details Zooms in to view the values between minValue and maxValue in x direction */ void UpdateMinMaxValueXView(double minValueX,double maxValueX); /*! * \brief Updates the min and max y values of the chart * \details Zooms in to view the values between minValue and maxValue in y direction */ void UpdateMinMaxValueYView(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 c477050f1d..4e70950a81 100644 --- a/Modules/Chart/include/QmitkChartxyData.h +++ b/Modules/Chart/include/QmitkChartxyData.h @@ -1,157 +1,156 @@ /*=================================================================== 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. */ class QmitkChartxyData : public QObject { Q_OBJECT Q_PROPERTY(QVariant m_LabelCount READ GetLabelCount CONSTANT); - 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_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(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, const QVariant &position); // Constructor for Data2D (x:y=1:2, 2:6, 3:7) 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; - emit SignalYDataChanged(yData); + emit SignalDataChanged(yData); }; Q_INVOKABLE QList GetXData() const { return m_XData; }; Q_INVOKABLE void SetXData(const QList &xData) { m_XData = xData; - emit SignalXDataChanged(xData); + emit SignalDataChanged(xData); }; Q_INVOKABLE QList GetXErrorDataPlus() const { return m_XErrorDataPlus; }; 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; emit SignalErrorDataChanged(errorData); }; Q_INVOKABLE QList GetYErrorDataPlus() const { return m_YErrorDataPlus; }; 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; emit SignalErrorDataChanged(errorData); }; Q_INVOKABLE QVariant GetChartType() const { return m_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; emit SignalLabelChanged(label); }; Q_INVOKABLE QVariant GetColor() const { return m_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; emit SignalLineStyleChanged(lineStyle); }; /** * \brief Clears the Data. * * This function clears the 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 SignalLineStyleChanged(const QVariant lineStyle); private: /** 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_ChartType; QVariant m_Color = ""; QVariant m_LineStyleName = "solid"; }; #endif // QmitkC3xyData_h diff --git a/Modules/Chart/resource/Chart.js b/Modules/Chart/resource/Chart.js index 08ecb25e4d..b7bcc55314 100644 --- a/Modules/Chart/resource/Chart.js +++ b/Modules/Chart/resource/Chart.js @@ -1,576 +1,587 @@ document.body.style.backgroundColor = 'rgb(240, 240, 240)'; const minHeight = 255; var chart; var xErrorValuesPlus=[]; var xErrorValuesMinus=[]; var yErrorValuesPlus=[]; var yErrorValuesMinus=[]; var xValues=[]; var yValues=[]; var dataLabels=[]; var xs = {}; 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){ console.log("diagram type changed"); console.log(newValue); let updateDiagramType = generateTraceByChartType(newValue); Plotly.restyle('chart', updateDiagramType, position); }); registeredChannelObject.SignalLineStyleChanged.connect(function(newValue){ console.log("line style changed"); console.log(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.SignalXDataChanged.connect(function(newValue){ - console.log("xdata changed"); + 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.SignalYDataChanged.connect(function(newValue){ console.log("ydata changed"); }); } /** * 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 chartXYData = channel.objects[propertyName]; handleDataChangeEvents(chartXYData); let xDataTemp = chartXYData.m_XData; let yDataTemp = chartXYData.m_YData; let xErrorsTempPlus = chartXYData.m_XErrorDataPlus; 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; var tempLineStyle = ''; if (chartXYData.m_LineStyleName == "solid") { tempLineStyle = '' } else { tempLineStyle = "dashed" } dataProperties[dataLabel] = { "color" : chartXYData.m_Color, "chartType": chartXYData.m_ChartType, "style": tempLineStyle } count++; } } var theme = chartData.m_themeName; 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"]); trace["x"] = xValues[index].slice(1); trace["y"] = yValues[index].slice(1); trace["name"] = dataLabels[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 =========== trace["line"]["color"] = dataProperties[dataLabels[index]]["color"] // 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; 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: 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.plot('chart', data, layout, {displayModeBar: false, responsive: true}); } /** * 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/QmitkChartxyData.cpp b/Modules/Chart/src/QmitkChartxyData.cpp index e01acedd60..c93d5ca39a 100644 --- a/Modules/Chart/src/QmitkChartxyData.cpp +++ b/Modules/Chart/src/QmitkChartxyData.cpp @@ -1,43 +1,43 @@ /*=================================================================== 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, 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); - } - emit SignalXDataChanged(m_XData); - emit SignalYDataChanged(m_YData); + 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(); }