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  | 
