Qt 5.13 | Qt Charts | Donut Chart Breakdown Example

Qt Charts | Commercial or GPLv3

# Donut Chart Breakdown Example

This example shows how to use create a donut breakdown chart using QPieSeries API.

#### Running the Example

To run the example from Qt Creator, open the Welcome mode and select the example from Examples. For more information, visit Building and Running an Example.

#### Creating Donut Breakdown Charts

Let's start by defining some data for the chart.

      // Graph is based on data of 'Total consumption of energy increased by 10 per cent in 2010'
      // Statistics Finland, 13 December 2011
      // http://www.stat.fi/til/ekul/2010/ekul_2010_2011-12-13_tie_001_en.html

      QPieSeries *series1 = new QPieSeries();
      series1->setName("Fossil fuels");
      series1->append("Oil", 353295);
      series1->append("Coal", 188500);
      series1->append("Natural gas", 148680);
      series1->append("Peat", 94545);

      QPieSeries *series2 = new QPieSeries();
      series2->setName("Renewables");
      series2->append("Wood fuels", 319663);
      series2->append("Hydro power", 45875);
      series2->append("Wind power", 1060);

      QPieSeries *series3 = new QPieSeries();
      series3->setName("Others");
      series3->append("Nuclear energy", 238789);
      series3->append("Import energy", 37802);
      series3->append("Other", 32441);

Then we create a chart where we add the data. Note that this is our own chart derived from QChart.

      DonutBreakdownChart *donutBreakdown = new DonutBreakdownChart();
      donutBreakdown->setAnimationOptions(QChart::AllAnimations);
      donutBreakdown->setTitle("Total consumption of energy in Finland 2010");
      donutBreakdown->legend()->setAlignment(Qt::AlignRight);
      donutBreakdown->addBreakdownSeries(series1, Qt::red);
      donutBreakdown->addBreakdownSeries(series2, Qt::darkGreen);
      donutBreakdown->addBreakdownSeries(series3, Qt::darkBlue);

Our own chart works in such a way that we create a main series in the constructor we create a main series, which aggregates the data provided by the breakdown series. This is the piechart in the center.

      DonutBreakdownChart::DonutBreakdownChart(QGraphicsItem *parent, Qt::WindowFlags wFlags)
          : QChart(QChart::ChartTypeCartesian, parent, wFlags)
      {
          // create the series for main center pie
          m_mainSeries = new QPieSeries();
          m_mainSeries->setPieSize(0.7);
          QChart::addSeries(m_mainSeries);
      }

When a breakdown series is added the data is used to create a slice in the main series and the breakdown series itself is used to create a segment of a donut positioned so that it is aligned with the corresponding slice in the main series.

      void DonutBreakdownChart::addBreakdownSeries(QPieSeries *breakdownSeries, QColor color)
      {
          QFont font("Arial", 8);

          // add breakdown series as a slice to center pie
          MainSlice *mainSlice = new MainSlice(breakdownSeries);
          mainSlice->setName(breakdownSeries->name());
          mainSlice->setValue(breakdownSeries->sum());
          m_mainSeries->append(mainSlice);

          // customize the slice
          mainSlice->setBrush(color);
          mainSlice->setLabelVisible();
          mainSlice->setLabelColor(Qt::white);
          mainSlice->setLabelPosition(QPieSlice::LabelInsideHorizontal);
          mainSlice->setLabelFont(font);

          // position and customize the breakdown series
          breakdownSeries->setPieSize(0.8);
          breakdownSeries->setHoleSize(0.7);
          breakdownSeries->setLabelsVisible();
          const auto slices = breakdownSeries->slices();
          for (QPieSlice *slice : slices) {
              color = color.lighter(115);
              slice->setBrush(color);
              slice->setLabelFont(font);
          }

          // add the series to the chart
          QChart::addSeries(breakdownSeries);

          // recalculate breakdown donut segments
          recalculateAngles();

          // update customize legend markers
          updateLegendMarkers();
      }

Here's how the start and end angles for the donut segments are calculated.

      void DonutBreakdownChart::recalculateAngles()
      {
          qreal angle = 0;
          const auto slices = m_mainSeries->slices();
          for (QPieSlice *slice : slices) {
              QPieSeries *breakdownSeries = qobject_cast<MainSlice *>(slice)->breakdownSeries();
              breakdownSeries->setPieStartAngle(angle);
              angle += slice->percentage() * 360.0; // full pie is 360.0
              breakdownSeries->setPieEndAngle(angle);
          }
      }

The legend markers are customized to show the breakdown percentage. The markers for the main level slices are hidden.

      void DonutBreakdownChart::updateLegendMarkers()
      {
          // go through all markers
          const auto allseries = series();
          for (QAbstractSeries *series : allseries) {
              const auto markers = legend()->markers(series);
              for (QLegendMarker *marker : markers) {
                  QPieLegendMarker *pieMarker = qobject_cast<QPieLegendMarker *>(marker);
                  if (series == m_mainSeries) {
                      // hide markers from main series
                      pieMarker->setVisible(false);
                  } else {
                      // modify markers from breakdown series
                      pieMarker->setLabel(QString("%1 %2%")
                                          .arg(pieMarker->slice()->label())
                                          .arg(pieMarker->slice()->percentage() * 100, 0, 'f', 2));
                      pieMarker->setFont(QFont("Arial", 8));
                  }
              }
          }
      }

Instead the main level slices show the percentage on the label.

      MainSlice::MainSlice(QPieSeries *breakdownSeries, QObject *parent)
          : QPieSlice(parent),
            m_breakdownSeries(breakdownSeries)
      {
          connect(this, &MainSlice::percentageChanged, this, &MainSlice::updateLabel);
      }
      void MainSlice::updateLabel()
      {
          this->setLabel(QString("%1 %2%").arg(m_name).arg(percentage() * 100, 0, 'f', 2));
      }

Now that we have our chart defined, we can finally create a QChartView and show the chart.

      QMainWindow window;
      QChartView *chartView = new QChartView(donutBreakdown);
      chartView->setRenderHint(QPainter::Antialiasing);
      window.setCentralWidget(chartView);
      window.resize(800, 500);
      window.show();

Example project @ code.qt.io