// ***************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * // * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * // ***************************************************************************** #ifndef GRAPH_H_234425245936567345799 #define GRAPH_H_234425245936567345799 #include #include #include #include #include #include #include #include //elegant 2D graph as wxPanel specialization namespace zen { /* Example: //init graph (optional) m_panelGraph->setAttributes(Graph2D::MainAttributes(). setLabelX(Graph2D::LABEL_X_BOTTOM, 20, std::make_shared()). setLabelY(Graph2D::LABEL_Y_RIGHT, 60, std::make_shared())); //set graph data std::shared_ptr curveDataBytes = ... m_panelGraph->setCurve(curveDataBytes, Graph2D::CurveAttributes().setLineWidth(2).setColor(wxColor(0, 192, 0))); */ struct CurvePoint { CurvePoint() {} CurvePoint(double xVal, double yVal) : x(xVal), y(yVal) {} double x = 0; double y = 0; }; inline bool operator==(const CurvePoint& lhs, const CurvePoint& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; } inline bool operator!=(const CurvePoint& lhs, const CurvePoint& rhs) { return !(lhs == rhs); } struct CurveData { virtual ~CurveData() {} virtual std::pair getRangeX() const = 0; virtual std::vector getPoints(double minX, double maxX, int pixelWidth) const = 0; //points outside the draw area are automatically trimmed! }; //special curve types: struct ContinuousCurveData : public CurveData { virtual double getValue(double x) const = 0; private: std::vector getPoints(double minX, double maxX, int pixelWidth) const override; }; struct SparseCurveData : public CurveData { SparseCurveData(bool addSteps = false) : addSteps_(addSteps) {} //addSteps: add points to get a staircase effect or connect points via a direct line virtual Opt getLessEq (double x) const = 0; virtual Opt getGreaterEq(double x) const = 0; private: std::vector getPoints(double minX, double maxX, int pixelWidth) const override; const bool addSteps_; }; struct ArrayCurveData : public SparseCurveData { virtual double getValue(size_t pos) const = 0; virtual size_t getSize () const = 0; private: std::pair getRangeX() const override { const size_t sz = getSize(); return std::make_pair(0.0, sz == 0 ? 0.0 : sz - 1.0); } Opt getLessEq(double x) const override { const size_t sz = getSize(); const size_t pos = std::min(std::floor(x), sz - 1); //[!] expect unsigned underflow if empty! if (pos < sz) return CurvePoint(pos, getValue(pos)); return NoValue(); } Opt getGreaterEq(double x) const override { const size_t pos = std::max(std::ceil(x), 0); //[!] use std::max with signed type! if (pos < getSize()) return CurvePoint(pos, getValue(pos)); return NoValue(); } }; struct VectorCurveData : public ArrayCurveData { std::vector& refData() { return data; } private: double getValue(size_t pos) const override { return pos < data.size() ? data[pos] : 0; } size_t getSize() const override { return data.size(); } std::vector data; }; //------------------------------------------------------------------------------------------------------------ struct LabelFormatter { virtual ~LabelFormatter() {} //determine convenient graph label block size in unit of data: usually some small deviation on "sizeProposed" virtual double getOptimalBlockSize(double sizeProposed) const = 0; //create human-readable text for x or y-axis position virtual wxString formatText(double value, double optimalBlockSize) const = 0; }; double nextNiceNumber(double blockSize); //round to next number which is convenient to read, e.g. 2.13 -> 2; 2.7 -> 2.5 struct DecimalNumberFormatter : public LabelFormatter { double getOptimalBlockSize(double sizeProposed ) const override { return nextNiceNumber(sizeProposed); } wxString formatText (double value, double optimalBlockSize) const override { return zen::numberTo(value); } }; //------------------------------------------------------------------------------------------------------------ //emit data selection event //Usage: wnd.Connect(wxEVT_GRAPH_SELECTION, GraphSelectEventHandler(MyDlg::OnGraphSelection), nullptr, this); // void MyDlg::OnGraphSelection(GraphSelectEvent& event); extern const wxEventType wxEVT_GRAPH_SELECTION; struct SelectionBlock { CurvePoint from; CurvePoint to; }; class GraphSelectEvent : public wxCommandEvent { public: GraphSelectEvent(const SelectionBlock& selBlock) : wxCommandEvent(wxEVT_GRAPH_SELECTION), selBlock_(selBlock) {} wxEvent* Clone() const override { return new GraphSelectEvent(selBlock_); } SelectionBlock getSelection() { return selBlock_; } private: SelectionBlock selBlock_; }; using GraphSelectEventFunction = void (wxEvtHandler::*)(GraphSelectEvent&); #define GraphSelectEventHandler(func) \ (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GraphSelectEventFunction, &func) //------------------------------------------------------------------------------------------------------------ class Graph2D : public wxPanel { public: Graph2D(wxWindow* parent, wxWindowID winid = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxTAB_TRAVERSAL | wxNO_BORDER, const wxString& name = wxPanelNameStr); class CurveAttributes { public: CurveAttributes() {} //required by GCC CurveAttributes& setColor (const wxColor& col) { color = col; autoColor = false; return *this; } CurveAttributes& fillCurveArea (const wxColor& col) { fillColor = col; fillMode = FILL_CURVE; return *this; } CurveAttributes& fillPolygonArea(const wxColor& col) { fillColor = col; fillMode = FILL_POLYGON; return *this; } CurveAttributes& setLineWidth(size_t width) { lineWidth = static_cast(width); return *this; } private: friend class Graph2D; bool autoColor = true; wxColor color; enum FillMode { FILL_NONE, FILL_CURVE, FILL_POLYGON }; FillMode fillMode = FILL_NONE; wxColor fillColor; int lineWidth = 2; }; void setCurve(const std::shared_ptr& data, const CurveAttributes& ca = CurveAttributes()); void addCurve(const std::shared_ptr& data, const CurveAttributes& ca = CurveAttributes()); static wxColor getBorderColor() { return { 130, 135, 144 }; } //medium grey, the same Win7 uses for other frame borders => not accessible! but no big deal... enum PosLabelY { LABEL_Y_LEFT, LABEL_Y_RIGHT, LABEL_Y_NONE }; enum PosLabelX { LABEL_X_TOP, LABEL_X_BOTTOM, LABEL_X_NONE }; enum PosCorner { CORNER_TOP_LEFT, CORNER_TOP_RIGHT, CORNER_BOTTOM_LEFT, CORNER_BOTTOM_RIGHT, }; enum SelMode { SELECT_NONE, SELECT_RECTANGLE, SELECT_X_AXIS, SELECT_Y_AXIS, }; class MainAttributes { public: MainAttributes& setMinX(double newMinX) { minX = newMinX; minXauto = false; return *this; } MainAttributes& setMaxX(double newMaxX) { maxX = newMaxX; maxXauto = false; return *this; } MainAttributes& setMinY(double newMinY) { minY = newMinY; minYauto = false; return *this; } MainAttributes& setMaxY(double newMaxY) { maxY = newMaxY; maxYauto = false; return *this; } MainAttributes& setAutoSize() { minXauto = maxXauto = minYauto = maxYauto = true; return *this; } MainAttributes& setLabelX(PosLabelX posX, size_t height = 25, std::shared_ptr newLabelFmt = std::make_shared()) { labelposX = posX; xLabelHeight = static_cast(height); labelFmtX = newLabelFmt; return *this; } MainAttributes& setLabelY(PosLabelY posY, size_t width = 60, std::shared_ptr newLabelFmt = std::make_shared()) { labelposY = posY; yLabelWidth = static_cast(width); labelFmtY = newLabelFmt; return *this; } MainAttributes& setCornerText(const wxString& txt, PosCorner pos) { cornerTexts[pos] = txt; return *this; } MainAttributes& setBackgroundColor(const wxColor& col) { backgroundColor = col; return *this; } MainAttributes& setSelectionMode(SelMode mode) { mouseSelMode = mode; return *this; } private: friend class Graph2D; bool minXauto = true; //autodetect range for X value bool maxXauto = true; double minX = 0; //x-range to visualize double maxX = 0; // bool minYauto = true; //autodetect range for Y value bool maxYauto = true; double minY = 0; //y-range to visualize double maxY = 0; // PosLabelX labelposX = LABEL_X_BOTTOM; int xLabelHeight = 25; std::shared_ptr labelFmtX = std::make_shared(); PosLabelY labelposY = LABEL_Y_LEFT; int yLabelWidth = 60; std::shared_ptr labelFmtY = std::make_shared(); std::map cornerTexts; wxColor backgroundColor = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); SelMode mouseSelMode = SELECT_RECTANGLE; }; void setAttributes(const MainAttributes& newAttr) { attr = newAttr; Refresh(); } MainAttributes getAttributes() const { return attr; } std::vector getSelections() const { return oldSel; } void setSelections(const std::vector& sel) { oldSel = sel; activeSel.reset(); Refresh(); } void clearSelection() { oldSel.clear(); Refresh(); } private: void OnMouseLeftDown(wxMouseEvent& event); void OnMouseMovement(wxMouseEvent& event); void OnMouseLeftUp (wxMouseEvent& event); void OnMouseCaptureLost(wxMouseCaptureLostEvent& event); void onPaintEvent(wxPaintEvent& event); void onSizeEvent(wxSizeEvent& event) { Refresh(); event.Skip(); } void onEraseBackGround(wxEraseEvent& event) {} void render(wxDC& dc) const; class MouseSelection { public: MouseSelection(wxWindow& wnd, const wxPoint& posDragStart) : wnd_(wnd), posDragStart_(posDragStart), posDragCurrent(posDragStart) { wnd_.CaptureMouse(); } ~MouseSelection() { if (wnd_.HasCapture()) wnd_.ReleaseMouse(); } wxPoint getStartPos() const { return posDragStart_; } wxPoint& refCurrentPos() { return posDragCurrent; } SelectionBlock& refSelection() { return selBlock; } //updated in Graph2d::render(): this is fine, since only what's shown is selected! private: wxWindow& wnd_; const wxPoint posDragStart_; wxPoint posDragCurrent; SelectionBlock selBlock; }; std::vector oldSel; //applied selections std::shared_ptr activeSel; //set during mouse selection MainAttributes attr; //global attributes Opt doubleBuffer; using CurveList = std::vector, CurveAttributes>>; CurveList curves_; //perf!!! generating the font is *very* expensive! don't do this repeatedly in Graph2D::render()! const wxFont labelFont { wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, L"Arial" }; }; } #endif //GRAPH_H_234425245936567345799