diff options
Diffstat (limited to 'src/charts')
-rw-r--r-- | src/charts/Chart.cpp | 23 | ||||
-rw-r--r-- | src/charts/Chart.h | 32 | ||||
-rw-r--r-- | src/charts/ChartAxes.cpp | 138 | ||||
-rw-r--r-- | src/charts/ChartAxes.h | 51 | ||||
-rw-r--r-- | src/charts/ChartDataLine.cpp | 80 | ||||
-rw-r--r-- | src/charts/ChartDataLine.h | 37 | ||||
-rw-r--r-- | src/charts/ChartMarker.cpp | 50 | ||||
-rw-r--r-- | src/charts/ChartMarker.h | 36 | ||||
-rw-r--r-- | src/charts/ChartScene.cpp | 111 | ||||
-rw-r--r-- | src/charts/ChartScene.h | 55 | ||||
-rw-r--r-- | src/charts/ChartToolTip.cpp | 43 | ||||
-rw-r--r-- | src/charts/ChartToolTip.h | 26 | ||||
-rw-r--r-- | src/charts/ChartView.cpp | 12 | ||||
-rw-r--r-- | src/charts/ChartView.h | 16 | ||||
-rw-r--r-- | src/charts/DataPoint.h | 15 | ||||
-rw-r--r-- | src/charts/PieChart.cpp | 28 | ||||
-rw-r--r-- | src/charts/PieChart.h | 27 | ||||
-rw-r--r-- | src/charts/PieChartScene.cpp | 30 | ||||
-rw-r--r-- | src/charts/PieChartScene.h | 30 | ||||
-rw-r--r-- | src/charts/PieLegend.cpp | 48 | ||||
-rw-r--r-- | src/charts/PieLegend.h | 34 | ||||
-rw-r--r-- | src/charts/PieRound.cpp | 77 | ||||
-rw-r--r-- | src/charts/PieRound.h | 37 | ||||
-rw-r--r-- | src/charts/TimeChart.cpp | 155 | ||||
-rw-r--r-- | src/charts/TimeChart.h | 54 |
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 |