source: trunk/src/simple_pie_plot.cpp @ 209

Revision 209, 7.9 KB checked in by guyru, 2 years ago (diff)

greatly simplfy the way pie slices are drawn

Line 
1/***************************************************************************
2 *   Copyright (C) 2009 by Guy Rutenberg   *
3 *   guyrutenberg@gmail.com   *
4 *                                                                         *
5 *   This program is free software; you can redistribute it and/or modify  *
6 *   it under the terms of the GNU General Public License as published by  *
7 *   the Free Software Foundation; either version 2 of the License, or     *
8 *   (at your option) any later version.                                   *
9 *                                                                         *
10 *   This program is distributed in the hope that it will be useful,       *
11 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13 *   GNU General Public License for more details.                          *
14 *                                                                         *
15 *   You should have received a copy of the GNU General Public License     *
16 *   along with this program; if not, write to the                         *
17 *   Free Software Foundation, Inc.,                                       *
18 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19 ***************************************************************************/
20
21#include "simple_pie_plot.h"
22#include <boost/foreach.hpp>
23#include <memory>
24#include <algorithm>
25#include <cmath>
26using namespace std;
27using namespace simple_pie_plot;
28
29const double PI = 4.0 * atan(1.0);
30
31/**
32 * Draws a pie slice with origin in (\a x,\a y), radius (\a r) from
33 * from \a start_angle to \a end_angle, where angles are in radians measured
34 * from the x-axis. The figure is drawn using \a dc.
35 */
36void DrawPieSlice(double x, double y, double r, double start_angle,
37                  double end_angle, wxGraphicsContext *dc)
38{
39        wxGraphicsPath path = dc->CreatePath();
40        path.MoveToPoint(x,y);
41        path.AddArc(x, y, r, start_angle, end_angle, true);
42        dc->DrawPath(path);
43}
44
45SimplePiePlot::SimplePiePlot(wxWindow* parent, wxWindowID id,
46                                const wxPoint& pos, const wxSize& size,
47                                long style, const wxString& name)
48                                : wxPanel(parent, id, pos, size, style, name)
49{
50        m_highlight = -1;
51
52        m_max_legend_width = 0;
53        m_max_legend_height = 0;
54        m_legend_width = 0;
55
56        Connect(this->GetId(), wxEVT_PAINT, wxPaintEventHandler(SimplePiePlot::OnPaint));
57        Connect(this->GetId(), wxEVT_SIZE, wxSizeEventHandler(SimplePiePlot::OnResize));
58
59        Connect(this->GetId(), wxEVT_MOTION, wxMouseEventHandler(SimplePiePlot::OnMouseMove));
60        Connect(this->GetId(), wxEVT_LEAVE_WINDOW, wxMouseEventHandler(SimplePiePlot::OnMouseLeaveWindow));
61}
62
63void SimplePiePlot::OnPaint(wxPaintEvent& event)
64{
65        wxPaintDC pdc(this);
66        auto_ptr<wxGraphicsContext> dc(wxGraphicsContext::Create(pdc));
67        wxBrush color_brush;
68
69        if (!m_max_legend_width || !m_max_legend_height) {
70                CalculateLegendDimensions(dc.get());
71                // This might have caused a new minimum size to occure, tell
72                // parent about it by refitting. See also comment in
73                // GetMinSize()
74                if (this->GetParent() && this->GetParent()->GetSizer())
75                        this->GetParent()->GetSizer()->SetSizeHints(this->GetParent());
76        }
77        CalculatePiePlotLocation();
78       
79
80        dc->SetPen(*wxTRANSPARENT_PEN);
81        for (int i = 1; i<m_angles.size(); i++) {
82                // create brush
83                color_brush.SetColour(GetSegmentColor(i-1));
84                dc->SetBrush(color_brush);
85
86                DrawPieSlice(m_pie_x, m_pie_y, m_radius, m_angles[i-1],
87                             m_angles[i], dc.get());
88        }
89
90        DrawLegend(dc.get());
91}
92
93void SimplePiePlot::CalculatePiePlotLocation()
94{
95        double pie_width;
96        int width, height;
97
98        assert(m_legend_width);
99
100        GetClientSize(&width, &height);
101        // m_max_legend_height* for the color box and it's right padding
102        pie_width = width - m_legend_width - m_legend_left_padding;
103        m_pie_x = pie_width/2.0;
104        m_pie_y = height/2.0;
105        m_radius = min(pie_width,(double)height)/2.0;
106}
107
108void SimplePiePlot::OnResize(wxSizeEvent& event)
109{
110        Refresh();
111        event.Skip();
112}
113
114void SimplePiePlot::SetData(vector<double> d, vector<wxString> labels)
115{
116        // number of values should match number of labels
117        assert(labels.size() == d.size());
118
119        m_data.clear();
120        m_data_total = 0;
121        BOOST_FOREACH(double tmp, d) {
122                // we can't plot negative values
123                assert(tmp >= 0);
124                m_data_total += tmp;
125                m_data.push_back(tmp);
126        }
127        m_labels.assign(labels.begin(), labels.end());
128        CalculateAngles();
129
130        // this will cause the legend size to be re-calculated
131        m_max_legend_width = 0;
132        m_max_legend_height = 0;
133}
134
135void SimplePiePlot::CalculateAngles()
136{
137        double new_angle = 0;
138
139        m_angles.clear();
140        m_angles.push_back(new_angle);
141        BOOST_FOREACH(double tmp, m_data) {
142                new_angle = new_angle + 2 * PI * (tmp/m_data_total);
143                m_angles.push_back(new_angle);
144        }
145}
146
147void SimplePiePlot::CalculateLegendDimensions(wxGraphicsContext *dc)
148{
149        double tmp_height;
150        double tmp_width;
151        m_max_legend_width = 0;
152        m_max_legend_height = 0;
153
154        dc->SetFont(*wxNORMAL_FONT, *wxBLACK);
155       
156        BOOST_FOREACH(wxString label, m_labels) {
157                dc->GetTextExtent(label, &tmp_width, &tmp_height, NULL, NULL);
158                m_max_legend_width = max(m_max_legend_width, tmp_width);
159                m_max_legend_height = max(m_max_legend_height, tmp_height);
160        }
161
162        m_legend_width = m_max_legend_width + 2*m_max_legend_height;
163        m_legend_line_height = 1.5 * m_max_legend_height;
164}
165
166void SimplePiePlot::DrawLegend(wxGraphicsContext *dc)
167{
168        wxBrush color_brush;
169        int width, height;
170        GetClientSize(&width, &height);
171
172        assert(m_legend_width);
173        double legend_x = width - m_legend_width;
174
175        dc->SetPen(*wxTRANSPARENT_PEN);
176        dc->SetFont(*wxNORMAL_FONT, *wxBLACK);
177
178        for (int i = 0; i<m_labels.size(); i++) {
179                color_brush.SetColour(GetSegmentColor(i));
180                dc->SetBrush(color_brush);
181
182                dc->DrawRectangle(legend_x, i * m_legend_line_height,
183                        m_max_legend_height, m_max_legend_height);
184                dc->DrawText(m_labels[i], legend_x + 2*m_max_legend_height,
185                        i * m_legend_line_height);
186        }
187
188}
189
190wxColour SimplePiePlot::GetSegmentColor(int i)
191{
192        double hue_step = 1.0/(m_data.size());
193        double hue = i * hue_step;
194        double value = m_highlight == i ? 1.0 : 0.9;
195        wxColour color;
196        wxImage::RGBValue rgb;
197        wxImage::HSVValue hsv(hue, 1, value);
198        rgb = wxImage::HSVtoRGB(hsv);
199        color.Set(rgb.red, rgb.green, rgb.blue);
200
201        return color;
202}
203
204void SimplePiePlot::OnMouseMove(wxMouseEvent& event)
205{
206        //check if the move is even in the plot
207        int width, height;
208        GetClientSize(&width, &height);
209
210        const double dist_mouse = (m_pie_x-event.m_x)*(m_pie_x-event.m_x) +
211                            (m_pie_y-event.m_y)*(m_pie_y-event.m_y);
212        if (dist_mouse <= (m_radius*m_radius)) {
213                //angles are clockwise from the x-axis
214                double angle = acos((event.m_x-m_pie_x)/sqrt(dist_mouse));
215                if (event.m_y < m_pie_y)
216                        angle = 2*PI - angle;
217                int i;
218                for (i = 1; i<m_angles.size(); i++) {
219                        if (angle<=m_angles[i])
220                                break;
221                }
222                Highlight(i-1);
223                return;
224        } else if (event.m_x >= width-m_legend_width){
225                int i = (int) (event.m_y / m_legend_line_height);
226                if (i<m_labels.size()){
227                        Highlight(i);
228                        return;
229                }
230        }
231
232        ClearHighlight();
233}
234
235void SimplePiePlot::OnMouseLeaveWindow(wxMouseEvent& event)
236{
237        ClearHighlight();
238}
239
240void SimplePiePlot::Highlight(int i)
241{
242        m_highlight = i;
243        Refresh();
244}
245
246void SimplePiePlot::ClearHighlight()
247{
248        if (m_highlight != -1) {
249                m_highlight = -1;
250                Refresh();
251        }
252}
253
254wxSize SimplePiePlot::GetMinSize() const
255{
256        // In the first call to GetMinSize, m_legend_line_height and
257        // m_legend_width may not be populated yet (set to zero), this
258        // is a result of not being able to call CalculateLegendDimensions()
259        // at this point due to constness. In order to workaround this issue,
260        // if CalculateLegendDimensions() gets called in OnPaint() (this
261        // happens also after setting new data), OnPaint() also notifies the
262        // parent's sizer (is such exist) to update the size hints
263        // accordingly.
264        const double total_legend_height = m_labels.size() * m_legend_line_height;
265        const double total_width = total_legend_height + m_legend_width +
266                                   m_legend_left_padding;
267        return wxSize((int)ceil(total_width),(int)ceil(total_legend_height));
268}
269
Note: See TracBrowser for help on using the repository browser.