diff --git a/Modules/Chart/include/QmitkChartxyData.h b/Modules/Chart/include/QmitkChartxyData.h index bb2c20db44..c9c75e6725 100644 --- a/Modules/Chart/include/QmitkChartxyData.h +++ b/Modules/Chart/include/QmitkChartxyData.h @@ -1,106 +1,151 @@ /*=================================================================== 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( + 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); //Constructor for Data2D (x:y=1:2, 2:6, 3:7) + explicit QmitkChartxyData(const QMap &data, + const QVariant &label, + const QVariant &diagramType); // Constructor for Data2D (x:y=1:2, 2:6, 3:7) - void SetData(const QMap& data); + void SetData(const QMap &data); 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 SignalYDataChanged(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 SignalXDataChanged(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 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. + */ void ClearData(); signals: void SignalYDataChanged(const QList yData); void SignalXDataChanged(const QList xData); 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: - QList m_YData; - QList m_XData; - QList m_XErrorDataPlus; - QList m_XErrorDataMinus; + 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; + QVariant m_Label; + QVariant m_ChartType; + QVariant m_Color; + QVariant m_LineStyleName; }; -#endif //QmitkC3xyData_h +#endif // QmitkC3xyData_h diff --git a/Modules/Chart/resource/Chart.js b/Modules/Chart/resource/Chart.js index 0a0fb91356..e5bfeac713 100644 --- a/Modules/Chart/resource/Chart.js +++ b/Modules/Chart/resource/Chart.js @@ -1,420 +1,466 @@ 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 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) + { + registeredChannelObject.SignalDiagramTypeChanged.connect(function(newValue){ + console.log("diagram type changed"); + console.log(newValue); + }); + + registeredChannelObject.SignalLineStyleChanged.connect(function(newValue){ + console.log("line style changed"); + console.log(newValue); + }); + } + + /** + * 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){ + console.log("label changed"); + console.log(newValue); + var layout = { + xaxis: { + title: { + text: newValue + } + } + } + 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 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 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 (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; 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; } 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 generatePlotData(){ 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), type: chartType, 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 =========== // initialize line object trace["line"] = {} - + trace["line"]["color"] = dataProperties[dataLabels[index]]["color"] - if (chartType == "scatter"){ + 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; } 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 } } }; - + 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}); } /** * 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/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.cpp b/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.cpp index 092fcde0cb..867dfa38d6 100644 --- a/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.cpp +++ b/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.cpp @@ -1,329 +1,337 @@ /*=================================================================== 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_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); 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"); 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::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(); 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->SetXAxisLabel("Test Label"); +} + void ChartExample::ClearChart() { m_Controls.m_Chart->Clear(); m_Controls.m_plainTextEditDataView->clear(); } std::vector ChartExample::ConvertToVector(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; } void ChartExample::AddData() { QString data = m_Controls.m_lineEditDataYVector->text(); auto dataY = ConvertToVector(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 = ConvertToVector(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 (m_Controls.m_checkBoxEnableErrors->isChecked()) { if (m_Controls.m_checkBoxEnableXErrors->isChecked()) { auto errorsPlus = ConvertToVector(m_Controls.m_lineEditXErrorPlus->text()); auto errorsMinus = ConvertToVector(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 = ConvertToVector(m_Controls.m_lineEditYErrorPlus->text()); auto errorsMinus = ConvertToVector(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() { +void ChartExample::AdaptZoomX() +{ m_Controls.m_Chart->UpdateMinMaxValueXView(m_Controls.m_doubleSpinBox_minZoomX->value(), m_Controls.m_doubleSpinBox_maxZoomX->value()); } void ChartExample::AdaptZoomY() { m_Controls.m_Chart->UpdateMinMaxValueYView(m_Controls.m_doubleSpinBox_minZoomY->value(), m_Controls.m_doubleSpinBox_maxZoomY->value()); } 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 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::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; } 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 2a717ff41f..8767785b53 100644 --- a/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.h +++ b/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExample.h @@ -1,75 +1,76 @@ /*=================================================================== 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; 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(); private: void FillRandomDataValues(); std::vector GenerateRandomNumbers(unsigned int amount, double max) const; std::vector ConvertToVector(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; 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 6cbef6d95e..7c441cb30f 100644 --- a/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExampleControls.ui +++ b/Plugins/org.mitk.gui.qt.chartExample/src/internal/ChartExampleControls.ui @@ -1,648 +1,666 @@ ChartExampleControls 0 0 455 1054 0 0 QmitkTemplate 0 0 0 0 0 420 411 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 Chart type 0 0 bar line area spline area-spline scatter Color 0 0 Line style 0 0 solid dashed Add data 0 0 - 420 + 225 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 + 100 + 30 + + 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