Files
2025-12-10 14:38:26 -08:00

347 lines
12 KiB
C++

// *****************************************************************************
// * This file is part of the FreeFileSync project. It is distributed under *
// * GNU General Public License: https://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 <map>
#include <vector>
#include <memory>
#include <wx/panel.h>
#include <wx/settings.h>
#include <wx/bitmap.h>
#include <zen/string_tools.h>
#include "color_tools.h"
#include "dc.h"
//elegant 2D graph as wxPanel specialization
namespace zen
{
/* //init graph (optional)
m_panelGraph->setAttributes(Graph2D::MainAttributes().
setLabelX(Graph2D::LABEL_X_BOTTOM, 20, std::make_shared<LabelFormatterTimeElapsed>()).
setLabelY(Graph2D::LABEL_Y_RIGHT, 60, std::make_shared<LabelFormatterBytes>()));
//set graph data
SharedRef<CurveData> curveDataBytes_ = ...
m_panelGraph->setCurve(curveDataBytes_, Graph2D::CurveAttributes().setLineWidth(2).setColor(wxColor(0, 192, 0))); */
struct CurvePoint
{
double x = 0;
double y = 0;
};
struct CurveData
{
virtual ~CurveData() {}
virtual std::pair<double, double> getRangeX() const = 0;
virtual std::vector<CurvePoint> getPoints(double minX, double maxX, const wxSize& areaSizePx) 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<CurvePoint> getPoints(double minX, double maxX, const wxSize& areaSizePx) const override;
};
struct SparseCurveData : public CurveData
{
explicit SparseCurveData(bool addSteps = false) : addSteps_(addSteps) {} //addSteps: add points to get a staircase effect or connect points via a direct line
virtual std::optional<CurvePoint> getLessEq (double x) const = 0;
virtual std::optional<CurvePoint> getGreaterEq(double x) const = 0;
private:
std::vector<CurvePoint> getPoints(double minX, double maxX, const wxSize& areaSizePx) 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<double, double> getRangeX() const override { const size_t sz = getSize(); return { 0.0, sz == 0 ? 0.0 : sz - 1.0}; }
std::optional<CurvePoint> getLessEq(double x) const override
{
const size_t sz = getSize();
const size_t pos = std::min<ptrdiff_t>(std::floor(x), sz - 1); //[!] expect unsigned underflow if empty!
if (pos < sz)
return CurvePoint{1.0 * pos, getValue(pos)};
return {};
}
std::optional<CurvePoint> getGreaterEq(double x) const override
{
const size_t pos = std::max<ptrdiff_t>(std::ceil(x), 0); //[!] use std::max with signed type!
if (pos < getSize())
return CurvePoint{1.0 * pos, getValue(pos)};
return {};
}
};
struct VectorCurveData : public ArrayCurveData
{
std::vector<double>& 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<double> 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 numberTo<wxString>(value); }
};
//------------------------------------------------------------------------------------------------------------
//example: wnd.Bind(EVENT_GRAPH_SELECTION, [this](GraphSelectEvent& event) { onGraphSelect(event); });
struct GraphSelectEvent;
wxDECLARE_EVENT(EVENT_GRAPH_SELECTION, GraphSelectEvent);
struct SelectionBlock
{
CurvePoint from;
CurvePoint to;
};
struct GraphSelectEvent : public wxEvent
{
explicit GraphSelectEvent(const SelectionBlock& selBlock) : wxEvent(0 /*winid*/, EVENT_GRAPH_SELECTION), selectBlock_(selBlock) {}
GraphSelectEvent* Clone() const override { return new GraphSelectEvent(*this); }
SelectionBlock selectBlock_;
};
//------------------------------------------------------------------------------------------------------------
enum class XLabelPos
{
none,
top,
bottom,
};
enum class YLabelPos
{
none,
left,
right,
};
enum class CurveFillMode
{
none,
curve,
polygon
};
enum class GraphCorner
{
topL,
topR,
bottomL,
bottomR,
};
enum class GraphSelMode
{
none,
rect,
x,
y,
};
//------------------------------------------------------------------------------------------------------------
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 = wxASCII_STR(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 = CurveFillMode::curve; return *this; }
CurveAttributes& fillPolygonArea(const wxColor& col) { fillColor = col; fillMode = CurveFillMode::polygon; return *this; }
CurveAttributes& setLineWidth(size_t width) { lineWidth = static_cast<int>(width); return *this; }
private:
friend class Graph2D;
bool autoColor = true;
wxColor color;
CurveFillMode fillMode = CurveFillMode::none;
wxColor fillColor;
int lineWidth = dipToWxsize(2);
};
void addCurve(const SharedRef<CurveData>& data, const CurveAttributes& ca = CurveAttributes());
void clearCurves() { curves_.clear(); }
class MainAttributes
{
public:
MainAttributes()
{
//accessibility: consider system text and background colors;
//small drawback: color of graphs is NOT related to the background! => responsibility of client to use correct colors
setBaseColors(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT),
wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
}
MainAttributes& setMinX(double newMinX) { minX = newMinX; return *this; }
MainAttributes& setMaxX(double newMaxX) { maxX = newMaxX; return *this; }
MainAttributes& setMinY(double newMinY) { minY = newMinY; return *this; }
MainAttributes& setMaxY(double newMaxY) { maxY = newMaxY; return *this; }
MainAttributes& setAutoSize() { minX = maxX = minY = maxY = {}; return *this; }
MainAttributes& setLabelX(XLabelPos posX, int height = -1, std::shared_ptr<LabelFormatter> newLabelFmt = nullptr)
{
xLabelpos = posX;
if (height >= 0) xLabelHeight = height;
if (newLabelFmt) labelFmtX = newLabelFmt;
return *this;
}
MainAttributes& setLabelY(YLabelPos posY, int width = -1, std::shared_ptr<LabelFormatter> newLabelFmt = nullptr)
{
yLabelpos = posY;
if (width >= 0) yLabelWidth = width;
if (newLabelFmt) labelFmtY = newLabelFmt;
return *this;
}
MainAttributes& setCornerText(const wxString& txt, GraphCorner pos) { cornerTexts[pos] = txt; return *this; }
MainAttributes& setBaseColors(const wxColor& text, const wxColor& back) //accessibility: always set both colors
{
colorText = text;
colorBack = back;
colorGridLine = enhanceContrast(colorBack, //start with back color and deviate only as little as required
colorBack, 4 /*contrastRatioMin*/); //W3C recommends >= 4.5 for text
return *this;
}
wxColor getGridLineColor() const { return colorGridLine; }
MainAttributes& setSelectionMode(GraphSelMode mode) { mouseSelMode = mode; return *this; }
private:
friend class Graph2D;
std::optional<double> minX; //x-range to visualize
std::optional<double> maxX; //
std::optional<double> minY; //y-range to visualize
std::optional<double> maxY; //
XLabelPos xLabelpos = XLabelPos::bottom;
std::optional<int> xLabelHeight;
std::shared_ptr<LabelFormatter> labelFmtX = std::make_shared<DecimalNumberFormatter>();
YLabelPos yLabelpos = YLabelPos::left;
std::optional<int> yLabelWidth;
std::shared_ptr<LabelFormatter> labelFmtY = std::make_shared<DecimalNumberFormatter>();
std::map<GraphCorner, wxString> cornerTexts;
wxColor colorText;
wxColor colorBack;
wxColor colorGridLine;
GraphSelMode mouseSelMode = GraphSelMode::rect;
};
void setAttributes(const MainAttributes& newAttr) { attr_ = newAttr; Refresh(); }
MainAttributes getAttributes() const { return attr_; }
std::vector<SelectionBlock> getSelections() const { return oldSel_; }
void setSelections(const std::vector<SelectionBlock>& 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 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<SelectionBlock> oldSel_; //applied selections
std::unique_ptr<MouseSelection> activeSel_; //set during mouse selection
MainAttributes attr_; //global attributes
std::vector<std::pair<SharedRef<CurveData>, CurveAttributes>> curves_;
std::optional<wxBitmap> doubleBuffer_;
};
}
#endif //GRAPH_H_234425245936567345799