Commit d69bb12f authored by twanvl's avatar twanvl

Informational tooltips in the graph control

parent a968d022
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include <util/alignment.hpp> #include <util/alignment.hpp>
#include <gfx/gfx.hpp> #include <gfx/gfx.hpp>
#include <wx/dcbuffer.h> #include <wx/dcbuffer.h>
#include <wx/tooltip.h>
DECLARE_TYPEOF_COLLECTION(GraphAxisP); DECLARE_TYPEOF_COLLECTION(GraphAxisP);
DECLARE_TYPEOF_COLLECTION(GraphElementP); DECLARE_TYPEOF_COLLECTION(GraphElementP);
...@@ -196,14 +197,29 @@ void GraphData::crossAxis(size_t axis1, size_t axis2, size_t axis3, vector<UInt> ...@@ -196,14 +197,29 @@ void GraphData::crossAxis(size_t axis1, size_t axis2, size_t axis3, vector<UInt>
} }
} }
UInt GraphData::count(const vector<int>& match) const {
if (match.size() != axes.size()) return 0;
UInt count = 0;
FOR_EACH_CONST(v, values) {
bool matches = true;
for (size_t i = 0 ; i < match.size() ; ++i) {
if (v[i] == -1 || match[i] != -1 && v[i] != match[i]) {
matches = false;
break;
}
}
count += matches;
}
return count;
}
// ----------------------------------------------------------------------------- : Graph1D // ----------------------------------------------------------------------------- : Graph1D
void Graph1D::draw(RotatedDC& dc, const vector<int>& current, DrawLayer layer) const { void Graph1D::draw(RotatedDC& dc, const vector<int>& current, DrawLayer layer) const {
draw(dc, axis < current.size() ? current.at(axis) : -1, layer); draw(dc, axis < current.size() ? current.at(axis) : -1, layer);
} }
bool Graph1D::findItem(const RealPoint& pos, const RealRect& rect, vector<int>& out) const { bool Graph1D::findItem(const RealPoint& pos, const RealRect& rect, bool tight, vector<int>& out) const {
int i = findItem(pos, rect); int i = findItem(pos, rect, tight);
if (i == -1) return false; if (i == -1) return false;
else { else {
out.clear(); out.clear();
...@@ -288,10 +304,11 @@ void BarGraph::draw(RotatedDC& dc, int current, DrawLayer layer) const { ...@@ -288,10 +304,11 @@ void BarGraph::draw(RotatedDC& dc, int current, DrawLayer layer) const {
} }
} }
} }
int BarGraph::findItem(const RealPoint& pos, const RealRect& rect) const { int BarGraph::findItem(const RealPoint& pos, const RealRect& rect, bool tight) const {
if (!data) return -1; if (!data) return -1;
if (pos.y > max(rect.top(), rect.bottom())) return -1; // below if (pos.y > max(rect.top(), rect.bottom())) return -1; // below
if (pos.y < min(rect.top(), rect.bottom())) return -1; // above if (pos.y < min(rect.top(), rect.bottom())) return -1; // above
// TODO: tight check
return find_bar_graph_column(rect.width, pos.x - rect.x, (int)axis_data().groups.size()); return find_bar_graph_column(rect.width, pos.x - rect.x, (int)axis_data().groups.size());
} }
...@@ -359,7 +376,7 @@ void BarGraph2D::draw(RotatedDC& dc, const vector<int>& current, DrawLayer layer ...@@ -359,7 +376,7 @@ void BarGraph2D::draw(RotatedDC& dc, const vector<int>& current, DrawLayer layer
} }
} }
} }
bool BarGraph2D::findItem(const RealPoint& pos, const RealRect& rect, vector<int>& out) const { bool BarGraph2D::findItem(const RealPoint& pos, const RealRect& rect, bool tight, vector<int>& out) const {
if (!data || data->axes.size() <= max(axis1,axis2)) return false; if (!data || data->axes.size() <= max(axis1,axis2)) return false;
if (pos.y > max(rect.top(), rect.bottom())) return false; // below if (pos.y > max(rect.top(), rect.bottom())) return false; // below
if (pos.y < min(rect.top(), rect.bottom())) return false; // above if (pos.y < min(rect.top(), rect.bottom())) return false; // above
...@@ -441,7 +458,7 @@ void PieGraph::draw(RotatedDC& dc, int current, DrawLayer layer) const { ...@@ -441,7 +458,7 @@ void PieGraph::draw(RotatedDC& dc, int current, DrawLayer layer) const {
} }
} }
} }
int PieGraph::findItem(const RealPoint& pos, const RealRect& rect) const { int PieGraph::findItem(const RealPoint& pos, const RealRect& rect, bool tight) const {
if (!data) return -1; if (!data) return -1;
// Rectangle for the pie // Rectangle for the pie
GraphAxis& axis = axis_data(); GraphAxis& axis = axis_data();
...@@ -449,7 +466,7 @@ int PieGraph::findItem(const RealPoint& pos, const RealRect& rect) const { ...@@ -449,7 +466,7 @@ int PieGraph::findItem(const RealPoint& pos, const RealRect& rect) const {
RealPoint pie_pos = rect.position() + rect.size() / 2; RealPoint pie_pos = rect.position() + rect.size() / 2;
// position in circle // position in circle
Vector2D delta = pos - pie_pos; Vector2D delta = pos - pie_pos;
if (delta.lengthSqr() > size*size) { if (delta.lengthSqr()*4 > size*size) {
return -1; // outside circle return -1; // outside circle
} }
double pos_angle = atan2(-delta.y, delta.x) - M_PI/2; // in range [-pi..pi] double pos_angle = atan2(-delta.y, delta.x) - M_PI/2; // in range [-pi..pi]
...@@ -516,7 +533,7 @@ void ScatterGraph::draw(RotatedDC& dc, const vector<int>& current, DrawLayer lay ...@@ -516,7 +533,7 @@ void ScatterGraph::draw(RotatedDC& dc, const vector<int>& current, DrawLayer lay
} }
} }
} }
bool ScatterGraph::findItem(const RealPoint& pos, const RealRect& rect, vector<int>& out) const { bool ScatterGraph::findItem(const RealPoint& pos, const RealRect& rect, bool tight, vector<int>& out) const {
if (!data || data->axes.size() <= max(axis1,axis2)) return false; if (!data || data->axes.size() <= max(axis1,axis2)) return false;
// clicked item // clicked item
GraphAxis& axis1 = axis1_data(); GraphAxis& axis1 = axis1_data();
...@@ -525,6 +542,8 @@ bool ScatterGraph::findItem(const RealPoint& pos, const RealRect& rect, vector<i ...@@ -525,6 +542,8 @@ bool ScatterGraph::findItem(const RealPoint& pos, const RealRect& rect, vector<i
int row = (int) floor((rect.bottom() - pos.y) / rect.height * axis2.groups.size()); int row = (int) floor((rect.bottom() - pos.y) / rect.height * axis2.groups.size());
if (col < 0 || col >= (int)axis1.groups.size()) return false; if (col < 0 || col >= (int)axis1.groups.size()) return false;
if (row < 0 || row >= (int)axis2.groups.size()) return false; if (row < 0 || row >= (int)axis2.groups.size()) return false;
// any values here?
if (tight && values[row + col * axis2.groups.size()] == 0) return false;
// done // done
out.clear(); out.clear();
out.insert(out.begin(), data->axes.size(), -1); out.insert(out.begin(), data->axes.size(), -1);
...@@ -697,7 +716,8 @@ void GraphLegend::draw(RotatedDC& dc, int current, DrawLayer layer) const { ...@@ -697,7 +716,8 @@ void GraphLegend::draw(RotatedDC& dc, int current, DrawLayer layer) const {
} }
} }
} }
int GraphLegend::findItem(const RealPoint& pos, const RealRect& rect) const { int GraphLegend::findItem(const RealPoint& pos, const RealRect& rect, bool tight) const {
if (tight) return -1;
RealPoint mypos = align_in_rect(alignment, size, rect); RealPoint mypos = align_in_rect(alignment, size, rect);
RealPoint pos2(pos.x - mypos.x, pos.y - mypos.y); RealPoint pos2(pos.x - mypos.x, pos.y - mypos.y);
if (pos2.x < 0 || pos2.y < 0 || pos2.x >= size.width || pos2.y >= size.height) return -1; if (pos2.x < 0 || pos2.y < 0 || pos2.x >= size.width || pos2.y >= size.height) return -1;
...@@ -787,7 +807,7 @@ void GraphLabelAxis::draw(RotatedDC& dc, int current, DrawLayer layer) const { ...@@ -787,7 +807,7 @@ void GraphLabelAxis::draw(RotatedDC& dc, int current, DrawLayer layer) const {
} }
} }
} }
int GraphLabelAxis::findItem(const RealPoint& pos, const RealRect& rect) const { int GraphLabelAxis::findItem(const RealPoint& pos, const RealRect& rect, bool tight) const {
GraphAxis& axis = axis_data(); GraphAxis& axis = axis_data();
int col; int col;
if (direction == HORIZONTAL) { if (direction == HORIZONTAL) {
...@@ -859,10 +879,10 @@ void GraphWithMargins::draw(RotatedDC& dc, const vector<int>& current, DrawLayer ...@@ -859,10 +879,10 @@ void GraphWithMargins::draw(RotatedDC& dc, const vector<int>& current, DrawLayer
Rotater rot(dc, new_size); Rotater rot(dc, new_size);
graph->draw(dc, current, layer); graph->draw(dc, current, layer);
} }
bool GraphWithMargins::findItem(const RealPoint& pos, const RealRect& rect, vector<int>& out) const { bool GraphWithMargins::findItem(const RealPoint& pos, const RealRect& rect, bool tight, vector<int>& out) const {
RealRect inner = rect.move(margin_left, margin_top, - margin_left - margin_right, - margin_top - margin_bottom); RealRect inner = rect.move(margin_left, margin_top, - margin_left - margin_right, - margin_top - margin_bottom);
if (upside_down) { inner.y += inner.height; inner.height = -inner.height; } if (upside_down) { inner.y += inner.height; inner.height = -inner.height; }
return graph->findItem(pos, inner, out); return graph->findItem(pos, inner, tight, out);
} }
void GraphWithMargins::setData(const GraphDataP& d) { void GraphWithMargins::setData(const GraphDataP& d) {
Graph::setData(d); Graph::setData(d);
...@@ -876,9 +896,9 @@ void GraphContainer::draw(RotatedDC& dc, const vector<int>& current, DrawLayer l ...@@ -876,9 +896,9 @@ void GraphContainer::draw(RotatedDC& dc, const vector<int>& current, DrawLayer l
g->draw(dc, current, layer); g->draw(dc, current, layer);
} }
} }
bool GraphContainer::findItem(const RealPoint& pos, const RealRect& rect, vector<int>& out) const { bool GraphContainer::findItem(const RealPoint& pos, const RealRect& rect, bool tight, vector<int>& out) const {
FOR_EACH_CONST_REVERSE(g, items) { FOR_EACH_CONST_REVERSE(g, items) {
if (g->findItem(pos, rect, out)) return true; if (g->findItem(pos, rect, tight, out)) return true;
} }
return false; return false;
} }
...@@ -988,7 +1008,7 @@ void GraphControl::onSize(wxSizeEvent&) { ...@@ -988,7 +1008,7 @@ void GraphControl::onSize(wxSizeEvent&) {
void GraphControl::onMouseDown(wxMouseEvent& ev) { void GraphControl::onMouseDown(wxMouseEvent& ev) {
if (!graph) return; if (!graph) return;
wxSize cs = GetClientSize(); wxSize cs = GetClientSize();
if (graph->findItem(RealPoint(ev.GetX(), ev.GetY()), RealRect(RealPoint(0,0),cs), current_item)) { if (graph->findItem(RealPoint(ev.GetX(), ev.GetY()), RealRect(RealPoint(0,0),cs), false, current_item)) {
onSelectionChange(); onSelectionChange();
} }
ev.Skip(); // focus ev.Skip(); // focus
...@@ -1049,9 +1069,38 @@ String GraphControl::getSelection(size_t axis) const { ...@@ -1049,9 +1069,38 @@ String GraphControl::getSelection(size_t axis) const {
return a.groups[current_item[axis]].name; return a.groups[current_item[axis]].name;
} }
void GraphControl::onMotion(wxMouseEvent& ev) {
if (!graph) return;
wxSize cs = GetClientSize();
vector<int> hovered_item(current_item.size());
if (graph->findItem(RealPoint(ev.GetX(), ev.GetY()), RealRect(RealPoint(0,0),cs), true, hovered_item)) {
// determine tooltip
const GraphData& data = *graph->getData();
String tip;
for (size_t dim = 0 ; dim < hovered_item.size() ; ++dim) {
if (hovered_item[dim] != -1 && (size_t)hovered_item[dim] < data.axes[dim]->groups.size()) {
if (!tip.empty()) tip += _("\n");
tip += data.axes[dim]->name + _(": ") + data.axes[dim]->groups[hovered_item[dim]].name;
}
}
UInt count = data.count(hovered_item);
tip += String::Format(_("\n%d "), count) + (count == 1 ? _TYPE_("card") : _TYPE_("cards"));
tip.Replace(_(" "),_("\xA0"));
// set tooltip
SetToolTip(tip);
} else {
// Note: don't use SetToolTip directly, we don't want to create a tip if it doesn't exist
// on winXP this destroys the multiline behaviour.
wxToolTip* tooltip = GetToolTip();
if (tooltip) tooltip->SetTip(_(""));
}
ev.Skip();
}
BEGIN_EVENT_TABLE(GraphControl, wxControl) BEGIN_EVENT_TABLE(GraphControl, wxControl)
EVT_PAINT (GraphControl::onPaint) EVT_PAINT (GraphControl::onPaint)
EVT_SIZE (GraphControl::onSize) EVT_SIZE (GraphControl::onSize)
EVT_LEFT_DOWN (GraphControl::onMouseDown) EVT_LEFT_DOWN (GraphControl::onMouseDown)
EVT_MOTION (GraphControl::onMotion)
EVT_CHAR (GraphControl::onChar) EVT_CHAR (GraphControl::onChar)
END_EVENT_TABLE () END_EVENT_TABLE ()
...@@ -106,6 +106,8 @@ class GraphData : public IntrusivePtrBase<GraphData> { ...@@ -106,6 +106,8 @@ class GraphData : public IntrusivePtrBase<GraphData> {
void crossAxis(size_t axis1, size_t axis2, vector<UInt>& out) const; void crossAxis(size_t axis1, size_t axis2, vector<UInt>& out) const;
/// Create a cross table for three axes /// Create a cross table for three axes
void crossAxis(size_t axis1, size_t axis2, size_t axis3, vector<UInt>& out) const; void crossAxis(size_t axis1, size_t axis2, size_t axis3, vector<UInt>& out) const;
/// Count the number of elements with the given values, -1 is a wildcard
UInt count(const vector<int>& match) const;
}; };
...@@ -128,7 +130,7 @@ class Graph : public IntrusivePtrVirtualBase { ...@@ -128,7 +130,7 @@ class Graph : public IntrusivePtrVirtualBase {
/// Draw this graph, filling the internalRect() of the dc. /// Draw this graph, filling the internalRect() of the dc.
virtual void draw(RotatedDC& dc, const vector<int>& current, DrawLayer layer) const = 0; virtual void draw(RotatedDC& dc, const vector<int>& current, DrawLayer layer) const = 0;
/// Find the item at the given position, the rectangle gives the screen size /// Find the item at the given position, the rectangle gives the screen size
virtual bool findItem(const RealPoint& pos, const RealRect& rect, vector<int>& out) const { return false; } virtual bool findItem(const RealPoint& pos, const RealRect& rect, bool tight, vector<int>& out) const { return false; }
/// Change the data /// Change the data
virtual void setData(const GraphDataP& d) { data = d; } virtual void setData(const GraphDataP& d) { data = d; }
/// Get the data /// Get the data
...@@ -144,11 +146,11 @@ class Graph1D : public Graph { ...@@ -144,11 +146,11 @@ class Graph1D : public Graph {
public: public:
inline Graph1D(size_t axis) : axis(axis) {} inline Graph1D(size_t axis) : axis(axis) {}
virtual void draw(RotatedDC& dc, const vector<int>& current, DrawLayer layer) const; virtual void draw(RotatedDC& dc, const vector<int>& current, DrawLayer layer) const;
virtual bool findItem(const RealPoint& pos, const RealRect& rect, vector<int>& out) const; virtual bool findItem(const RealPoint& pos, const RealRect& rect, bool tight, vector<int>& out) const;
protected: protected:
size_t axis; size_t axis;
/// Find an item, return the position along the axis, or -1 if not found /// Find an item, return the position along the axis, or -1 if not found
virtual int findItem(const RealPoint& pos, const RealRect& rect) const { return -1; } virtual int findItem(const RealPoint& pos, const RealRect& rect, bool tight) const { return -1; }
virtual void draw(RotatedDC& dc, int current, DrawLayer layer) const = 0; virtual void draw(RotatedDC& dc, int current, DrawLayer layer) const = 0;
inline GraphAxis& axis_data() const { return *data->axes.at(axis); } inline GraphAxis& axis_data() const { return *data->axes.at(axis); }
}; };
...@@ -170,7 +172,7 @@ class BarGraph : public Graph1D { ...@@ -170,7 +172,7 @@ class BarGraph : public Graph1D {
public: public:
inline BarGraph(size_t axis) : Graph1D(axis) {} inline BarGraph(size_t axis) : Graph1D(axis) {}
virtual void draw(RotatedDC& dc, int current, DrawLayer layer) const; virtual void draw(RotatedDC& dc, int current, DrawLayer layer) const;
virtual int findItem(const RealPoint& pos, const RealRect& rect) const; virtual int findItem(const RealPoint& pos, const RealRect& rect, bool tight) const;
}; };
// A bar graph with stacked bars // A bar graph with stacked bars
...@@ -178,7 +180,7 @@ class BarGraph2D : public Graph2D { ...@@ -178,7 +180,7 @@ class BarGraph2D : public Graph2D {
public: public:
inline BarGraph2D(size_t axis_h, size_t axis_v) : Graph2D(axis_h, axis_v) {} inline BarGraph2D(size_t axis_h, size_t axis_v) : Graph2D(axis_h, axis_v) {}
virtual void draw(RotatedDC& dc, const vector<int>& current, DrawLayer layer) const; virtual void draw(RotatedDC& dc, const vector<int>& current, DrawLayer layer) const;
virtual bool findItem(const RealPoint& pos, const RealRect& rect, vector<int>& out) const; virtual bool findItem(const RealPoint& pos, const RealRect& rect, bool tight, vector<int>& out) const;
}; };
/// A pie graph /// A pie graph
...@@ -186,7 +188,7 @@ class PieGraph : public Graph1D { ...@@ -186,7 +188,7 @@ class PieGraph : public Graph1D {
public: public:
inline PieGraph(size_t axis) : Graph1D(axis) {} inline PieGraph(size_t axis) : Graph1D(axis) {}
virtual void draw(RotatedDC& dc, int current, DrawLayer layer) const; virtual void draw(RotatedDC& dc, int current, DrawLayer layer) const;
virtual int findItem(const RealPoint& pos, const RealRect& rect) const; virtual int findItem(const RealPoint& pos, const RealRect& rect, bool tight) const;
}; };
/// A scatter plot /// A scatter plot
...@@ -194,7 +196,7 @@ class ScatterGraph : public Graph2D { ...@@ -194,7 +196,7 @@ class ScatterGraph : public Graph2D {
public: public:
inline ScatterGraph(size_t axis1, size_t axis2) : Graph2D(axis1, axis2) {} inline ScatterGraph(size_t axis1, size_t axis2) : Graph2D(axis1, axis2) {}
virtual void draw(RotatedDC& dc, const vector<int>& current, DrawLayer layer) const; virtual void draw(RotatedDC& dc, const vector<int>& current, DrawLayer layer) const;
virtual bool findItem(const RealPoint& pos, const RealRect& rect, vector<int>& out) const; virtual bool findItem(const RealPoint& pos, const RealRect& rect, bool tight, vector<int>& out) const;
virtual void setData(const GraphDataP& d); virtual void setData(const GraphDataP& d);
protected: protected:
UInt max_value; ///< highest value UInt max_value; ///< highest value
...@@ -226,7 +228,7 @@ class GraphLegend : public Graph1D { ...@@ -226,7 +228,7 @@ class GraphLegend : public Graph1D {
{} {}
virtual RealSize determineSize(RotatedDC& dc) const; virtual RealSize determineSize(RotatedDC& dc) const;
virtual void draw(RotatedDC& dc, int current, DrawLayer layer) const; virtual void draw(RotatedDC& dc, int current, DrawLayer layer) const;
virtual int findItem(const RealPoint& pos, const RealRect& rect) const; virtual int findItem(const RealPoint& pos, const RealRect& rect, bool tight) const;
private: private:
mutable RealSize size, item_size; mutable RealSize size, item_size;
Alignment alignment; Alignment alignment;
...@@ -265,7 +267,7 @@ class GraphLabelAxis : public Graph1D { ...@@ -265,7 +267,7 @@ class GraphLabelAxis : public Graph1D {
: Graph1D(axis), direction(direction), rotate(rotate), draw_lines(draw_lines), label(label) : Graph1D(axis), direction(direction), rotate(rotate), draw_lines(draw_lines), label(label)
{} {}
virtual void draw(RotatedDC& dc, int current, DrawLayer layer) const; virtual void draw(RotatedDC& dc, int current, DrawLayer layer) const;
virtual int findItem(const RealPoint& pos, const RealRect& rect) const; virtual int findItem(const RealPoint& pos, const RealRect& rect, bool tight) const;
private: private:
Direction direction; Direction direction;
int levels; int levels;
...@@ -294,7 +296,7 @@ class GraphWithMargins : public Graph { ...@@ -294,7 +296,7 @@ class GraphWithMargins : public Graph {
, upside_down(upside_down) , upside_down(upside_down)
{} {}
virtual void draw(RotatedDC& dc, const vector<int>& current, DrawLayer layer) const; virtual void draw(RotatedDC& dc, const vector<int>& current, DrawLayer layer) const;
virtual bool findItem(const RealPoint& pos, const RealRect& rect, vector<int>& out) const; virtual bool findItem(const RealPoint& pos, const RealRect& rect, bool tight, vector<int>& out) const;
virtual void setData(const GraphDataP& d); virtual void setData(const GraphDataP& d);
private: private:
const GraphP graph; const GraphP graph;
...@@ -306,7 +308,7 @@ class GraphWithMargins : public Graph { ...@@ -306,7 +308,7 @@ class GraphWithMargins : public Graph {
class GraphContainer : public Graph { class GraphContainer : public Graph {
public: public:
virtual void draw(RotatedDC& dc, const vector<int>& current, DrawLayer layer) const; virtual void draw(RotatedDC& dc, const vector<int>& current, DrawLayer layer) const;
virtual bool findItem(const RealPoint& pos, const RealRect& rect, vector<int>& out) const; virtual bool findItem(const RealPoint& pos, const RealRect& rect, bool tight, vector<int>& out) const;
virtual void setData(const GraphDataP& d); virtual void setData(const GraphDataP& d);
void add(const GraphP& graph); void add(const GraphP& graph);
...@@ -352,6 +354,7 @@ class GraphControl : public wxControl { ...@@ -352,6 +354,7 @@ class GraphControl : public wxControl {
void onPaint(wxPaintEvent&); void onPaint(wxPaintEvent&);
void onSize (wxSizeEvent&); void onSize (wxSizeEvent&);
void onMouseDown(wxMouseEvent& ev); void onMouseDown(wxMouseEvent& ev);
void onMotion(wxMouseEvent& ev);
void onChar(wxKeyEvent& ev); void onChar(wxKeyEvent& ev);
void onSelectionChange(); void onSelectionChange();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment