summaryrefslogtreecommitdiff
path: root/src/charts
diff options
context:
space:
mode:
authorJedidiah Barber <contact@jedbarber.id.au>2021-07-14 11:49:10 +1200
committerJedidiah Barber <contact@jedbarber.id.au>2021-07-14 11:49:10 +1200
commitd24f813f3f2a05c112e803e4256b53535895fc98 (patch)
tree601e6ae9a1cd44bcfdcf91739a5ca36aedd827c9 /src/charts
Initial mirror commitHEADmaster
Diffstat (limited to 'src/charts')
-rw-r--r--src/charts/Chart.cpp23
-rw-r--r--src/charts/Chart.h32
-rw-r--r--src/charts/ChartAxes.cpp138
-rw-r--r--src/charts/ChartAxes.h51
-rw-r--r--src/charts/ChartDataLine.cpp80
-rw-r--r--src/charts/ChartDataLine.h37
-rw-r--r--src/charts/ChartMarker.cpp50
-rw-r--r--src/charts/ChartMarker.h36
-rw-r--r--src/charts/ChartScene.cpp111
-rw-r--r--src/charts/ChartScene.h55
-rw-r--r--src/charts/ChartToolTip.cpp43
-rw-r--r--src/charts/ChartToolTip.h26
-rw-r--r--src/charts/ChartView.cpp12
-rw-r--r--src/charts/ChartView.h16
-rw-r--r--src/charts/DataPoint.h15
-rw-r--r--src/charts/PieChart.cpp28
-rw-r--r--src/charts/PieChart.h27
-rw-r--r--src/charts/PieChartScene.cpp30
-rw-r--r--src/charts/PieChartScene.h30
-rw-r--r--src/charts/PieLegend.cpp48
-rw-r--r--src/charts/PieLegend.h34
-rw-r--r--src/charts/PieRound.cpp77
-rw-r--r--src/charts/PieRound.h37
-rw-r--r--src/charts/TimeChart.cpp155
-rw-r--r--src/charts/TimeChart.h54
25 files changed, 1245 insertions, 0 deletions
diff --git a/src/charts/Chart.cpp b/src/charts/Chart.cpp
new file mode 100644
index 0000000..f50911a
--- /dev/null
+++ b/src/charts/Chart.cpp
@@ -0,0 +1,23 @@
+#include "Chart.h"
+#include "ChartScene.h"
+#include "ChartView.h"
+
+Chart::Chart():
+ scene(new ChartScene(this))
+{
+ createChartView();
+}
+
+void Chart::createChartView()
+{
+ view = new ChartView(scene);
+ QVBoxLayout* mainLt = new QVBoxLayout;
+ mainLt->addWidget(view);
+ mainLt->setContentsMargins(QMargins());
+ setLayout(mainLt);
+}
+
+void Chart::setDataSet(const QList<DataPoint>& dataSet)
+{
+ scene->setDataSet(dataSet, 1);
+}
diff --git a/src/charts/Chart.h b/src/charts/Chart.h
new file mode 100644
index 0000000..a613b06
--- /dev/null
+++ b/src/charts/Chart.h
@@ -0,0 +1,32 @@
+#ifndef CHART_H
+#define CHART_H
+
+#include <QtCore>
+#include <QtWidgets>
+
+#include "DataPoint.h"
+
+class ChartView;
+class ChartScene;
+
+class Chart: public QWidget
+{
+public:
+ Chart();
+ void setLabels(const QString& xLabel, const QString& yLabel)
+ { this->xLabel = xLabel; this->yLabel = yLabel; }
+ QString getXLabel() const { return xLabel; }
+ QString getYLabel() const { return yLabel; }
+ void setDataSet(const QList<DataPoint>& dataSet);
+
+private:
+ void createChartView();
+
+protected:
+ ChartScene* scene;
+ ChartView* view;
+ QString xLabel;
+ QString yLabel;
+};
+
+#endif
diff --git a/src/charts/ChartAxes.cpp b/src/charts/ChartAxes.cpp
new file mode 100644
index 0000000..caf8828
--- /dev/null
+++ b/src/charts/ChartAxes.cpp
@@ -0,0 +1,138 @@
+#include "ChartAxes.h"
+#include "ChartScene.h"
+
+#include <cmath>
+
+const QPointF ChartAxes::XLabelOffset(0, 5);
+const QPointF ChartAxes::YLabelOffset(-10, -7);
+const QFont ChartAxes::TickLabelFont("Sans Serif", 11);
+
+ChartAxes::ChartAxes(ChartScene* scene, int xTickInterval):
+ scene(scene), xTickInterval(xTickInterval)
+{
+}
+
+void ChartAxes::paint()
+{
+ addAxisLines();
+ addTicks();
+}
+
+void ChartAxes::addAxisLines()
+{
+ QPointF chartOrigin = scene->getChartOrigin();
+ QRectF chartRect = scene->getChartRect();
+ scene->addLine(QLineF(chartOrigin, chartRect.topLeft()));
+ scene->addLine(QLineF(chartOrigin, chartRect.bottomRight()));
+}
+
+void ChartAxes::addTicks()
+{
+ addXTicks();
+ addYTicks();
+}
+
+void ChartAxes::addXTicks()
+{
+ qreal tickSpacing = scene->getDataDirection() * scene->getXTickSpacing();
+ QPointF pos = getTickOrigin(XTick);
+ for(int i = 0; i < scene->getDataSet().size(); i++)
+ {
+ addLabelledXTick(i, pos);
+ pos.rx() += tickSpacing;
+ }
+}
+
+void ChartAxes::addLabelledXTick(int index, QPointF& pos)
+{
+ if(index % xTickInterval == 0)
+ {
+ addTick(pos, XTick);
+ DataPoint point = scene->getDataSet().at(index);
+ addLabel(point.label, pos, XTick);
+ }
+}
+
+QPointF ChartAxes::getTickOrigin(TickOrientation orientation) const
+{
+ QPointF res = scene->getChartOrigin();
+ if(orientation == XTick)
+ {
+ if(scene->getDataDirection() == 1)
+ res.rx() += ChartScene::DataSetPadding.width();
+ else
+ res.rx() += scene->getChartRect().width();
+ }
+ else
+ res.ry() -= ChartScene::DataSetPadding.height();
+ return res;
+}
+
+void ChartAxes::addTick(const QPointF& pos, TickOrientation orientation)
+{
+ QPointF end = pos + getTickVector(orientation);
+ scene->addLine(QLineF(pos, end));
+}
+
+QPointF ChartAxes::getTickVector(TickOrientation orientation)
+{
+ if(orientation == XTick)
+ return QPointF(0, TickLength);
+ else
+ return QPointF(-TickLength, 0);
+}
+
+void ChartAxes::addLabel(const QString& labelText, const QPointF& pos,
+ TickOrientation orientation)
+{
+ QPointF labelOffset = getLabelOffset(orientation);
+ QGraphicsItem* textItem = scene->addSimpleText(labelText, TickLabelFont);
+ qreal xAlignOffset = getLabelXAlignOffset(
+ textItem->boundingRect().width(), orientation);
+ textItem->setPos(pos + labelOffset + QPointF(xAlignOffset, 0));
+}
+
+QPointF ChartAxes::getLabelOffset(TickOrientation orientation)
+{
+ if(orientation == XTick)
+ return XLabelOffset;
+ else
+ return YLabelOffset;
+}
+
+qreal ChartAxes::getLabelXAlignOffset(qreal labelWidth, TickOrientation orientation)
+{
+ if(orientation == XTick)
+ return -labelWidth / 2;
+ else
+ return -labelWidth;
+}
+
+void ChartAxes::addYTicks()
+{
+ int valueSpacing = scene->getYTickSpacing();
+ qreal posSpacing = scene->getYPosFromValue(valueSpacing);
+ QPointF pos = getTickOrigin(YTick);
+ for(int value = scene->getMinY(); value <= scene->getMaxY();
+ value += valueSpacing)
+ {
+ addLabelledYTick(value, pos, posSpacing);
+ if(valueSpacing == 0)
+ break;
+ }
+}
+
+void ChartAxes::addLabelledYTick(int value, QPointF& pos, qreal posSpacing)
+{
+ addTick(pos, YTick);
+ addLabel(QString::number(value), pos, YTick);
+ addHRuler(pos);
+ pos.ry() -= posSpacing;
+}
+
+void ChartAxes::addHRuler(const QPointF& pos)
+ {
+ QPointF end = pos;
+ end.rx() += scene->getChartRect().width();
+ scene->addLine(QLineF(pos, end), QPen(Qt::lightGray));
+ }
diff --git a/src/charts/ChartAxes.h b/src/charts/ChartAxes.h
new file mode 100644
index 0000000..ebfe4ac
--- /dev/null
+++ b/src/charts/ChartAxes.h
@@ -0,0 +1,51 @@
+#ifndef CHART_AXES_H
+#define CHART_AXES_H
+
+#include <QtCore>
+#include <QtWidgets>
+
+class ChartScene;
+
+class ChartAxes
+{
+public:
+ ChartAxes(ChartScene* scene, int xTickInterval);
+ void paint();
+
+private:
+ enum TickOrientation
+ {
+ XTick,
+ YTick
+ };
+
+private:
+ static QPointF getTickVector(TickOrientation orientation);
+ static QPointF getLabelOffset(TickOrientation orientation);
+ static qreal getLabelXAlignOffset(qreal labelWidth, TickOrientation orientation);
+
+private:
+ void addAxisLines();
+ void addTicks();
+ void addXTicks();
+ void addYTicks();
+ void addLabel(const QString& labelText, const QPointF& pos,
+ TickOrientation orientation);
+ QPointF getTickOrigin(TickOrientation orientation) const;
+ void addHRuler(const QPointF& pos);
+ void addTick(const QPointF& pos, TickOrientation orientation);
+ void addLabelledXTick(int index, QPointF& pos);
+ void addLabelledYTick(int value, QPointF& pos, qreal posSpacing);
+
+private:
+ static const int TickLength = 5;
+ static const QPointF XLabelOffset;
+ static const QPointF YLabelOffset;
+ static const QFont TickLabelFont;
+
+private:
+ ChartScene* scene;
+ int xTickInterval;
+};
+
+#endif
diff --git a/src/charts/ChartDataLine.cpp b/src/charts/ChartDataLine.cpp
new file mode 100644
index 0000000..17919cd
--- /dev/null
+++ b/src/charts/ChartDataLine.cpp
@@ -0,0 +1,80 @@
+#include "ChartDataLine.h"
+#include "ChartScene.h"
+#include "ChartMarker.h"
+
+const QColor ChartDataLine::MarkerColor(Qt::blue);
+const QColor ChartDataLine::FillColor(0, 0, 255, 128);
+
+ChartDataLine::ChartDataLine(ChartScene* scene):
+ scene(scene),
+ origin(getDataSetOrigin()),
+ xSpacing(scene->getXTickSpacing())
+{
+}
+
+QPointF ChartDataLine::getDataSetOrigin() const
+{
+ QSizeF padding = scene->DataSetPadding;
+ QPointF res = scene->getChartOrigin();
+ res.ry() -= padding.height();
+ if(scene->getDataDirection() == 1)
+ res.rx() += padding.width();
+ else
+ res.rx() += scene->getChartRect().width();
+ return res;
+}
+
+void ChartDataLine::paint()
+{
+ QPainterPath linePath = createLinePath();
+ addLinePath(linePath);
+ addAreaPath(linePath);
+}
+
+QPainterPath ChartDataLine::createLinePath()
+{
+ QList<DataPoint> dataSet = scene->getDataSet();
+ QPointF firstPoint = getDataPos(0, dataSet.first().value);
+ QPainterPath linePath(firstPoint);
+ for (int i = 0; i < dataSet.size(); i++)
+ addLineSegment(linePath, i);
+ return linePath;
+}
+
+void ChartDataLine::addLineSegment(QPainterPath& path, int i)
+{
+ DataPoint dataPoint = scene->getDataSet().at(i);
+ QPointF pos = getDataPos(i, dataPoint.value);
+ QString toolTipText = createToolTipText(dataPoint);
+ scene->addItem(new ChartMarker(pos, toolTipText));
+ path.lineTo(pos);
+}
+
+QPointF ChartDataLine::getDataPos(int index, int yValue)
+{
+ qreal xPos = scene->getDataDirection() * index * xSpacing;
+ qreal yPos = scene->getYPosFromValue(yValue - scene->getMinY());
+ return origin + QPointF(xPos, -yPos);
+}
+
+QString ChartDataLine::createToolTipText(const DataPoint& dataPoint) const
+{
+ return scene->getXLabel() + ": <b>" + dataPoint.toolTipLabel + "</b><br/>" +
+ scene->getYLabel() + ": <b>" + QString::number(dataPoint.value) + "</b>";
+}
+
+void ChartDataLine::addLinePath(const QPainterPath& path)
+{
+ QPen pen(MarkerColor);
+ pen.setWidth(2);
+ scene->addPath(path, pen);
+}
+
+void ChartDataLine::addAreaPath(const QPainterPath& linePath)
+{
+ QPainterPath areaPath(origin);
+ areaPath.lineTo(QPointF(linePath.elementAt(0)));
+ areaPath.connectPath(linePath);
+ areaPath.lineTo(QPointF(linePath.currentPosition().x(), origin.y()));
+ scene->addPath(areaPath, QPen(FillColor), QBrush(FillColor));
+}
diff --git a/src/charts/ChartDataLine.h b/src/charts/ChartDataLine.h
new file mode 100644
index 0000000..c90ccb4
--- /dev/null
+++ b/src/charts/ChartDataLine.h
@@ -0,0 +1,37 @@
+#ifndef CHART_DATA_SET_H
+#define CHART_DATA_SET_H
+
+#include <QtCore>
+#include <QtWidgets>
+
+#include "DataPoint.h"
+
+class ChartScene;
+
+class ChartDataLine
+{
+public:
+ ChartDataLine(ChartScene* scene);
+ void paint();
+
+private:
+ QPointF getDataSetOrigin() const;
+ QPainterPath createLinePath();
+ QPointF getDataPos(int index, int yValue);
+ void addLinePath(const QPainterPath& path);
+ void addLineSegment(QPainterPath& path, int i);
+ void addAreaPath(const QPainterPath& linePath);
+ QString createToolTipText(const DataPoint& dataPoint) const;
+
+private:
+ static const int MarkerRadius = 2;
+ static const QColor MarkerColor;
+ static const QColor FillColor;
+
+private:
+ ChartScene* scene;
+ QPointF origin;
+ qreal xSpacing;
+};
+
+#endif
diff --git a/src/charts/ChartMarker.cpp b/src/charts/ChartMarker.cpp
new file mode 100644
index 0000000..7fdd8cd
--- /dev/null
+++ b/src/charts/ChartMarker.cpp
@@ -0,0 +1,50 @@
+#include "ChartMarker.h"
+#include "ChartToolTip.h"
+
+const QPointF ChartMarker::ToolTipOffset(45, -35);
+const QColor ChartMarker::Color(Qt::blue);
+
+ChartMarker::ChartMarker(const QPointF& center, const QString& toolTipText):
+ radius(Radius), toolTip(NULL), toolTipText(toolTipText)
+{
+ setPos(center);
+ setRect(QRectF(-HoverRadius, -HoverRadius,
+ 2 * HoverRadius, 2 * HoverRadius));
+ setAcceptHoverEvents(true);
+}
+
+ChartMarker::~ChartMarker()
+{
+ delete toolTip;
+}
+
+void ChartMarker::hoverEnterEvent(QGraphicsSceneHoverEvent*)
+{
+ radius = HighlightedRadius;
+ update();
+ if(!toolTip)
+ createToolTip();
+ toolTip->show();
+}
+
+void ChartMarker::createToolTip()
+{
+ toolTip = new ChartToolTip(mapToScene(ToolTipOffset), toolTipText);
+ scene()->addItem(toolTip);
+ toolTip->adjustPos();
+}
+
+void ChartMarker::hoverLeaveEvent(QGraphicsSceneHoverEvent*)
+{
+ radius = Radius;
+ update();
+ toolTip->hide();
+}
+
+void ChartMarker::paint(QPainter* painter, const QStyleOptionGraphicsItem*,
+ QWidget*)
+{
+ painter->setPen(Color);
+ painter->setBrush(Color);
+ painter->drawEllipse(QPointF(), radius, radius);
+}
diff --git a/src/charts/ChartMarker.h b/src/charts/ChartMarker.h
new file mode 100644
index 0000000..1f3d81e
--- /dev/null
+++ b/src/charts/ChartMarker.h
@@ -0,0 +1,36 @@
+#ifndef CHART_MARKER_H
+#define CHART_MARKER_H
+
+#include <QtWidgets>
+
+class ChartToolTip;
+
+class ChartMarker: public QGraphicsEllipseItem
+{
+public:
+ ChartMarker(const QPointF& center, const QString& toolTipText);
+ ~ChartMarker();
+ void paint(QPainter* painter,
+ const QStyleOptionGraphicsItem* option, QWidget* widget = 0);
+
+protected:
+ void hoverEnterEvent(QGraphicsSceneHoverEvent* event);
+ void hoverLeaveEvent(QGraphicsSceneHoverEvent* event);
+
+private:
+ void createToolTip();
+
+private:
+ static const QColor Color;
+ static const int HoverRadius = 15;
+ static const int Radius = 2;
+ static const int HighlightedRadius = 4;
+ static const QPointF ToolTipOffset;
+
+private:
+ int radius;
+ ChartToolTip* toolTip;
+ QString toolTipText;
+};
+
+#endif
diff --git a/src/charts/ChartScene.cpp b/src/charts/ChartScene.cpp
new file mode 100644
index 0000000..559ecc5
--- /dev/null
+++ b/src/charts/ChartScene.cpp
@@ -0,0 +1,111 @@
+#include "ChartScene.h"
+#include "ChartAxes.h"
+#include <cstdlib>
+#include <cmath>
+#include "ChartDataLine.h"
+
+const QSizeF ChartScene::Margin(30, 20);
+const QSizeF ChartScene::Padding(20, 20);
+const QSizeF ChartScene::DataSetPadding(10, 10);
+
+ChartScene::ChartScene(Chart* chart):
+ QGraphicsScene(chart), chart(chart), dataDirection(1)
+{
+ setSceneRect(0, 0, 600, 300);
+}
+
+void ChartScene::setDataSet(const QList<DataPoint>& dataSet, int xTickInterval)
+{
+ clear();
+ this->dataSet = dataSet;
+ initYScale();
+ ChartAxes(this, xTickInterval).paint();
+ ChartDataLine(this).paint();
+}
+
+void ChartScene::initYScale()
+{
+ yMin = computeMinY();
+ yMax = computeMaxY();
+ yTickSpacing = computeYTickSpacing();
+ extendYMinMax();
+ yScale = getDataSetSize().height() / getYDiff();
+}
+
+int ChartScene::computeMinY() const
+{
+ int min = 1000000;
+ foreach(DataPoint point, dataSet)
+ if(point.value < min)
+ min = point.value;
+ return min;
+}
+
+int ChartScene::computeMaxY() const
+{
+ int max = -1000000;
+ foreach(DataPoint point, dataSet)
+ if(point.value > max)
+ max = point.value;
+ return max;
+}
+
+int ChartScene::computeYTickSpacing() const
+{
+ const int higherZonesLimit = 4;
+ const int lowerZonesLimit = 3;
+
+ double order = floor(log10(getYDiff()));
+ double base = pow(10, order);
+ double zonesNum = ceil(getYDiff() / base);
+ if(zonesNum > higherZonesLimit)
+ base *= 2;
+ else if(zonesNum < lowerZonesLimit)
+ base /= 2;
+ int res = (int)base;
+ if(res == 0)
+ res = 10;
+ return res;
+}
+
+void ChartScene::extendYMinMax()
+{
+ yMin = int(floor(yMin / double(yTickSpacing))) * yTickSpacing;
+ yMax = int(ceil(yMax / double(yTickSpacing))) * yTickSpacing;
+ if (yMax == yMin)
+ yMax = yMin + yTickSpacing;
+}
+
+QSizeF ChartScene::getBorder()
+{
+ return Margin + Padding;
+}
+
+QRectF ChartScene::getChartRect() const
+{
+ QSizeF border = getBorder();
+ return sceneRect().adjusted(border.width(), border.height(),
+ -border.width(), -border.height());
+}
+
+QPointF ChartScene::getChartOrigin() const
+{
+ QRectF chartRect = getChartRect();
+ return QPointF(chartRect.left(), chartRect.bottom());
+}
+
+QSizeF ChartScene::getDataSetSize() const
+{
+ QSizeF dataSetBorder(2 * getBorder() + DataSetPadding);
+ return sceneRect().size() - dataSetBorder;
+}
+
+qreal ChartScene::getXTickSpacing() const
+{
+ return getDataSetSize().width() / (dataSet.size() - 1);
+}
+
+qreal ChartScene::getYPosFromValue(int value) const
+{
+ return yScale * value;
+}
diff --git a/src/charts/ChartScene.h b/src/charts/ChartScene.h
new file mode 100644
index 0000000..3a9af62
--- /dev/null
+++ b/src/charts/ChartScene.h
@@ -0,0 +1,55 @@
+#ifndef CHART_SCENE_H
+#define CHART_SCENE_H
+
+#include <QtCore>
+#include <QtWidgets>
+
+#include "Chart.h"
+#include "DataPoint.h"
+
+class ChartScene: public QGraphicsScene
+{
+public:
+ static const QSizeF Margin;
+ static const QSizeF Padding;
+ static const QSizeF DataSetPadding;
+
+public:
+ static QSizeF getBorder();
+
+public:
+ ChartScene(Chart* chart);
+ void setDataSet(const QList<DataPoint>& dataSet, int xTickInterval);
+ void setDataDirection(int direction) { dataDirection = direction; }
+ QString getXLabel() const { return chart->getXLabel(); }
+ QString getYLabel() const { return chart->getYLabel(); }
+ int getDataDirection() const { return dataDirection; }
+ QPointF getChartOrigin() const;
+ QRectF getChartRect() const;
+ QSizeF getDataSetSize() const;
+ QList<DataPoint> getDataSet() const {return dataSet; }
+ qreal getXTickSpacing() const;
+ qreal getYPosFromValue(int value) const;
+ int getMinY() const { return yMin; }
+ int getMaxY() const { return yMax; }
+ int getYDiff() const { return yMax - yMin; }
+ int getYTickSpacing() const { return yTickSpacing; }
+
+private:
+ void initYScale();
+ int computeMinY() const;
+ int computeMaxY() const;
+ int computeYTickSpacing() const;
+ void extendYMinMax();
+
+private:
+ Chart* chart;
+ QList<DataPoint> dataSet;
+ int yMin;
+ int yMax;
+ qreal yScale;
+ int yTickSpacing;
+ int dataDirection;
+};
+
+#endif
diff --git a/src/charts/ChartToolTip.cpp b/src/charts/ChartToolTip.cpp
new file mode 100644
index 0000000..15783ea
--- /dev/null
+++ b/src/charts/ChartToolTip.cpp
@@ -0,0 +1,43 @@
+#include "ChartToolTip.h"
+
+const QFont ChartToolTip::Font("Sans Serif", 11);
+
+ChartToolTip::ChartToolTip(const QPointF& pos, const QString& text):
+ text(text)
+{
+ setPos(pos);
+ document.setDefaultFont(Font);
+ document.setHtml(text);
+ QSizeF docSize = document.size();
+ setRect(QRectF(QPointF(-docSize.width() / 2, -docSize.height() / 2), docSize));
+}
+
+void ChartToolTip::paint(QPainter* painter, const QStyleOptionGraphicsItem*,
+ QWidget*)
+{
+ drawFrame(painter);
+ drawText(painter);
+}
+
+void ChartToolTip::drawFrame(QPainter* painter)
+{
+ painter->setPen(QPen(Qt::black));
+ painter->setBrush(QBrush(Qt::white));
+ painter->drawRect(rect());
+}
+
+void ChartToolTip::drawText(QPainter* painter)
+{
+ painter->translate(rect().topLeft());
+ document.drawContents(painter);
+}
+
+void ChartToolTip::adjustPos()
+{
+ QRectF toolTipRect = mapRectToScene(boundingRect());
+ QRectF sceneRect = scene()->sceneRect();
+ if(toolTipRect.right() >= sceneRect.right())
+ moveBy(-(toolTipRect.right() - sceneRect.right()) - 1, 0);
+ if(toolTipRect.top() <= sceneRect.top())
+ moveBy(0, sceneRect.top() - toolTipRect.top() + 1);
+}
diff --git a/src/charts/ChartToolTip.h b/src/charts/ChartToolTip.h
new file mode 100644
index 0000000..88453a8
--- /dev/null
+++ b/src/charts/ChartToolTip.h
@@ -0,0 +1,26 @@
+#ifndef CHART_TOOL_TIP_H
+#define CHART_TOOL_TIP_H
+
+#include <QtWidgets>
+
+class ChartToolTip: public QGraphicsRectItem
+{
+public:
+ ChartToolTip(const QPointF& pos, const QString& text);
+ void paint(QPainter* painter,
+ const QStyleOptionGraphicsItem* option, QWidget* widget = 0);
+ void adjustPos();
+
+private:
+ void drawFrame(QPainter* painter);
+ void drawText(QPainter* painter);
+
+private:
+ static const QFont Font;
+
+private:
+ QString text;
+ QTextDocument document;
+};
+
+#endif
diff --git a/src/charts/ChartView.cpp b/src/charts/ChartView.cpp
new file mode 100644
index 0000000..24a08c5
--- /dev/null
+++ b/src/charts/ChartView.cpp
@@ -0,0 +1,12 @@
+#include "ChartView.h"
+
+ChartView::ChartView(QGraphicsScene* scene):
+ QGraphicsView(scene)
+{
+ setRenderHints(QPainter::Antialiasing);
+}
+
+void ChartView::resizeEvent(QResizeEvent*)
+{
+ fitInView(scene()->sceneRect(), Qt::KeepAspectRatio);
+}
diff --git a/src/charts/ChartView.h b/src/charts/ChartView.h
new file mode 100644
index 0000000..fcd071c
--- /dev/null
+++ b/src/charts/ChartView.h
@@ -0,0 +1,16 @@
+#ifndef CHART_VIEW_H
+#define CHART_VIEW_H
+
+#include <QtCore>
+#include <QtWidgets>
+
+class ChartView: public QGraphicsView
+{
+public:
+ ChartView(QGraphicsScene* scene);
+
+protected:
+ void resizeEvent(QResizeEvent* event);
+};
+
+#endif
diff --git a/src/charts/DataPoint.h b/src/charts/DataPoint.h
new file mode 100644
index 0000000..394d1be
--- /dev/null
+++ b/src/charts/DataPoint.h
@@ -0,0 +1,15 @@
+#ifndef DATAPOINT_H
+#define DATAPOINT_H
+
+struct DataPoint
+{
+public:
+ DataPoint(const QString& label, int value, QString toolTipLabel = ""):
+ label(label), value(value), toolTipLabel(toolTipLabel) {}
+
+ QString label;
+ int value;
+ QString toolTipLabel;
+};
+
+#endif
diff --git a/src/charts/PieChart.cpp b/src/charts/PieChart.cpp
new file mode 100644
index 0000000..0637823
--- /dev/null
+++ b/src/charts/PieChart.cpp
@@ -0,0 +1,28 @@
+#include "PieChart.h"
+#include "PieChartScene.h"
+#include "ChartView.h"
+
+PieChart::PieChart():
+ scene(new PieChartScene(this))
+{
+ createChartView();
+}
+
+void PieChart::createChartView()
+{
+ view = new ChartView(scene);
+ QVBoxLayout* mainLt = new QVBoxLayout;
+ mainLt->addWidget(view);
+ mainLt->setContentsMargins(QMargins());
+ setLayout(mainLt);
+}
+
+void PieChart::setDataSet(const QList<DataPoint>& dataSet)
+{
+ scene->setDataSet(dataSet);
+}
+
+void PieChart::setColors(const QStringList& colors)
+{
+ scene->setColors(colors);
+}
diff --git a/src/charts/PieChart.h b/src/charts/PieChart.h
new file mode 100644
index 0000000..8e60833
--- /dev/null
+++ b/src/charts/PieChart.h
@@ -0,0 +1,27 @@
+#ifndef PIE_CHART_H
+#define PIE_CHART_H
+
+#include <QtCore>
+#include <QtWidgets>
+
+#include "DataPoint.h"
+
+class ChartView;
+class PieChartScene;
+
+class PieChart: public QWidget
+{
+public:
+ PieChart();
+ void setDataSet(const QList<DataPoint>& dataSet);
+ void setColors(const QStringList& colors);
+
+private:
+ void createChartView();
+
+protected:
+ PieChartScene* scene;
+ ChartView* view;
+};
+
+#endif
diff --git a/src/charts/PieChartScene.cpp b/src/charts/PieChartScene.cpp
new file mode 100644
index 0000000..135a0e4
--- /dev/null
+++ b/src/charts/PieChartScene.cpp
@@ -0,0 +1,30 @@
+#include <cstdlib>
+#include <cmath>
+#include "PieChartScene.h"
+#include "PieRound.h"
+#include "PieLegend.h"
+
+const QSizeF PieChartScene::Margin(20, 20);
+const QSizeF PieChartScene::Size(500, 230);
+
+PieChartScene::PieChartScene(PieChart* chart):
+ QGraphicsScene(chart), chart(chart)
+{
+ setSceneRect(0, 0, Size.width(), Size.height());
+}
+
+void PieChartScene::setDataSet(const QList<DataPoint>& dataSet)
+{
+ clear();
+ this->dataSet = dataSet;
+ int centerPos = PieRound::Margin + PieRound::Radius;
+ QPointF roundPos(QPointF(Margin.width(), Margin.height()) +
+ QPointF(centerPos, centerPos));
+ addItem(new PieRound(roundPos, this));
+ addItem(new PieLegend(roundPos + QPointF(LegendDistance, 0), this));
+}
+
+void PieChartScene::setColors(const QStringList& colors)
+{
+ this->colors = colors;
+}
diff --git a/src/charts/PieChartScene.h b/src/charts/PieChartScene.h
new file mode 100644
index 0000000..4ab8129
--- /dev/null
+++ b/src/charts/PieChartScene.h
@@ -0,0 +1,30 @@
+#ifndef PIE_CHART_SCENE_H
+#define PIE_CHART_SCENE_H
+
+#include <QtCore>
+#include <QtWidgets>
+
+#include "DataPoint.h"
+#include "PieChart.h"
+
+class PieChartScene: public QGraphicsScene
+{
+public:
+ static const QSizeF Margin;
+ static const QSizeF Size;
+ static const int LegendDistance = 230;
+
+public:
+ PieChartScene(PieChart* chart);
+ void setDataSet(const QList<DataPoint>& dataSet);
+ void setColors(const QStringList& colors);
+ QList<DataPoint> getDataSet() const {return dataSet; }
+ QStringList getColors() const { return colors; }
+
+private:
+ PieChart* chart;
+ QList<DataPoint> dataSet;
+ QStringList colors;
+};
+
+#endif
diff --git a/src/charts/PieLegend.cpp b/src/charts/PieLegend.cpp
new file mode 100644
index 0000000..5faabf4
--- /dev/null
+++ b/src/charts/PieLegend.cpp
@@ -0,0 +1,48 @@
+#include "PieLegend.h"
+#include "PieChartScene.h"
+
+PieLegend::PieLegend(const QPointF& pos, const PieChartScene* scene):
+ scene(scene)
+{
+ setPos(pos);
+ addLabels();
+}
+
+QRectF PieLegend::boundingRect() const
+{
+ return QRectF(-Width / 2, -Width / 2, Width, Width);
+}
+
+void PieLegend::addLabels()
+{
+ for(int i = 0; i < scene->getDataSet().size(); i++)
+ addLabel(i, getLabelPos(i));
+}
+
+QPointF PieLegend::getLabelPos(int index) const
+{
+ return boundingRect().topLeft() +
+ QPointF(SquareSide / 2, SquareSide / 2) +
+ index * QPointF(0, SquareSide + LabelSpacing);
+}
+
+void PieLegend::addLabel(int index, const QPointF& pos)
+{
+ QRectF squareRect(-SquareSide / 2., -SquareSide / 2.,
+ SquareSide, SquareSide);
+ QGraphicsRectItem* squareItem = new QGraphicsRectItem(squareRect, this);
+ squareItem->setBrush(QColor(scene->getColors()[index]));
+ squareItem->setPos(pos);
+
+ QGraphicsSimpleTextItem* textItem =
+ new QGraphicsSimpleTextItem(scene->getDataSet()[index].label, this);
+ textItem->setPos(getTextPos(pos, textItem));
+}
+
+QPointF PieLegend::getTextPos(const QPointF& squarePos,
+ const QGraphicsSimpleTextItem* textItem) const
+{
+ return squarePos +
+ QPointF(SquareSide / 2. + LabelTextSpacing,
+ -textItem->boundingRect().height() / 2);
+}
diff --git a/src/charts/PieLegend.h b/src/charts/PieLegend.h
new file mode 100644
index 0000000..d8e83e5
--- /dev/null
+++ b/src/charts/PieLegend.h
@@ -0,0 +1,34 @@
+#ifndef PIE_LEGEND_H
+#define PIE_LEGEND_H
+
+#include <QtWidgets>
+
+#include "DataPoint.h"
+
+class PieChartScene;
+
+class PieLegend: public QGraphicsItem
+{
+public:
+ static const int Width = 150;
+ static const int SquareSide = 20;
+ static const int LabelSpacing = 10;
+ static const int LabelTextSpacing = 10;
+
+public:
+ PieLegend(const QPointF& pos, const PieChartScene* scene);
+ QRectF boundingRect() const;
+ void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget* = 0) {}
+
+private:
+ void addLabels();
+ QPointF getLabelPos(int index) const;
+ void addLabel(int index, const QPointF& pos);
+ QPointF getTextPos(const QPointF& squarePos,
+ const QGraphicsSimpleTextItem* textItem) const;
+
+private:
+ const PieChartScene* scene;
+};
+
+#endif
diff --git a/src/charts/PieRound.cpp b/src/charts/PieRound.cpp
new file mode 100644
index 0000000..f90d740
--- /dev/null
+++ b/src/charts/PieRound.cpp
@@ -0,0 +1,77 @@
+#include "PieRound.h"
+#include "PieChartScene.h"
+
+PieRound::PieRound(const QPointF& center, const PieChartScene* scene):
+ scene(scene)
+{
+ setPos(center);
+ calculateSum();
+ addSectors();
+}
+
+QRectF PieRound::boundingRect() const
+{
+ return QRectF(-Radius, -Radius, 2 * Radius, 2 * Radius);
+}
+
+void PieRound::calculateSum()
+{
+ sum = 0;
+ foreach(DataPoint point, scene->getDataSet())
+ sum += point.value;
+}
+
+void PieRound::addSectors()
+{
+ qreal angle = -90;
+ for(int i = 0; i < scene->getDataSet().size(); i++)
+ {
+ qreal sweep = 360. * scene->getDataSet()[i].value / sum;
+ addSector(i, angle, sweep);
+ angle += sweep;
+ }
+}
+
+void PieRound::addSector(int index, qreal startAngle, qreal sweep)
+{
+ addSectorItem(createSectorPath(startAngle, sweep),
+ QColor(scene->getColors()[index]));
+ addSectorLabel(index, startAngle, sweep);
+}
+
+QPainterPath PieRound::createSectorPath(qreal startAngle, qreal sweep)
+{
+ QPainterPath sectorPath;
+ sectorPath.arcTo(boundingRect(), -startAngle, -sweep);
+ sectorPath.lineTo(QPointF());
+ return sectorPath;
+}
+
+void PieRound::addSectorItem(const QPainterPath& path, QColor color)
+{
+ QGraphicsPathItem* sectorItem = new QGraphicsPathItem(path, this);
+ sectorItem->setPen(QColor("white"));
+ sectorItem->setBrush(color);
+}
+
+void PieRound::addSectorLabel(int index, qreal startAngle, qreal sweep)
+{
+ int value = scene->getDataSet()[index].value;
+ if(value == 0)
+ return;
+ QGraphicsSimpleTextItem* labelItem =
+ new QGraphicsSimpleTextItem(QString::number(value), this);
+ labelItem->setPos(getLabelPos(startAngle, sweep, labelItem));
+}
+
+QPointF PieRound::getLabelPos(qreal startAngle, qreal sweep,
+ QGraphicsSimpleTextItem* labelItem)
+{
+ int radius = sweep > 30 ? LabelRadius : 2 * LabelRadius;
+ QRectF labelArcRect(-radius, -radius, 2 * radius, 2 * radius);
+ QPainterPath path;
+ path.arcMoveTo(labelArcRect, -startAngle - sweep / 2);
+ QSizeF labelSize = labelItem->boundingRect().size();
+ return path.currentPosition() -
+ QPointF(labelSize.width() / 2, labelSize.height() / 2);
+}
diff --git a/src/charts/PieRound.h b/src/charts/PieRound.h
new file mode 100644
index 0000000..1963f26
--- /dev/null
+++ b/src/charts/PieRound.h
@@ -0,0 +1,37 @@
+#ifndef PIE_ROUND_H
+#define PIE_ROUND_H
+
+#include <QtWidgets>
+
+#include "DataPoint.h"
+
+class PieChartScene;
+
+class PieRound: public QGraphicsItem
+{
+public:
+ static const int Margin = 20;
+ static const int Radius = 80;
+ static const int LabelRadius = 50;
+
+public:
+ PieRound(const QPointF& center, const PieChartScene* scene);
+ QRectF boundingRect() const;
+ void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget* = 0) {}
+
+private:
+ void calculateSum();
+ void addSectors();
+ void addSector(int index, qreal startAngle, qreal sweep);
+ QPainterPath createSectorPath(qreal startAngle, qreal sweep);
+ void addSectorItem(const QPainterPath& path, QColor color);
+ void addSectorLabel(int index, qreal startAngle, qreal sweep);
+ QPointF getLabelPos(qreal startAngle, qreal sweep,
+ QGraphicsSimpleTextItem* labelItem);
+
+private:
+ int sum;
+ const PieChartScene* scene;
+};
+
+#endif
diff --git a/src/charts/TimeChart.cpp b/src/charts/TimeChart.cpp
new file mode 100644
index 0000000..c6a60b9
--- /dev/null
+++ b/src/charts/TimeChart.cpp
@@ -0,0 +1,155 @@
+#include "TimeChart.h"
+#include "ChartScene.h"
+#include "DataPoint.h"
+
+const QList<int> TimeChart::TimeUnits = QList<int>() <<
+ 1 << 7 << 30 << 92 << 183;
+
+TimeChart::TimeChart()
+{
+}
+
+void TimeChart::setDates(const QList<QDateTime>& dates, int period,
+ int dataDirection)
+{
+ this->dates = dates;
+ scene->setDataDirection(dataDirection);
+ TimeUnit timeUnit = findTimeUnit(period);
+ QDate thisIntervalStart = getIntervalStart(QDate::currentDate(), timeUnit);
+ QList<DataPoint> dataSet;
+ int pointsNum = getDataPointsNum(period, timeUnit);
+ for(int i = 0; i < pointsNum; i++)
+ dataSet.append(createDataPoint(thisIntervalStart, timeUnit, i));
+ scene->setDataSet(dataSet, getTicksInterval(pointsNum));
+}
+
+int TimeChart::getDataPointsNum(int period, TimeUnit timeUnit)
+{
+ return period / TimeUnits.at(timeUnit);
+}
+
+int TimeChart::getTicksInterval(int pointsNum)
+{
+ int ticksInterval = 1;
+ while(pointsNum / ticksInterval > MaxXTicks)
+ ticksInterval *= 2;
+ return ticksInterval;
+}
+
+TimeChart::TimeUnit TimeChart::findTimeUnit(int period)
+{
+ for(int unit = Day; unit < (int)TimeUnitsNum; unit++)
+ if(getDataPointsNum(period, (TimeUnit)unit) <= MaxDataPoints)
+ return (TimeUnit)unit;
+ return HalfYear;
+}
+
+QDate TimeChart::getIntervalStart(const QDate& date, TimeUnit timeUnit)
+{
+ switch(timeUnit)
+ {
+ case Day:
+ return date;
+ case Week:
+ return date.addDays(-date.dayOfWeek() + 1);
+ case Month:
+ return QDate(date.year(), date.month(), 1);
+ case Quarter:
+ return getQuarterStart(date);
+ case HalfYear:
+ return getHalfYearStart(date);
+ default:
+ return date;
+ }
+}
+
+QDate TimeChart::getQuarterStart(const QDate& date)
+{
+ const int quarterLen = 3;
+ int quarter = (date.month() - 1) / quarterLen;
+ int startMonth = quarter * quarterLen + 1;
+ return QDate(date.year(), startMonth, 1);
+}
+
+QDate TimeChart::getHalfYearStart(const QDate& date)
+{
+ const int secondHalfStart = 7;
+ int startMonth = date.month() < secondHalfStart ? 1 : secondHalfStart;
+ return QDate(date.year(), startMonth, 1);
+}
+
+QDate TimeChart::getInterval(const QDate& start, TimeUnit timeUnit,
+ int num)
+{
+ switch(timeUnit)
+ {
+ case Day:
+ return start.addDays(num);
+ case Week:
+ return start.addDays(7 * num);
+ case Month:
+ return start.addMonths(num);
+ case Quarter:
+ return start.addMonths(3 * num);
+ case HalfYear:
+ return start.addMonths(6 * num);
+ default:
+ return start;
+ }
+}
+
+QDate TimeChart::getIntervalEnd(const QDate& start, TimeUnit timeUnit)
+{
+ return getInterval(start, timeUnit, 1).addDays(-1);
+}
+
+DataPoint TimeChart::createDataPoint(const QDate& thisIntervalStart, TimeUnit timeUnit,
+ int intervalIndex)
+{
+ QDate startDate = getInterval(thisIntervalStart, timeUnit,
+ scene->getDataDirection() * intervalIndex);
+ QDate endDate = getIntervalEnd(startDate, timeUnit);
+ QString xLabel = getDateLabel(startDate, timeUnit);
+ int value = getCardsNumForDate(startDate, endDate);
+ QString toolTipLabel = getIntervalLabel(startDate, endDate, timeUnit);
+ return DataPoint(xLabel, value, toolTipLabel);
+}
+
+QString TimeChart::getDateLabel(const QDate& date, TimeUnit timeUnit)
+{
+ return date.toString(getIntervalFormat(timeUnit));
+}
+
+QString TimeChart::getIntervalFormat(TimeUnit timeUnit)
+{
+ switch(timeUnit)
+ {
+ case Day:
+ case Week:
+ return "dd.MM";
+ case Month:
+ case Quarter:
+ case HalfYear:
+ return "MM/yy";
+ default:
+ return "dd.MM";
+ }
+}
+
+QString TimeChart::getIntervalLabel(const QDate& startDate,
+ const QDate& endDate, TimeUnit timeUnit)
+{
+ QString startLabel = getDateLabel(startDate, timeUnit);
+ if(timeUnit == Day || timeUnit == Month)
+ return startLabel;
+ return startLabel + " - " + getDateLabel(endDate, timeUnit);
+}
+
+int TimeChart::getCardsNumForDate(const QDate& startDate, const QDate& endDate)
+{
+ int cardsNum = 0;
+ foreach(QDateTime date, dates)
+ if(date.date() >= startDate && date.date() <= endDate)
+ cardsNum++;
+ return cardsNum;
+}
diff --git a/src/charts/TimeChart.h b/src/charts/TimeChart.h
new file mode 100644
index 0000000..1d9f92d
--- /dev/null
+++ b/src/charts/TimeChart.h
@@ -0,0 +1,54 @@
+#ifndef TIME_CHART_H
+#define TIME_CHART_H
+
+#include <QtCore>
+
+#include "Chart.h"
+
+class TimeChart: public Chart
+{
+enum TimeUnit
+{
+ Day = 0,
+ Week,
+ Month,
+ Quarter,
+ HalfYear,
+ TimeUnitsNum
+};
+
+public:
+ TimeChart();
+ void setDates(const QList<QDateTime>& dates, int period,
+ int dataDirection = 1);
+
+private:
+ static TimeUnit findTimeUnit(int period);
+ static QDate getIntervalStart(const QDate& date, TimeUnit timeUnit);
+ static QDate getInterval(const QDate& start, TimeUnit timeUnit,
+ int num);
+ static QDate getIntervalEnd(const QDate& start, TimeUnit timeUnit);
+ static QString getDateLabel(const QDate& date, TimeUnit timeUnit);
+ static QString getIntervalFormat(TimeUnit timeUnit);
+ static QString getIntervalLabel(const QDate& startDate,
+ const QDate& endDate, TimeUnit timeUnit);
+ static QDate getQuarterStart(const QDate& date);
+ static QDate getHalfYearStart(const QDate& date);
+ static int getDataPointsNum(int period, TimeUnit timeUnit);
+ static int getTicksInterval(int pointsNum);
+
+private:
+ DataPoint createDataPoint(const QDate& thisIntervalStart, TimeUnit timeUnit,
+ int intervalIndex);
+ int getCardsNumForDate(const QDate& startDate, const QDate& endDate);
+
+private:
+ static const QList<int> TimeUnits;
+ static const int MaxDataPoints = 28;
+ static const int MaxXTicks = 12;
+
+private:
+ QList<QDateTime> dates;
+};
+
+#endif