//+----------------------------------------------------------------------------+ //| Description: Magic Set Editor - Program to make Magic (tm) cards | //| Copyright: (C) Twan van Laarhoven and the other MSE developers | //| License: GNU General Public License 2 or later (see file COPYING) | //+----------------------------------------------------------------------------+ #pragma once // ----------------------------------------------------------------------------- : Includes #include #include #include #include DECLARE_POINTER_TYPE(GraphAxis); DECLARE_POINTER_TYPE(GraphElement); DECLARE_POINTER_TYPE(GraphData); DECLARE_POINTER_TYPE(Graph); // ----------------------------------------------------------------------------- : Events /// Event that indicates the a subset is selected/deselected in a graph DECLARE_LOCAL_EVENT_TYPE(EVENT_GRAPH_SELECT, ) /// Handle EVENT_GRAPH_SELECT events #define EVT_GRAPH_SELECT(id, handler) EVT_COMMAND(id, EVENT_GRAPH_SELECT, handler) // ----------------------------------------------------------------------------- : Graph data /// A group in a table or graph /** A group is rendered as a single bar or pie slice */ class GraphGroup : public IntrusivePtrBase { public: GraphGroup(const String& name, UInt size, const Color& color = *wxBLACK) : name(name), color(color), size(size) {} String name; ///< Name of this position Color color; ///< Associated color UInt size; ///< Number of elements in this group }; /// Automatic coloring mode enum AutoColor { AUTO_COLOR_NO , AUTO_COLOR_EVEN , AUTO_COLOR_WEIGHTED }; /// An axis in a graph, consists of a list of groups /** The sum of groups.sum = sum of all elements in the data */ class GraphAxis : public IntrusivePtrBase { public: GraphAxis(const String& name, AutoColor auto_color = AUTO_COLOR_EVEN, bool numeric = false, double bin_size = 0, const map* colors = nullptr, const vector* order = nullptr) : name(name) , auto_color(auto_color) , numeric(numeric), bin_size(bin_size) , max(0) , total(0) , mean_value(0), max_value(-numeric_limits::infinity()) , colors(colors) , order(order) {} String name; ///< Name/label of this axis AutoColor auto_color; ///< Automatically assign colors to the groups on this axis vector groups; ///< Groups along this axis bool numeric; ///< Numeric axis? double bin_size; ///< Group numeric values into bins of this size UInt max; ///< Maximum size of the groups UInt total; ///< Sum of the size of all groups double mean_value; ///< Mean value, only for numeric axes double max_value; ///< Maximal value, only for numeric axes const map* colors; ///< Colors for each choice (optional) const vector* order; ///< Order of the items (optional) /// Add a graph group void addGroup(const String& name, UInt size); }; /// A single data point of a graph class GraphElement : public IntrusivePtrBase { public: GraphElement(size_t original_index) : original_index(original_index) {} size_t original_index; ///< Corresponding index in the original input vector values; ///< Group name for each axis }; /// Data to be displayed in a graph, not processed yet class GraphDataPre { public: vector axes; vector elements; /// Split compound elements, "a,b,c" -> "a" and "b" and "c" void splitList(size_t axis); }; /// A single data point of a graph struct GraphDataElement { size_t original_index; int group_nrs[1]; ///< Group number for each axis }; /// Data to be displayed in a graph class GraphData : public IntrusivePtrBase { public: GraphData(const GraphDataPre&); ~GraphData(); vector axes; ///< The axes in the data vector values; ///< All elements, with the group number for each axis, or -1 UInt size; ///< Total number of elements /// Create a cross table for two axes void crossAxis(size_t axis1, size_t axis2, vector& out) const; /// Create a cross table for three axes void crossAxis(size_t axis1, size_t axis2, size_t axis3, vector& out) const; /// Count the number of elements with the given values, -1 is a wildcard UInt count(const vector& match) const; /// Get the original_indices of elements matching the selection void indices(const vector& match, vector& out) const; }; // ----------------------------------------------------------------------------- : Graph enum DrawLayer { LAYER_BOTTOM = 0 , LAYER_SELECTION = 0 , LAYER_AXES , LAYER_VALUES , LAYER_COUNT }; /// A type of graph /** It is rendered into a sub-rectangle of the screen */ class Graph : public IntrusivePtrVirtualBase { public: /// Determine the size of this graph viewer, return -1 if the viewer stretches virtual RealSize determineSize(RotatedDC& dc) const { return RealSize(-1,-1); } /// Draw this graph, filling the internalRect() of the dc. virtual void draw(RotatedDC& dc, const vector& current, DrawLayer layer) const = 0; /// Find the item at the given position, the rectangle gives the screen size virtual bool findItem(const RealPoint& pos, const RealRect& screen_rect, bool tight, vector& out) const { return false; } /// Change the data virtual void setData(const GraphDataP& d) { data = d; } /// Get the data inline const GraphDataP& getData() const { return data; } protected: /// Data of the graph GraphDataP data; }; /// Base class for 1 dimensional graph components class Graph1D : public Graph { public: inline Graph1D(size_t axis) : axis(axis) {} virtual void draw(RotatedDC& dc, const vector& current, DrawLayer layer) const; virtual bool findItem(const RealPoint& pos, const RealRect& screen_rect, bool tight, vector& out) const; virtual void setData(const GraphDataP& d); protected: size_t axis; /// Find an item, return the position along the axis, or -1 if not found virtual int findItem(const RealPoint& pos, const RealRect& screen_rect, bool tight) const { return -1; } virtual void draw(RotatedDC& dc, int current, DrawLayer layer) const = 0; inline GraphAxis& axis_data() const { return *data->axes.at(axis); } }; /// Base class for 2 dimensional graph components class Graph2D : public Graph { public: inline Graph2D(size_t axis1, size_t axis2) : axis1(axis1), axis2(axis2) {} virtual void setData(const GraphDataP& d); protected: size_t axis1, axis2; vector values; // axis1.size * axis2.size array inline GraphAxis& axis1_data() const { return *data->axes.at(axis1); } inline GraphAxis& axis2_data() const { return *data->axes.at(axis2); } }; /// A bar graph class BarGraph : public Graph1D { public: inline BarGraph(size_t axis) : Graph1D(axis) {} virtual void draw(RotatedDC& dc, int current, DrawLayer layer) const; virtual int findItem(const RealPoint& pos, const RealRect& screen_rect, bool tight) const; }; // A bar graph with stacked bars class BarGraph2D : public Graph2D { public: inline BarGraph2D(size_t axis_h, size_t axis_v) : Graph2D(axis_h, axis_v) {} virtual void draw(RotatedDC& dc, const vector& current, DrawLayer layer) const; virtual bool findItem(const RealPoint& pos, const RealRect& screen_rect, bool tight, vector& out) const; }; /// A pie graph class PieGraph : public Graph1D { public: inline PieGraph(size_t axis) : Graph1D(axis) {} virtual void draw(RotatedDC& dc, int current, DrawLayer layer) const; virtual int findItem(const RealPoint& pos, const RealRect& screen_rect, bool tight) const; }; /// A scatter plot class ScatterGraph : public Graph2D { public: inline ScatterGraph(size_t axis1, size_t axis2) : Graph2D(axis1, axis2) {} virtual void draw(RotatedDC& dc, const vector& current, DrawLayer layer) const; virtual bool findItem(const RealPoint& pos, const RealRect& screen_rect, bool tight, vector& out) const; virtual void setData(const GraphDataP& d); protected: UInt max_value; double max_value_x, max_value_y; ///< highest sum of two adjacent scaled values (radii) static double scale(double x); ///< nonlinear scaling }; /// A scatter plot with an extra dimension class ScatterGraphPlus : public ScatterGraph { public: inline ScatterGraphPlus(size_t axis1, size_t axis2, size_t axis3) : ScatterGraph(axis1, axis2), axis3(axis3) {} virtual void setData(const GraphDataP& d); protected: size_t axis3; vector values3D; // axis1.size * axis2.size * axis3.size array inline GraphAxis& axis3_data() const { return *data->axes.at(axis3); } }; /// A scatter plot with a pie graph for the third dimension class ScatterPieGraph : public ScatterGraphPlus { public: inline ScatterPieGraph(size_t axis1, size_t axis2, size_t axis3) : ScatterGraphPlus(axis1, axis2, axis3) {} virtual void draw(RotatedDC& dc, const vector& current, DrawLayer layer) const; }; /// The legend, used for pie graphs class GraphLegend : public Graph1D { public: inline GraphLegend(size_t axis, Alignment alignment, bool reverse = false) : Graph1D(axis), alignment(alignment), reverse(reverse) {} virtual RealSize determineSize(RotatedDC& dc) const; virtual void draw(RotatedDC& dc, int current, DrawLayer layer) const; virtual int findItem(const RealPoint& pos, const RealRect& screen_rect, bool tight) const; private: mutable RealSize size, item_size; Alignment alignment; bool reverse; }; /// Simple statistics like the mean class GraphStats : public Graph1D { public: inline GraphStats(size_t axis, Alignment alignment) : Graph1D(axis), alignment(alignment) {} virtual RealSize determineSize(RotatedDC& dc) const; virtual void draw(RotatedDC& dc, int current, DrawLayer layer) const; virtual void setData(const GraphDataP& d); private: mutable RealSize size, item_size; mutable double label_width; Alignment alignment; vector > values; }; //class GraphTable { //}; enum DrawLines { DRAW_LINES_NO , DRAW_LINES_BETWEEN , DRAW_LINES_MID }; /// Draws a horizontal/vertical axis for group labels class GraphLabelAxis : public Graph1D { public: inline GraphLabelAxis(size_t axis, Direction direction, bool rotate = false, DrawLines draw_lines = DRAW_LINES_NO, bool label = false) : Graph1D(axis), direction(direction), rotate(rotate), draw_lines(draw_lines), label(label) {} virtual void draw(RotatedDC& dc, int current, DrawLayer layer) const; virtual int findItem(const RealPoint& pos, const RealRect& screen_rect, bool tight) const; private: Direction direction; int levels; bool rotate; DrawLines draw_lines; bool label; }; /// Draws an a vertical axis for counts class GraphValueAxis : public Graph1D { public: inline GraphValueAxis(size_t axis, bool highlight_value) : Graph1D(axis), highlight_value(highlight_value) {} virtual void draw(RotatedDC& dc, int current, DrawLayer layer) const; private: bool highlight_value; }; /// A graph with margins class GraphWithMargins : public Graph { public: inline GraphWithMargins(const GraphP& graph, double margin_left, double margin_top, double margin_right, double margin_bottom, bool upside_down = false) : graph(graph) , margin_left(margin_left), margin_top(margin_top), margin_right(margin_right), margin_bottom(margin_bottom) , upside_down(upside_down) {} virtual void draw(RotatedDC& dc, const vector& current, DrawLayer layer) const; virtual bool findItem(const RealPoint& pos, const RealRect& screen_rect, bool tight, vector& out) const; virtual void setData(const GraphDataP& d); private: const GraphP graph; double margin_left, margin_top, margin_right, margin_bottom; bool upside_down; // put the coordinate system upside down, since graphs are usually bottom-to-top }; /// A display containing multiple graphs class GraphContainer : public Graph { public: virtual void draw(RotatedDC& dc, const vector& current, DrawLayer layer) const; virtual bool findItem(const RealPoint& pos, const RealRect& screen_rect, bool tight, vector& out) const; virtual void setData(const GraphDataP& d); void add(const GraphP& graph); private: vector items; }; // ----------------------------------------------------------------------------- : Graph control /// A control showing statistics in a graphical form class GraphControl : public wxControl { public: /// Create a graph control GraphControl(Window* parent, int id); /// Set the type of graph used, from a number of predefined choices void setLayout(GraphType type, bool force_refresh = false); /// Update the data in the graph void setData(const GraphDataPre& data); /// Update the data in the graph void setData(const GraphDataP& data); /// Retrieve the data in the graph GraphDataP getData() const; /// Is there a selection on the given axis? bool hasSelection(size_t axis) const; /// Get the current item along the given axis String getSelection(size_t axis) const; /// Get the current item along each axis vector getSelectionIndices() const; /// Get the current layout GraphType getLayout() const; /// Get the current dimensionality size_t getDimensionality() const; private: /// Graph object GraphP graph; GraphType layout; /// < The current layout /// The selected item per axis, or an empty vector if there is no selection /** If the value for an axis is -1, then all groups on that axis are selected */ vector current_item; DECLARE_EVENT_TABLE(); void onPaint(wxPaintEvent&); void onEraseBackground(wxEraseEvent&) {} void onSize (wxSizeEvent&); void onMouseDown(wxMouseEvent& ev); void onMotion(wxMouseEvent& ev); void onChar(wxKeyEvent& ev); void onSelectionChange(); };