//+----------------------------------------------------------------------------+ //| Description: Magic Set Editor - Program to make Magic (tm) cards | //| Copyright: (C) 2001 - 2007 Twan van Laarhoven | //| License: GNU General Public License 2 or later (see file COPYING) | //+----------------------------------------------------------------------------+ // ----------------------------------------------------------------------------- : Includes #include #include #include #include DECLARE_TYPEOF_COLLECTION(GraphAxisP); DECLARE_TYPEOF_COLLECTION(GraphElementP); DECLARE_TYPEOF_COLLECTION(GraphGroup); DECLARE_TYPEOF_COLLECTION(GraphP); DECLARE_TYPEOF_COLLECTION(int); DECLARE_TYPEOF_COLLECTION(vector); DECLARE_TYPEOF_COLLECTION(String); DECLARE_TYPEOF_COLLECTION(UInt); DECLARE_TYPEOF(map); template inline T sgn(T v) { return v < 0 ? -1 : 1; } // ----------------------------------------------------------------------------- : Events DEFINE_EVENT_TYPE(EVENT_GRAPH_SELECT); // ----------------------------------------------------------------------------- : GraphData GraphElement::GraphElement(const String& v1) { values.push_back(v1); } GraphElement::GraphElement(const String& v1, const String& v2) { values.push_back(v1); values.push_back(v2); } GraphData::GraphData(const GraphDataPre& d) : axes(d.axes) { // total size size = (UInt)d.elements.size(); // find groups on each axis size_t i = 0; FOR_EACH(a, axes) { map counts; // note: default constructor for UInt() does initialize to 0 FOR_EACH_CONST(e, d.elements) { counts[e->values[i]] += 1; } // TODO: allow some ordering in the groups if (a->numeric) { // TODO: start at something other than 0? // TODO: support fractions? size_t left = counts.size(); int i = 0; while (left) { String is = String() << i++; map::const_iterator it = counts.find(is); if (it == counts.end()) { // not found, add a 0 bar a->groups.push_back(GraphGroup(is, 0)); } else { a->groups.push_back(GraphGroup(is, it->second)); a->max = max(a->max, it->second); a->total += it->second; left--; } if (i > 100) { // prevent infinite loops if there are non-numeric entries // drop empty tail while (a->groups.size() > 1 && a->groups.back().size == 0) { a->groups.pop_back(); } break; } } } else if (a->order) { // specific group order FOR_EACH_CONST(gn, *a->order) { UInt count = counts[gn]; a->groups.push_back(GraphGroup(gn, count)); a->max = max(a->max, count); a->total += count; } } else { FOR_EACH(c, counts) { a->groups.push_back(GraphGroup(c.first, c.second)); a->max = max(a->max, c.second); a->total += c.second; } } // colors if (a->auto_color == AUTO_COLOR_NO && a->colors) { // use colors from the table FOR_EACH(g, a->groups) { map::const_iterator it = a->colors->find(g.name); if (it != a->colors->end()) { g.color = it->second; } } } else { // find some nice colors for the groups double hue = 0.6; // start hue bool first = true; FOR_EACH(g, a->groups) { double amount = a->auto_color == AUTO_COLOR_EVEN ? 1. / a->groups.size() : double(g.size) / a->total; // amount this group takes if (!first) hue += amount/2; g.color = hsl2rgb(hue, 1.0, 0.5); hue += amount / 2; first = false; } } ++i; } // count elements in each position values.clear(); FOR_EACH_CONST(e, d.elements) { // find index j in elements vector group_nrs(axes.size(), -1); int i = 0; FOR_EACH(a, axes) { String v = e->values[i]; int j = 0; FOR_EACH(g, a->groups) { if (v == g.name) { group_nrs[i] = j; break; } ++j; } ++i; } values.push_back(group_nrs); } } void GraphData::crossAxis(size_t axis1, size_t axis2, vector& out) const { size_t a1_size = axes[axis1]->groups.size(); size_t a2_size = axes[axis2]->groups.size(); out.clear(); out.resize(a1_size * a2_size, 0); FOR_EACH_CONST(v, values) { int v1 = v[axis1], v2 = v[axis2]; if (v1 >= 0 && v2 >= 0) { out[a2_size * v1 + v2]++; } } } // ----------------------------------------------------------------------------- : Graph1D void Graph1D::draw(RotatedDC& dc, const vector& current, DrawLayer layer) const { draw(dc, axis < current.size() ? current.at(axis) : -1, layer); } bool Graph1D::findItem(const RealPoint& pos, const RealRect& rect, vector& out) const { int i = findItem(pos, rect); if (i == -1) return false; else { out.clear(); out.insert(out.begin(), data->axes.size(), -1); out.at(axis) = i; return true; } } // ----------------------------------------------------------------------------- : Graph2D void Graph2D::setData(const GraphDataP& d) { Graph::setData(d); if (data->axes.size() <= max(axis1,axis2)) return; d->crossAxis(axis1,axis2,values); } // ----------------------------------------------------------------------------- : Bar Graph /// Rectangle for the bar of a bar graph RealRect bar_graph_bar(const RealRect& rect, int group, int group_count, int start, int end, int max) { double width_space = rect.width / group_count; // including spacing double width = width_space / 5 * 4; double space = width_space / 5; double step_height = rect.height / max; // multiplier for bar height int top = rect.bottom() - start * step_height; int bottom = rect.bottom() - end * step_height; if (bottom < top) swap(top,bottom); bottom += 1; return RealRect( rect.x + width_space * group + space / 2, top, width, bottom - top ); } /// Which column of the bar graph with count bars is coordinate x in? int find_bar_graph_column(double width, double x, int count) { double width_space = width / count; // including spacing double space = width_space / 5; // Find column in which the point could be located int col = floor(x / width_space); if (col < 0 || col >= count) return -1; // not a column double in_col = x - col * width_space; if (in_col < space / 2) return -1; // left if (in_col > width_space - space / 2) return -1; // right return col; } void BarGraph::draw(RotatedDC& dc, int current, DrawLayer layer) const { if (!data) return; // Rectangle for bars RealRect rect = dc.getInternalRect(); GraphAxis& axis = axis_data(); int count = int(axis.groups.size()); // Bar sizes if (layer == LAYER_SELECTION) { // Highlight current column Color bg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); if (current >= 0) { const GraphGroup& group = axis.groups[current]; RealRect bar = bar_graph_bar(rect, current, count, 0, group.size, axis.max); dc.SetPen(*wxTRANSPARENT_PEN); dc.SetBrush(lerp(bg, group.color, 0.25)); dc.DrawRectangle(bar.move(-5,-5,10,10)); dc.SetBrush(lerp(bg, group.color, 0.5)); dc.DrawRectangle(bar.move(-2,-2,4,4)); } } else if (layer == LAYER_VALUES) { // Draw bars dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); int i = 0; FOR_EACH_CONST(g, axis.groups) { // draw bar dc.SetBrush(g.color); dc.DrawRectangle(bar_graph_bar(rect, i++, count, 0, g.size, axis.max)); } } } int BarGraph::findItem(const RealPoint& pos, const RealRect& rect) const { if (!data) return -1; if (pos.y > max(rect.top(), rect.bottom())) return -1; // below if (pos.y < min(rect.top(), rect.bottom())) return -1; // above return find_bar_graph_column(rect.width, pos.x - rect.x, (int)axis_data().groups.size()); } // ----------------------------------------------------------------------------- : Bar Graph 2D void BarGraph2D::draw(RotatedDC& dc, const vector& current, DrawLayer layer) const { if (!data || data->axes.size() <= max(axis1,axis2)) return; // Rectangle for bars RealRect rect = dc.getInternalRect(); GraphAxis& axis1 = axis1_data(); // the major axis GraphAxis& axis2 = axis2_data(); // the stacked axis int count = int(axis1.groups.size()); // Draw if (layer == LAYER_SELECTION) { // Highlight current column Color bg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); int cur1 = this->axis1 < current.size() ? current[this->axis1] : -1; int cur2 = this->axis2 < current.size() ? current[this->axis2] : -1; if (cur1 >= 0) { // draw selected bar int start = 0; int j = 0; FOR_EACH_CONST(g2, axis2.groups) { int end = start + values[j + axis2.groups.size() * cur1]; if (j == cur2 || cur2 < 0) { RealRect bar = bar_graph_bar(rect, cur1, count, start, end, axis1.max); dc.SetBrush(lerp(bg, g2.color, 0.25)); dc.DrawRectangle(bar.move(-5,0,10,0)); dc.SetBrush(lerp(bg, g2.color, 0.5)); dc.DrawRectangle(bar.move(-2,0,4,0)); } start = end; ++j; } } else if (cur2 >= 0) { // entire row // TODO } } else if (layer == LAYER_VALUES) { // Draw bars dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); for (int i = 0 ; i < count ; ++i) { // draw stacked bars int start = 0; int j = 0; FOR_EACH_CONST(g2, axis2.groups) { int end = start + values[j++ + axis2.groups.size() * i]; dc.SetBrush(g2.color); dc.DrawRectangle(bar_graph_bar(rect, i, count, start, end, axis1.max)); start = end; } } } } bool BarGraph2D::findItem(const RealPoint& pos, const RealRect& rect, vector& out) const { if (!data || data->axes.size() <= max(axis1,axis2)) return false; if (pos.y > max(rect.top(), rect.bottom())) return false; // below if (pos.y < min(rect.top(), rect.bottom())) return false; // above // column GraphAxis& axis1 = axis1_data(); // the major axis int count = (int)axis1.groups.size(); int col = find_bar_graph_column(rect.width, pos.x - rect.x, count); if (col < 0) return false; // row int max_value = (int)axis1.max; int value = (rect.bottom() - pos.y) / rect.height * max_value; if (value < 0 || value > max_value) return false; // find row int row = -1; size_t vs = col * axis2_data().groups.size(); for (int i = 0 ; i < count ; ++i) { value -= values[vs+i]; if (value < 0) { // in this layer of the stack row = i; break; } } if (row == -1) return false; // done out.clear(); out.insert(out.begin(), data->axes.size(), -1); out.at(this->axis1) = col; out.at(this->axis2) = row; return true; } // ----------------------------------------------------------------------------- : Pie Graph void PieGraph::draw(RotatedDC& dc, int current, DrawLayer layer) const { if (!data) return; // Rectangle for the pie GraphAxis& axis = axis_data(); RealRect rect = dc.getInternalRect(); double size = min(rect.width, rect.height); RealSize pie_size(size, size); RealSize pie_size_large(size+20, size+20); RealPoint pie_pos = rect.position() + rect.size() / 2; //RealPoint pos = align_in_rect(ALIGN_MIDDLE_CENTER, RealSize(size,size), rect); // draw items if (layer == LAYER_VALUES) { Color fg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); dc.SetPen(fg); // draw pies double angle = 0; int i = 0; FOR_EACH_CONST(g, axis.groups) { // draw pie dc.SetBrush(g.color); if (g.size > 0) { double end_angle = angle + 2 * M_PI * (double)g.size / axis.total; dc.DrawEllipticArc(pie_pos, i == current ? pie_size_large : pie_size, angle, end_angle); angle = end_angle; } ++i; } // draw spokes if (axis.groups.size() > 1) { angle = 0; FOR_EACH_CONST(g, axis.groups) { if (g.size > 0) { dc.DrawEllipticSpoke(pie_pos, pie_size, angle); angle += 2 * M_PI * (double)g.size / axis.total; } } } } } int PieGraph::findItem(const RealPoint& pos, const RealRect& rect) const { if (!data) return -1; // Rectangle for the pie GraphAxis& axis = axis_data(); double size = min(rect.width, rect.height); RealPoint pie_pos = rect.position() + rect.size() / 2; // position in circle Vector2D delta = pos - pie_pos; if (delta.lengthSqr() > size*size) { return -1; // outside circle } double pos_angle = atan2(-delta.y, delta.x); // in range [-pi..pi] if (pos_angle < 0) pos_angle += 2 * M_PI; // find angle double angle = 0; int i = 0; FOR_EACH_CONST(g, axis.groups) { angle += 2 * M_PI * (double)g.size / axis.total; if (angle > pos_angle) return i; ++i; } return -1; //should not happen } // ----------------------------------------------------------------------------- : Scatter Plot void ScatterGraph::draw(RotatedDC& dc, const vector& current, DrawLayer layer) const { if (!data || data->axes.size() <= max(axis1,axis2)) return; // Rectangle for bars RealRect rect = dc.getInternalRect(); GraphAxis& axis1 = axis1_data(); // the major axis GraphAxis& axis2 = axis2_data(); // the stacked axis RealSize size(rect.width / axis1.groups.size(), rect.height / axis2.groups.size()); // size for a single cell double step = min(size.width, size.height) / sqrt((double)max_value) / 2.01; // Draw dc.SetPen(*wxTRANSPARENT_PEN); if (layer == LAYER_SELECTION) { Color bg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); int cur1 = this->axis1 < current.size() ? current[this->axis1] : -1; int cur2 = this->axis2 < current.size() ? current[this->axis2] : -1; if (cur1 >= 0 && cur2 >= 0) { UInt value = values[cur1 * axis2.groups.size() + cur2]; if (value) { dc.SetBrush(lerp(bg,lerp(axis1.groups[cur1].color, axis2.groups[cur2].color, 0.5),0.5)); dc.DrawCircle(RealPoint(rect.left() + cur1 * size.width, rect.bottom() - (cur2+1) * size.height) + size/2, sqrt((double)value) * step + 5); } } else if (cur1 >= 0) { dc.SetBrush(lerp(bg,axis1.groups[cur1].color,0.3)); dc.DrawRectangle(RealRect(rect.x + cur1 * size.width, rect.y, size.width, rect.height)); } else if (cur2 >= 0) { dc.SetBrush(lerp(bg,axis2.groups[cur2].color,0.3)); dc.DrawRectangle(RealRect(rect.x, rect.bottom() - (cur2+1) * size.height, rect.width, size.height)); } } else { size_t i = 0; double x = rect.left(); FOR_EACH_CONST(g1, axis1.groups) { double y = rect.bottom() - size.height; FOR_EACH_CONST(g2, axis2.groups) { UInt value = values[i++]; dc.SetBrush(lerp(g1.color, g2.color, 0.5)); dc.DrawCircle(RealPoint(x,y) + size/2, sqrt((double)value) * step); y -= size.height; } x += size.width; } } } bool ScatterGraph::findItem(const RealPoint& pos, const RealRect& rect, vector& out) const { if (!data || data->axes.size() <= max(axis1,axis2)) return false; // clicked item GraphAxis& axis1 = axis1_data(); GraphAxis& axis2 = axis2_data(); int col = floor((pos.x - rect.x) / rect.width * axis1.groups.size()); int row = floor((rect.bottom() - pos.y) / rect.height * axis2.groups.size()); if (col < 0 || col >= (int)axis1.groups.size()) return false; if (row < 0 || row >= (int)axis2.groups.size()) return false; // done out.clear(); out.insert(out.begin(), data->axes.size(), -1); out.at(this->axis1) = col; out.at(this->axis2) = row; return true; } void ScatterGraph::setData(const GraphDataP& d) { Graph2D::setData(d); // find maximum max_value = 0; FOR_EACH(v, values) { max_value = max(max_value, v); } } // ----------------------------------------------------------------------------- : Graph Legend // ----------------------------------------------------------------------------- : Graph label axis void GraphLabelAxis::draw(RotatedDC& dc, int current, DrawLayer layer) const { RealRect rect = dc.getInternalRect(); GraphAxis& axis = axis_data(); int count = int(axis.groups.size()); // Draw dc.SetFont(*wxNORMAL_FONT); if (layer == LAYER_SELECTION) { // highlight selection } else if (layer != LAYER_AXES) { if (direction == HORIZONTAL) { double width = rect.width / count; // width of an item // Draw labels double x = rect.x; FOR_EACH_CONST(g, axis.groups) { // draw label, aligned bottom center RealSize text_size = dc.GetTextExtent(g.name); dc.SetClippingRegion(RealRect(x + 2, rect.bottom() + 3, width - 4, text_size.height)); dc.DrawText(g.name, align_in_rect(ALIGN_TOP_CENTER, text_size, RealRect(x, rect.bottom() + 3, width, 0))); dc.DestroyClippingRegion(); x += width; } // Draw lines Color bg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); Color fg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); if (draw_lines) { dc.SetPen(lerp(bg, fg, 0.5)); for (int i = 0 ; i < count ; ++i) { if (draw_lines == DRAW_LINES_BETWEEN) { dc.DrawLine(RealPoint(rect.x + (i+1.0)*width, rect.top()), RealPoint(rect.x + (i+1.0)*width, rect.bottom())); } else { dc.DrawLine(RealPoint(rect.x + (i+0.5)*width, rect.top()), RealPoint(rect.x + (i+0.5)*width, rect.bottom() + 2)); } } } // always draw axis line dc.SetPen(fg); dc.DrawLine(rect.topLeft(), rect.bottomLeft()); } else { double height = rect.height / count; // width of an item // Draw labels double y = rect.bottom(); FOR_EACH_CONST(g, axis.groups) { // draw label, aligned bottom center RealSize text_size = dc.GetTextExtent(g.name); //dc.SetClippingRegion(RealRect(x + 2, rect.bottom() + 3, width - 4, text_size.height)); dc.DrawText(g.name, align_in_rect(ALIGN_MIDDLE_RIGHT, text_size, RealRect(-4, y, 0, -height))); //dc.DestroyClippingRegion(); y -= height; } // Draw lines Color bg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); Color fg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); if (draw_lines) { dc.SetPen(lerp(bg, fg, 0.5)); for (int i = 0 ; i < count ; ++i) { if (draw_lines == DRAW_LINES_BETWEEN) { dc.DrawLine(RealPoint(rect.left(), rect.bottom() - (i+1.0)*height), RealPoint(rect.right(), rect.bottom() - (i+1.0)*height)); } else { dc.DrawLine(RealPoint(rect.left() - 2, rect.bottom() - (i+0.5)*height), RealPoint(rect.right(), rect.bottom() - (i+0.5)*height)); } } } // always draw axis line dc.SetPen(fg); dc.DrawLine(rect.bottomLeft(), rect.bottomRight()); } } } int GraphLabelAxis::findItem(const RealPoint& pos, const RealRect& rect) const { GraphAxis& axis = axis_data(); int col; if (direction == HORIZONTAL) { col = floor((pos.x - rect.x) / rect.width * axis.groups.size()); if (pos.y < rect.bottom()) return -1; } else { col = floor((rect.bottom() - pos.y) / rect.height * axis.groups.size()); if (pos.x > rect.left()) return -1; } if (col < 0 || col >= (int)axis.groups.size()) return -1; return col; } // ----------------------------------------------------------------------------- : Graph value axis void GraphValueAxis::draw(RotatedDC& dc, int current, DrawLayer layer) const { if (layer != LAYER_AXES) return; if (!data) return; // How many labels and lines to draw? RealRect rect = dc.getInternalRect(); GraphAxis& axis = axis_data(); double step_height = rect.height / axis.max; // height of a single value dc.SetFont(*wxNORMAL_FONT); UInt label_step = (UInt)floor(max(1.0, (dc.GetCharHeight() + 1) / step_height)); // values per labeled line // Colors Color bg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); Color fg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); // Draw backlines (horizontal) and value labels dc.SetPen(lerp(bg, fg, 0.5)); for (UInt i = 0 ; i <= axis.max ; ++i) { if (i % label_step == 0) { int y = rect.bottom() - i * step_height; dc.DrawLine(RealPoint(rect.left() - 2, y), RealPoint(rect.right(), y)); // draw label, aligned middle right String label; label << i; RealSize text_size = dc.GetTextExtent(label); dc.DrawText(label, align_in_rect(ALIGN_MIDDLE_RIGHT, text_size, RealRect(rect.x - 4, y, 0, 0))); } } // Draw axis dc.SetPen(fg); dc.DrawLine(rect.bottomLeft() - RealSize(2,0), rect.bottomRight()); } // ----------------------------------------------------------------------------- : Graph with margins void GraphWithMargins::draw(RotatedDC& dc, const vector& current, DrawLayer layer) const { RealRect inner = dc.getInternalRect().move(margin_left, margin_top, - margin_left - margin_right, - margin_top - margin_bottom); if (upside_down) { inner.y += inner.height; inner.height = -inner.height; } Rotation new_size(0, inner); Rotater rot(dc, new_size); graph->draw(dc, current, layer); } bool GraphWithMargins::findItem(const RealPoint& pos, const RealRect& rect, vector& out) const { 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; } return graph->findItem(pos, inner, out); } void GraphWithMargins::setData(const GraphDataP& d) { Graph::setData(d); graph->setData(d); } // ----------------------------------------------------------------------------- : Graph Container void GraphContainer::draw(RotatedDC& dc, const vector& current, DrawLayer layer) const { FOR_EACH_CONST(g, items) { g->draw(dc, current, layer); } } bool GraphContainer::findItem(const RealPoint& pos, const RealRect& rect, vector& out) const { FOR_EACH_CONST_REVERSE(g, items) { if (g->findItem(pos, rect, out)) return true; } return false; } void GraphContainer::setData(const GraphDataP& d) { Graph::setData(d); FOR_EACH(g, items) { g->setData(d); } } void GraphContainer::add(const GraphP& graph) { items.push_back(graph); } // ----------------------------------------------------------------------------- : GraphControl GraphControl::GraphControl(Window* parent, int id) : wxControl(parent, id) { setLayout(GRAPH_TYPE_BAR); } void GraphControl::setLayout(GraphType type) { if (type == layout) return; GraphDataP data = graph ? graph->getData() : GraphDataP(); switch (type) { case GRAPH_TYPE_BAR: { intrusive_ptr combined(new GraphContainer()); combined->add(new_intrusive1(0)); combined->add(new_intrusive2(0, HORIZONTAL)); combined->add(new_intrusive1(0)); graph = new_intrusive5(combined, 23,8,7,20); break; } case GRAPH_TYPE_PIE: { intrusive_ptr combined(new GraphContainer()); combined->add(new_intrusive1(0)); graph = new_intrusive5(combined, 20,20,20,20); break; } case GRAPH_TYPE_STACK: { intrusive_ptr combined(new GraphContainer()); combined->add(new_intrusive1(0)); combined->add(new_intrusive2(0, HORIZONTAL)); combined->add(new_intrusive2(0,1)); graph = new_intrusive5(combined, 23,8,7,20); break; } case GRAPH_TYPE_SCATTER: { intrusive_ptr combined(new GraphContainer()); combined->add(new_intrusive4(0, HORIZONTAL, false, DRAW_LINES_MID)); combined->add(new_intrusive4(1, VERTICAL, false, DRAW_LINES_MID)); combined->add(new_intrusive2(0,1)); graph = new_intrusive5(combined, 80,8,7,20); break; } default: graph = GraphP(); } if (data && graph) graph->setData(data); layout = type; } void GraphControl::setData(const GraphDataPre& data) { setData(new_intrusive1(data)); } void GraphControl::setData(const GraphDataP& data) { if (graph) { graph->setData(data); current_item.clear(); // TODO : preserve selection } Refresh(false); } void GraphControl::onPaint(wxPaintEvent&) { wxBufferedPaintDC dc(this); wxSize cs = GetClientSize(); RotatedDC rdc(dc, 0, RealRect(RealPoint(0,0),cs), 1, QUALITY_LOW); rdc.SetPen(*wxTRANSPARENT_PEN); rdc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); rdc.DrawRectangle(rdc.getInternalRect()); if (graph) { for (int layer = LAYER_BOTTOM ; layer < LAYER_COUNT ; ++layer) { graph->draw(rdc, current_item, (DrawLayer)layer); } } } void GraphControl::onSize(wxSizeEvent&) { Refresh(false); } void GraphControl::onMouseDown(wxMouseEvent& ev) { if (!graph) return; wxSize cs = GetClientSize(); if (graph->findItem(RealPoint(ev.GetX(), ev.GetY()), RealRect(RealPoint(0,0),cs), current_item)) { wxCommandEvent ev(EVENT_GRAPH_SELECT, GetId()); ProcessEvent(ev); Refresh(false); } } bool GraphControl::hasSelection(size_t axis) const { return axis < current_item.size() && current_item[axis] >= 0; } String GraphControl::getSelection(size_t axis) const { if (!graph || axis >= current_item.size() || axis >= graph->getData()->axes.size()) return wxEmptyString; GraphAxis& a = *graph->getData()->axes[axis]; int i = current_item[axis]; if (i == -1 || (size_t)i >= a.groups.size()) return wxEmptyString; return a.groups[current_item[axis]].name; } BEGIN_EVENT_TABLE(GraphControl, wxControl) EVT_PAINT (GraphControl::onPaint) EVT_SIZE (GraphControl::onSize) EVT_LEFT_DOWN (GraphControl::onMouseDown) END_EVENT_TABLE ()