diff --git a/Modules/Chart/include/QmitkChartData.h b/Modules/Chart/include/QmitkChartData.h index ba2890b056..9f6349e3f5 100644 --- a/Modules/Chart/include/QmitkChartData.h +++ b/Modules/Chart/include/QmitkChartData.h @@ -1,160 +1,192 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkC3Data_h #define QmitkC3Data_h #include #include +#include +#include +#include +#include +#include +#include /** \brief This class holds the relevant properties for the chart generation with C3 such as labels and diagram type. * It is derived from QObject, because we need Q_PROPERTIES to send Data via QWebChannel to JavaScript. * \sa The actual data for the chart generation is in QmitkC3xyData! */ class QmitkChartData : public QObject { Q_OBJECT Q_PROPERTY(QVariant m_xAxisLabel READ GetXAxisLabel WRITE SetXAxisLabel NOTIFY SignalXAxisLabelChanged); Q_PROPERTY(QVariant m_yAxisLabel READ GetYAxisLabel WRITE SetYAxisLabel NOTIFY SignalYAxisLabelChanged); Q_PROPERTY(QVariant m_chartTitle READ GetTitle WRITE SetTitle NOTIFY SignalTitleChanged); Q_PROPERTY(QVariant m_themeName READ GetThemeName WRITE SetThemeName NOTIFY SignalThemeNameChanged); Q_PROPERTY(QVariant m_LegendPosition READ GetLegendPosition WRITE SetLegendPosition NOTIFY SignalLegendPositionChanged); Q_PROPERTY(QVariant m_ShowLegend READ GetShowLegend WRITE SetShowLegend NOTIFY SignalShowLegendChanged); Q_PROPERTY(QVariant m_ShowErrorBars READ GetShowErrorBars WRITE SetShowErrorBars NOTIFY SignalShowErrorBarsChanged); Q_PROPERTY(QVariant m_YAxisScale READ GetYAxisScale WRITE SetYAxisScale NOTIFY SignalYAxisScaleChanged); Q_PROPERTY(QVariant m_ShowSubchart READ GetShowSubchart WRITE SetShowSubchart NOTIFY SignalShowSubchartChanged); Q_PROPERTY(QVariant m_UsePercentageInPieChart READ GetUsePercentageInPieChart WRITE SetUsePercentageInPieChart NOTIFY SignalUsePercentageInPieChartChanged); Q_PROPERTY(QVariant m_DataPointSize READ GetDataPointSize WRITE SetDataPointSize NOTIFY SignalDataPointSizeChanged); Q_PROPERTY(QVariant m_StackedData READ GetStackedData WRITE SetStackedData NOTIFY SignalStackedDataChanged); Q_PROPERTY(QVariant m_MinValueXView READ GetMinValueXView WRITE SetMinValueXView NOTIFY SignalMinValueXViewChanged); Q_PROPERTY(QVariant m_MaxValueXView READ GetMaxValueXView WRITE SetMaxValueXView NOTIFY SignalMaxValueXViewChanged); Q_PROPERTY(QVariant m_MinValueYView READ GetMinValueYView WRITE SetMinValueYView NOTIFY SignalMinValueYViewChanged); Q_PROPERTY(QVariant m_MaxValueYView READ GetMaxValueYView WRITE SetMaxValueYView NOTIFY SignalMaxValueYViewChanged); public: QmitkChartData(); void SetAppearance(bool showSubChart = true, bool usePercentageInPieChart = false); Q_INVOKABLE QVariant GetXAxisLabel() const { return m_xAxisLabel; }; Q_INVOKABLE void SetXAxisLabel(const QVariant& label) { m_xAxisLabel = label; emit SignalXAxisLabelChanged(label); }; Q_INVOKABLE QVariant GetYAxisLabel() const { return m_yAxisLabel; }; Q_INVOKABLE void SetYAxisLabel(const QVariant& label) { m_yAxisLabel = label; emit SignalYAxisLabelChanged(label); }; Q_INVOKABLE QVariant GetTitle() const { return m_chartTitle; }; Q_INVOKABLE void SetTitle(const QVariant& title) { m_chartTitle = title; emit SignalTitleChanged(title); }; Q_INVOKABLE QVariant GetThemeName() const { return m_themeName; }; Q_INVOKABLE void SetThemeName(const QVariant &themeName) { m_themeName = themeName; emit SignalThemeNameChanged(themeName); }; Q_INVOKABLE QVariant GetLegendPosition() const { return m_LegendPosition; }; Q_INVOKABLE void SetLegendPosition(const QVariant& legendPosition) { m_LegendPosition = legendPosition; emit SignalLegendPositionChanged(legendPosition); }; Q_INVOKABLE QVariant GetShowLegend() const { return m_ShowLegend; }; Q_INVOKABLE void SetShowLegend(const QVariant& show) { m_ShowLegend = show; emit SignalShowLegendChanged(show); }; Q_INVOKABLE QVariant GetShowErrorBars() const { return m_ShowErrorBars; }; Q_INVOKABLE void SetShowErrorBars(const QVariant &show) { m_ShowErrorBars = show; emit SignalShowErrorBarsChanged(show); }; Q_INVOKABLE QVariant GetYAxisScale() const { return m_YAxisScale; }; Q_INVOKABLE void SetYAxisScale(const QVariant& YAxisScale) { m_YAxisScale = YAxisScale; emit SignalYAxisScaleChanged(YAxisScale); }; Q_INVOKABLE QVariant GetShowSubchart() const { return m_ShowSubchart; }; Q_INVOKABLE void SetShowSubchart(const QVariant& showSubchart) { m_ShowSubchart = showSubchart; emit SignalShowSubchartChanged(showSubchart); }; Q_INVOKABLE QVariant GetUsePercentageInPieChart() const { return m_UsePercentageInPieChart; }; Q_INVOKABLE void SetUsePercentageInPieChart(const QVariant& usePercentageInPieChart) { m_UsePercentageInPieChart = usePercentageInPieChart; emit SignalUsePercentageInPieChartChanged(usePercentageInPieChart); }; Q_INVOKABLE QVariant GetDataPointSize() const { return m_DataPointSize; }; Q_INVOKABLE void SetDataPointSize(const QVariant& showDataPoints) { m_DataPointSize = showDataPoints; emit SignalDataPointSizeChanged(showDataPoints); }; Q_INVOKABLE QVariant GetStackedData() const { return m_StackedData; }; Q_INVOKABLE void SetStackedData(const QVariant& stackedData) { m_StackedData = stackedData; emit SignalStackedDataChanged(m_StackedData); }; Q_INVOKABLE QVariant GetMinValueXView() const { return m_MinValueXView; }; Q_INVOKABLE void SetMinValueXView(const QVariant &minValueXView) { m_MinValueXView = minValueXView; emit SignalMinValueXViewChanged(m_MinValueXView); }; Q_INVOKABLE QVariant GetMaxValueXView() const { return m_MaxValueXView; }; Q_INVOKABLE void SetMaxValueXView(const QVariant &maxValueXView) { m_MaxValueXView = maxValueXView; emit SignalMaxValueXViewChanged(m_MaxValueXView); }; Q_INVOKABLE QVariant GetMinValueYView() const { return m_MinValueYView; }; Q_INVOKABLE void SetMinValueYView(const QVariant &minValueYView) { m_MinValueYView = minValueYView; emit SignalMinValueYViewChanged(m_MinValueYView); }; Q_INVOKABLE QVariant GetMaxValueYView() const { return m_MaxValueYView; }; Q_INVOKABLE void SetMaxValueYView(const QVariant &maxValueYView) { m_MaxValueYView = maxValueYView; emit SignalMaxValueYViewChanged(m_MaxValueYView); }; + void EmitSignalImageUrl() + { + emit SignalImageUrl(); + }; + signals: void SignalYAxisLabelChanged(const QVariant label); void SignalXAxisLabelChanged(const QVariant label); void SignalLegendPositionChanged(const QVariant legendPosition); void SignalShowLegendChanged(const QVariant show); void SignalShowErrorBarsChanged(const QVariant show); void SignalYAxisScaleChanged(const QVariant YAxisScale); void SignalTitleChanged(const QVariant title); void SignalThemeNameChanged(const QVariant themeName); void SignalShowSubchartChanged(const QVariant showSubchart); void SignalUsePercentageInPieChartChanged(const QVariant usePercentageInPieChart); void SignalDataPointSizeChanged(const QVariant showDataPoints); void SignalStackedDataChanged(const QVariant stackedData); void SignalMinValueXViewChanged(const QVariant minValueXView); void SignalMaxValueXViewChanged(const QVariant maxValueXView); void SignalMinValueYViewChanged(const QVariant minValueYView); void SignalMaxValueYViewChanged(const QVariant maxValueYView); + void SignalImageUrl(); + +public slots: + void slotImageUrl(const QString &datafromjs) + { + QString ds = QUrl::fromPercentEncoding(datafromjs.toLatin1()); + + QString filename = QFileDialog::getSaveFileName( + 0, + tr("Save Plot"), + "my_plot.svg", + tr("Scalable Vector Graphics (*.svg)") ); + if (filename.isEmpty()) + return; + + std::string out_image = ds.toStdString(); + boost::algorithm::replace_first(out_image, "data:image/svg+xml,", ""); + std::ofstream outfile(filename.toStdString()); + outfile.write(out_image.c_str(), out_image.size()); + outfile.close(); + } private: QVariant m_xAxisLabel; QVariant m_yAxisLabel; QVariant m_chartTitle; QVariant m_themeName = "dark"; QVariant m_ShowLegend = true; QVariant m_ShowErrorBars; QVariant m_LegendPosition = "topRight"; QVariant m_ShowSubchart; QVariant m_YAxisScale; QVariant m_UsePercentageInPieChart; QVariant m_numberDatasets; QVariant m_DataPointSize = 0; QVariant m_StackedData; QVariant m_MinValueXView; QVariant m_MaxValueXView; QVariant m_MinValueYView; QVariant m_MaxValueYView; }; #endif //QmitkC3Data_h diff --git a/Modules/Chart/include/QmitkChartWidget.h b/Modules/Chart/include/QmitkChartWidget.h index 7d44a723b4..b26aea11ea 100644 --- a/Modules/Chart/include/QmitkChartWidget.h +++ b/Modules/Chart/include/QmitkChartWidget.h @@ -1,333 +1,357 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkC3jsWidget_h #define QmitkC3jsWidget_h #include #include #include #include class QmitkChartxyData; /*! \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 MarkerSymbol { + circle, + diamond, + cross, + square, + pentagon, + star, + x, + diamond_tall, + star_diamond, + star_triangle_up, + star_triangle_down, + asterisk, + cross_thin, + x_thin + }; enum class ChartColor { red, orange, yellow, green, blue, purple, brown, magenta, tan, cyan, olive, maroon, navy, aquamarine, turqouise, silver, lime, teal, indigo, violet, pink, black, white, grey }; 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 UpdateChartExampleData(const std::map& data2D, const std::string& label, const std::string& type, const std::string& color, const std::string& lineStyle, const std::string& pieLabelsData = 0); void AddData2D(const std::map &data2D, const std::string &label, ChartType chartType = ChartType::bar); //Add Function for the ChartExample void AddChartExampleData(const std::map& data2D, const std::string& label, const std::string& type, const std::string& color, const std::string& style, const std::string& pieLabelsData = 0); /*! * \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); QmitkChartxyData *GetDataElementByLabel(const std::string& label) const; /*! * \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 marker style of one data entry (identifier is previously assigned label) + * \note If an unknown label is given, nothing happens. + */ + void SetMarkerSymbol(const std::string &label, MarkerSymbol symbol); + /*! * \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 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; + void SavePlotAsImage(); + 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 e934b9510a..30f71d94fc 100644 --- a/Modules/Chart/include/QmitkChartxyData.h +++ b/Modules/Chart/include/QmitkChartxyData.h @@ -1,161 +1,171 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #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 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); + Q_PROPERTY(QVariant m_MarkerSymbolName READ GetMarkerSymbol WRITE SetMarkerSymbol NOTIFY SignalMarkerSymbolChanged); 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 SignalDataChanged(yData); }; Q_INVOKABLE QList GetXData() const { return m_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; 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 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; emit SignalColorChanged(color); }; + Q_INVOKABLE QVariant GetMarkerSymbol() const { return m_MarkerSymbolName; }; + Q_INVOKABLE void SetMarkerSymbol(const QVariant &markerSymbol) + { + m_MarkerSymbolName = markerSymbol; + emit SignalMarkerSymbolChanged(markerSymbol); + }; + 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 (including error data). */ void ClearData(); QmitkChartxyData() {} signals: 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); + void SignalMarkerSymbolChanged(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; QList m_PieLabels; QVariant m_ChartType; QVariant m_Color; QVariant m_LineStyleName; + QVariant m_MarkerSymbolName; }; #endif // QmitkC3xyData_h diff --git a/Modules/Chart/resource/Chart.js b/Modules/Chart/resource/Chart.js index 78ae2adf06..2fe861932e 100644 --- a/Modules/Chart/resource/Chart.js +++ b/Modules/Chart/resource/Chart.js @@ -1,611 +1,628 @@ 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 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); - }); - - } + /** + * 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){ + let xDataTemp = registeredChannelObject.m_XData; + let yDataTemp = registeredChannelObject.m_YData; + + let trace = generateTraceByChartType(registeredChannelObject.m_ChartType); + + if (dataProperties[registeredChannelObject.m_Label]["style"] == "dashed"){ + trace["line"]["dash"] = "dot" + } + + 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.SignalImageUrl.connect(function() { + Plotly.toImage('chart', {format: 'svg', width: 800, height: 500, filename: 'test_plot'}).then(function(result) { + registeredChannelObject.slotImageUrl(result); + }); + }); + + 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); - + handleChartChangeEvents(chartData); + let count = 0; for(let propertyName in channel.objects) { if (propertyName != 'chartData') { - let chartXYData = channel.objects[propertyName]; - handleDataChangeEvents(chartXYData); + 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 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; + pieDataLabels[count] = pieDataLabelsTemp; var tempLineStyle = ''; if (chartXYData.m_LineStyleName == "solid") { tempLineStyle = '' } else { tempLineStyle = "dashed" } dataProperties[dataLabel] = { - "color" : chartXYData.m_Color, - "chartType": chartXYData.m_ChartType, - "style": tempLineStyle + "color" : chartXYData.m_Color, + "chartType": chartXYData.m_ChartType, + "style": tempLineStyle, + "symbol": chartXYData.m_MarkerSymbolName } count++; } } - var theme = chartData.m_themeName; - minValueX = chartData.m_MinValueXView; - minValueY = chartData.m_MinValueYView; - maxValueX = chartData.m_MaxValueXView; - maxValueY = chartData.m_MaxValueYView; + var theme = chartData.m_themeName; + minValueX = chartData.m_MinValueXView; + minValueY = chartData.m_MinValueYView; + maxValueX = chartData.m_MaxValueXView; + maxValueY = chartData.m_MaxValueYView; - setThemeColors(theme); + 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 - } + let errorObject = { + type: 'data', + array: errors, + visible: visible + } - return errorObject; + 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; + var legendX = 0.5; + var legendY = -0.75; } else if (legendPosition == "bottomRight"){ - var legendX = 1; - var legendY = 0; + var legendX = 1; + var legendY = 0; } else if (legendPosition == "topRight"){ - var legendX = 1; - var legendY = 1; + var legendX = 1; + var legendY = 1; } else if (legendPosition == "topLeft"){ - var legendX = 0; - var legendY = 1; + var legendX = 0; + var legendY = 1; } else if (legendPosition == "middleRight"){ - var legendX = 1; - var legendY = 0.5; + var legendX = 1; + var legendY = 0.5; } - let legendPositionObject = { - x: legendX, - y: legendY, - font : { - color: foregroundColor - } - } + let legendPositionObject = { + x: legendX, + y: legendY, + font : { + color: foregroundColor + } + } - return legendPositionObject; + return legendPositionObject; } function generateErrorBarsAsymmetric(errorsPlus, errorsMinus, visible){ - let errorObject = generateErrorBars(errorsPlus, visible); - errorObject["arrayminus"] = errorsMinus; - errorObject["symmetric"] = false; + let errorObject = generateErrorBars(errorsPlus, visible); + errorObject["arrayminus"] = errorsMinus; + errorObject["symmetric"] = false; - return errorObject; + return errorObject; } function generateStackPlotData(){ let data = []; for (let index = 0; index < dataLabels.length; index++){ - let inputType = 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), stackgroup: 'one', name: dataLabels[index], - type: chartType, - marker:{ - color: dataProperties[dataLabels[index]]["color"] - } + type: chartType, + marker:{ + color: dataProperties[dataLabels[index]]["color"] + } }; data.push(trace); } return data; } function generateTraceByChartType(chartType){ - let plotlyChartType = getPlotlyChartType(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' - } + let trace = { + type: plotlyChartType, + }; + + trace["mode"] = 'lines'; + if (chartData.m_DataPointSize > 0) + trace["mode"] = 'lines+markers'; - return trace; + 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 (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); - } - } + let trace = generateTraceByChartType(dataProperties[dataLabels[index]]["chartType"]); + + 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 =========== - trace["line"]["color"] = dataProperties[dataLabels[index]]["color"] + 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"; - } + trace["marker"] = {size: 6.5, line: {width: 1, color: dataProperties[dataLabels[index]]["color"]}, color: dataProperties[dataLabels[index]]["color"], symbol: dataProperties[dataLabels[index]]["symbol"]} 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 (chartData == undefined) + { + chartData = {} + } - if (dataLabels == undefined) - { + 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, + paper_bgcolor : backgroundColor, + plot_bgcolor : backgroundColor, title: { - text:chartData.m_chartTitle, - font: { - color: foregroundColor - } - }, + text:chartData.m_chartTitle, + font: { + color: foregroundColor + } + }, xaxis: { title: { - text: chartData.m_xAxisLabel - }, - color: foregroundColor + text: chartData.m_xAxisLabel + }, + color: foregroundColor }, yaxis: { title: { - text:chartData.m_yAxisLabel - }, - color: foregroundColor + text:chartData.m_yAxisLabel + }, + color: foregroundColor }, margin: { l: 50, r: 10, b: 40, t: marginTop, pad: 4 }, - showlegend: chartData.m_ShowLegend, - legend: legendPosition + showlegend: chartData.m_ShowLegend, + legend: legendPosition }; if (chartData.m_StackedData){ - layout["barmode"] = 'stack'; + layout["barmode"] = 'stack'; } if (chartData.m_YAxisScale){ - layout.yaxis["type"] = "log" + 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}); + Plotly.react('chart', data, layout, {displayModeBar: false, responsive: true}); -if (minValueX !== null && maxValueX !== null){ - UpdateMinMaxValueXView(minValueX, maxValueX); -} + if (minValueX !== null && maxValueX !== null){ + UpdateMinMaxValueXView(minValueX, maxValueX); + } if (minValueY !== null && maxValueY !== null){ - UpdateMinMaxValueYView(minValueY, maxValueY); -} + UpdateMinMaxValueYView(minValueY, maxValueY); + } } /** * Change theme of chart. * * @param {string} color - dark or not dark */ function changeTheme(color) { - setThemeColors(color); -link = document.getElementsByTagName("link")[0]; + 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; + chartData.m_ShowSubchart = showSubchart; } function setThemeColors(theme){ - if (theme == 'dark'){ - backgroundColor = '#2d2d30'; - foregroundColor = 'white'; - } - else { - backgroundColor = '#f0f0f0'; - foregroundColor = 'black'; - } + 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] - } - }; + 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] - } - }; + 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 6e18aa5e02..61b947498a 100644 --- a/Modules/Chart/src/QmitkChartWidget.cpp +++ b/Modules/Chart/src/QmitkChartWidget.cpp @@ -1,915 +1,959 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include "mitkExceptionMacro.h" #include #include class CustomPage : public QWebEnginePage { public: CustomPage(QObject *parent = nullptr) : QWebEnginePage(parent) {} virtual void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel /*level*/, const QString &message, int lineNumber, const QString & /*sourceID*/) override { 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 AddChartExampleData(const std::map& data2D, const std::string& label, const std::string& type, const std::string& color, const std::string& style, const std::string& pieLabelsData = 0); void UpdateData1D(const std::vector &data1D, const std::string &label); void UpdateData2D(const std::map &data2D, const std::string &label); void UpdateChartExampleData(const std::map& data2D, const std::string& label, const std::string& type, const std::string& color, const std::string& lineStyle, const std::string& pieLabelsData = 0); void RemoveData(const std::string &label); void UpdateLabel(const std::string &existingLabel, const std::string &newLabel); QmitkChartxyData* GetDataElementByLabel(const std::string& label) const; void ClearData(); void SetColor(const std::string &label, const std::string &colorName); + void SetLineStyle(const std::string &label, LineStyle style); + void SetMarkerSymbol(const std::string &label, MarkerSymbol symbol); + 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 SetLegendPosition(LegendPosition position); 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; + void GetImageUrl(); + private: using ChartxyDataVector = std::vector>; std::string GetUniqueLabelName(const QList &labelList, 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_ChartColorToName; std::map m_ColorThemeToName; std::map m_LegendPositionToName; std::map m_LineStyleToName; + std::map m_MarkerSymbolToName; std::map m_AxisScaleToName; }; QmitkChartWidget::Impl::Impl(QWidget *parent) : m_WebChannel(new QWebChannel(parent)), m_WebEngineView(new QWebEngineView(parent)) { // disable context menu for QWebEngineView m_WebEngineView->setContextMenuPolicy(Qt::NoContextMenu); 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_ChartColorToName.emplace(ChartColor::red, "red"); m_ChartColorToName.emplace(ChartColor::orange, "orange"); m_ChartColorToName.emplace(ChartColor::yellow, "yellow"); m_ChartColorToName.emplace(ChartColor::green, "green"); m_ChartColorToName.emplace(ChartColor::blue, "blue"); m_ChartColorToName.emplace(ChartColor::purple, "purple"); m_ChartColorToName.emplace(ChartColor::brown, "brown"); m_ChartColorToName.emplace(ChartColor::magenta, "magenta"); m_ChartColorToName.emplace(ChartColor::tan, "tan"); m_ChartColorToName.emplace(ChartColor::cyan, "cyan"); m_ChartColorToName.emplace(ChartColor::olive, "olive"); m_ChartColorToName.emplace(ChartColor::maroon, "maroon"); m_ChartColorToName.emplace(ChartColor::navy, "navy"); m_ChartColorToName.emplace(ChartColor::aquamarine, "aquamarine"); m_ChartColorToName.emplace(ChartColor::turqouise, "turqouise"); m_ChartColorToName.emplace(ChartColor::silver, "silver"); m_ChartColorToName.emplace(ChartColor::lime, "lime"); m_ChartColorToName.emplace(ChartColor::teal, "teal"); m_ChartColorToName.emplace(ChartColor::indigo, "indigo"); m_ChartColorToName.emplace(ChartColor::violet, "violet"); m_ChartColorToName.emplace(ChartColor::pink, "pink"); m_ChartColorToName.emplace(ChartColor::black, "black"); m_ChartColorToName.emplace(ChartColor::white, "white"); m_ChartColorToName.emplace(ChartColor::grey, "grey"); 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_MarkerSymbolToName.emplace(MarkerSymbol::circle, "circle"); + m_MarkerSymbolToName.emplace(MarkerSymbol::cross, "cross"); + m_MarkerSymbolToName.emplace(MarkerSymbol::diamond, "diamond"); + m_MarkerSymbolToName.emplace(MarkerSymbol::pentagon, "pentagon"); + m_MarkerSymbolToName.emplace(MarkerSymbol::square, "square"); + m_MarkerSymbolToName.emplace(MarkerSymbol::star, "star"); + m_MarkerSymbolToName.emplace(MarkerSymbol::x, "x"); + + m_MarkerSymbolToName.emplace(MarkerSymbol::diamond_tall, "diamond-tall"); + m_MarkerSymbolToName.emplace(MarkerSymbol::star_diamond, "star-diamond"); + m_MarkerSymbolToName.emplace(MarkerSymbol::star_triangle_up, "star-triangle-up"); + m_MarkerSymbolToName.emplace(MarkerSymbol::star_triangle_down, "star-triangle-down"); + m_MarkerSymbolToName.emplace(MarkerSymbol::asterisk, "asterisk"); + m_MarkerSymbolToName.emplace(MarkerSymbol::cross_thin, "cross-thin"); + m_MarkerSymbolToName.emplace(MarkerSymbol::x_thin, "x-thin"); + 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 QmitkChartWidget::Impl::GetThemeName() const { return m_C3Data.GetThemeName().toString().toStdString(); } 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::GetImageUrl() +{ + m_C3Data.EmitSignalImageUrl(); +} + 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::AddChartExampleData(const std::map& data2D, const std::string& label, const std::string& type, const std::string& color, const std::string& lineStyle, const std::string& pieLabelsData) { QMap data2DConverted; for (const auto& aValue : data2D) { data2DConverted.insert(aValue.first, aValue.second); } auto definedLabels = GetDataLabels(m_C3xyData); auto uniqueLabel = GetUniqueLabelName(definedLabels, label); if (type == "scatter") { SetShowDataPoints(true); MITK_INFO << "Enabling data points for all because of scatter plot"; } unsigned int sizeOfC3xyData = static_cast(m_C3xyData.size()); std::unique_ptr chartData = std::make_unique( data2DConverted, QVariant(QString::fromStdString(uniqueLabel)), QVariant(QString::fromStdString(type)), QVariant(sizeOfC3xyData)); chartData->SetColor(QVariant(QString::fromStdString(color))); chartData->SetLineStyle(QVariant(QString::fromStdString(lineStyle))); if (pieLabelsData != "") { std::string pieLabelsDataWorkingString = pieLabelsData; QList pieLabelsDataList; while (pieLabelsDataWorkingString.size() != 0) { QVariant oneElement = QString::fromStdString(pieLabelsDataWorkingString.substr(0, pieLabelsDataWorkingString.find(";"))); pieLabelsDataList.push_back(oneElement); if (pieLabelsDataWorkingString.find(";") != std::string::npos) { pieLabelsDataWorkingString.erase(0, pieLabelsDataWorkingString.find(";") + 1); } else { pieLabelsDataWorkingString.erase(pieLabelsDataWorkingString.begin(), pieLabelsDataWorkingString.end()); } } chartData->SetPieLabels(pieLabelsDataList); } m_C3xyData.push_back(std::move(chartData)); } 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); } 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::UpdateChartExampleData(const std::map& data2D, const std::string& label, const std::string& type, const std::string& color, const std::string& lineStyle, const std::string& pieLabelsData) { UpdateData2D(data2D, label); auto element = GetDataElementByLabel(label); if (element) { element->SetChartType(QString::fromStdString(type)); element->SetColor(QString::fromStdString(color)); element->SetLineStyle(QString::fromStdString(lineStyle)); if (pieLabelsData != "") { std::string pieLabelsDataWorkingString = pieLabelsData; QList pieLabelsDataList; while (pieLabelsDataWorkingString.size() != 0) { QVariant oneElement = QString::fromStdString(pieLabelsDataWorkingString.substr(0, pieLabelsDataWorkingString.find(";"))); pieLabelsDataList.push_back(oneElement); if (pieLabelsDataWorkingString.find(";") != std::string::npos) { pieLabelsDataWorkingString.erase(0, pieLabelsDataWorkingString.find(";") + 1); } else { pieLabelsDataWorkingString.erase(pieLabelsDataWorkingString.begin(), pieLabelsDataWorkingString.end()); } } element->SetPieLabels(pieLabelsDataList); } } } 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::SetMarkerSymbol(const std::string &label, MarkerSymbol symbol) +{ + auto element = GetDataElementByLabel(label); + const std::string markerSymbolName(m_MarkerSymbolToName.at(symbol)); + element->SetMarkerSymbol(QVariant(QString::fromStdString(markerSymbolName))); +} + 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::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::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::SetMarkerSymbol(const std::string &label, MarkerSymbol symbol) +{ + m_Impl->SetMarkerSymbol(label, symbol); +} + 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::AddData2D(const std::map& data2D, const std::string& label, ChartType type) { m_Impl->AddData2D(data2D, label, type); } void QmitkChartWidget::AddChartExampleData(const std::map& data2D, const std::string& label, const std::string& type, const std::string& color, const std::string& lineStyle, const std::string& pieLabelsData) { m_Impl->AddChartExampleData(data2D, label, type, color, lineStyle, pieLabelsData); } 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::UpdateChartExampleData(const std::map& data2D, const std::string& label, const std::string& type, const std::string& color, const std::string& lineStyle, const std::string& pieLabelsData) { m_Impl->UpdateChartExampleData(data2D, label, type, color, lineStyle, pieLabelsData); } 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); } QmitkChartxyData* QmitkChartWidget::GetDataElementByLabel(const std::string& label) const { return m_Impl->GetDataElementByLabel(label); } void QmitkChartWidget::SetXAxisLabel(const std::string &label) { m_Impl->SetXAxisLabel(label); } void QmitkChartWidget::SetYAxisLabel(const std::string &label) { m_Impl->SetYAxisLabel(label); } void QmitkChartWidget::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); } void QmitkChartWidget::SetTheme(ColorTheme themeEnabled) { m_Impl->SetThemeName(themeEnabled); } void QmitkChartWidget::SetShowSubchart(bool showSubChart) { 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(); } + +void QmitkChartWidget::SavePlotAsImage() +{ + m_Impl->GetImageUrl(); +}