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