1 /*
2  * go-pie.c
3  *
4  * Copyright (C) 2003-2004 Jody Goldberg (jody@gnome.org)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) version 3.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
19  * USA
20  */
21 
22 #include <goffice/goffice-config.h>
23 #include "gog-pie.h"
24 #include <goffice/graph/gog-view.h>
25 #include <goffice/graph/gog-renderer.h>
26 #include <goffice/graph/gog-theme.h>
27 #include <goffice/data/go-data.h>
28 #include <goffice/math/go-math.h>
29 #include <goffice/utils/go-color.h>
30 #include <goffice/utils/go-persist.h>
31 #include <goffice/utils/go-style.h>
32 #include <goffice/utils/go-styled-object.h>
33 #include <goffice/app/module-plugin-defs.h>
34 
35 #include <glib/gi18n-lib.h>
36 #include <gsf/gsf-impl-utils.h>
37 #include <math.h>
38 #include <string.h>
39 
40 #include "embedded-stuff.c"
41 
42 static GObjectClass *ppe_parent_klass;
43 
44 typedef GogSeriesElementClass GogPieSeriesElementClass;
45 enum {
46 	ELEMENT_PROP_0,
47 	ELEMENT_SEPARATION,
48 };
49 
50 static void
gog_pie_series_element_set_property(GObject * obj,guint param_id,GValue const * value,GParamSpec * pspec)51 gog_pie_series_element_set_property (GObject *obj, guint param_id,
52 				     GValue const *value, GParamSpec *pspec)
53 {
54 	GogPieSeriesElement *pse = GOG_PIE_SERIES_ELEMENT (obj);
55 
56 	switch (param_id) {
57 	case ELEMENT_SEPARATION:
58 		pse->separation = g_value_get_double (value);
59 		break;
60 
61 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
62 		 return; /* NOTE : RETURN */
63 	}
64 
65 	gog_object_emit_changed (GOG_OBJECT (obj), FALSE);
66 }
67 
68 static void
gog_pie_series_element_get_property(GObject * obj,guint param_id,GValue * value,GParamSpec * pspec)69 gog_pie_series_element_get_property (GObject *obj, guint param_id,
70 				     GValue *value, GParamSpec *pspec)
71 {
72 	GogPieSeriesElement *pse = GOG_PIE_SERIES_ELEMENT (obj);
73 
74 	switch (param_id) {
75 	case ELEMENT_SEPARATION:
76 		g_value_set_double (value, pse->separation);
77 		break;
78 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
79 		 break;
80 	}
81 }
82 
83 #ifdef GOFFICE_WITH_GTK
84 extern gpointer gog_pie_series_element_pref (GogPieSeriesElement *element, GOCmdContext *cc);
85 static gpointer
gog_pie_series_element_populate_editor(GogObject * gobj,GOEditor * editor,GOCmdContext * cc)86 gog_pie_series_element_populate_editor (GogObject *gobj,
87 					GOEditor *editor,
88 					GOCmdContext *cc)
89 {
90 	GtkWidget *widget = gog_pie_series_element_pref (GOG_PIE_SERIES_ELEMENT (gobj), cc);
91 	go_editor_add_page (editor, widget, _("Settings"));
92 	g_object_unref (widget);
93 	return widget;
94 }
95 #endif
96 
97 static void
gog_pie_series_element_class_init(GogPieSeriesElementClass * klass)98 gog_pie_series_element_class_init (GogPieSeriesElementClass *klass)
99 {
100 	GObjectClass *gobject_klass = (GObjectClass *) klass;
101 
102 	gobject_klass->set_property = gog_pie_series_element_set_property;
103 	gobject_klass->get_property = gog_pie_series_element_get_property;
104 
105 	ppe_parent_klass 	= g_type_class_peek_parent (klass);
106 #ifdef GOFFICE_WITH_GTK
107 	klass->gse_populate_editor	= gog_pie_series_element_populate_editor;
108 #endif
109 
110 	g_object_class_install_property (gobject_klass, ELEMENT_SEPARATION,
111 		g_param_spec_double ("separation",
112 			_("Separation"),
113 			_("Amount a slice is extended as a percentage of the radius"),
114 			0, 1000, 0.,
115 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
116 }
117 
118 GSF_DYNAMIC_CLASS (GogPieSeriesElement, gog_pie_series_element,
119 	gog_pie_series_element_class_init, NULL /*gog_pie_series_element_init*/,
120 	GOG_TYPE_SERIES_ELEMENT)
121 
122 /*****************************************************************************/
123 
124 typedef enum {
125 	GOG_SHOW_NEGS_SKIP,
126 	GOG_SHOW_NEGS_ABSOLUTE,
127 	GOG_SHOW_NEGS_WHITE,
128 	GOG_SHOW_NEGS_INVERTED, /* not used for now */
129 	GOG_SHOW_NEGS_MAX
130 } GogShowNegsMode;
131 
132 static struct {
133 	GogShowNegsMode mode;
134 	char const *name;
135 } neg_modes [GOG_SHOW_NEGS_MAX] = {
136 	{GOG_SHOW_NEGS_SKIP, "skip"},
137 	{GOG_SHOW_NEGS_ABSOLUTE, "absolute"},
138 	{GOG_SHOW_NEGS_WHITE, "white"},
139 	{GOG_SHOW_NEGS_INVERTED, "inverted"}
140 };
141 
142 static GogShowNegsMode
gog_show_neg_mode_from_str(char const * name)143 gog_show_neg_mode_from_str (char const *name)
144 {
145 	unsigned i;
146 	GogShowNegsMode ret = GOG_SHOW_NEGS_ABSOLUTE;
147 
148 	for (i = 0; i < GO_LINE_MAX; i++) {
149 		if (strcmp (neg_modes[i].name, name) == 0) {
150 			ret = neg_modes[i].mode;
151 			break;
152 		}
153 	}
154 	return ret;
155 }
156 
157 static char const *
gog_show_neg_mode_as_str(GogShowNegsMode mode)158 gog_show_neg_mode_as_str (GogShowNegsMode mode)
159 {
160 	unsigned i;
161 	char const *ret = "absolute";
162 
163 	for (i = 0; i < GOG_SHOW_NEGS_MAX; i++) {
164 		if (neg_modes[i].mode == mode) {
165 			ret = neg_modes[i].name;
166 			break;
167 		}
168 	}
169 	return ret;
170 }
171 
172 typedef struct {
173 	GogPlotClass	base;
174 } GogPiePlotClass;
175 
176 enum {
177 	PLOT_PROP_0,
178 	PLOT_PROP_INITIAL_ANGLE,
179 	PLOT_PROP_DEFAULT_SEPARATION,
180 	PLOT_PROP_IN_3D,
181 	PLOT_PROP_SPAN,
182 	PLOT_PROP_SHOW_NEGS
183 };
184 
185 GOFFICE_PLUGIN_MODULE_HEADER;
186 
187 static GObjectClass *pie_parent_klass;
188 static GType gog_pie_view_get_type (void);
189 
190 static void
gog_pie_plot_set_property(GObject * obj,guint param_id,GValue const * value,GParamSpec * pspec)191 gog_pie_plot_set_property (GObject *obj, guint param_id,
192 			   GValue const *value, GParamSpec *pspec)
193 {
194 	GogPiePlot *pie = GOG_PIE_PLOT (obj);
195 
196 	switch (param_id) {
197 	case PLOT_PROP_INITIAL_ANGLE: {
198 		double a = g_value_get_double (value);
199 		a = fmod (a, 360);
200 		if (a < 0) a += 360;
201 		pie->initial_angle = a;
202 		break;
203 	}
204 	case PLOT_PROP_DEFAULT_SEPARATION: {
205 		double f = g_value_get_double (value);
206 		pie->default_separation = CLAMP (f, 0., 5.);
207 		break;
208 	}
209 	case PLOT_PROP_IN_3D:
210 		pie->in_3d = g_value_get_boolean (value);
211 		break;
212 	case PLOT_PROP_SPAN:
213 		pie->span = g_value_get_double (value);
214 		break;
215 	case PLOT_PROP_SHOW_NEGS : {
216 		GSList *ptr = GOG_PLOT (obj)->series;
217 		pie->show_negatives = gog_show_neg_mode_from_str (g_value_get_string (value));
218 		/* we need to update all the series */
219 		while (ptr) {
220 			gog_object_request_update (GOG_OBJECT (ptr->data));
221 			ptr = ptr->next;
222 		}
223 		break;
224 	}
225 
226 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
227 		 return; /* NOTE : RETURN */
228 	}
229 
230 	/* none of the attributes triggers a size change yet.
231 	 * When we add data labels we'll need it */
232 	gog_object_emit_changed (GOG_OBJECT (obj), FALSE);
233 }
234 
235 static void
gog_pie_plot_get_property(GObject * obj,guint param_id,GValue * value,GParamSpec * pspec)236 gog_pie_plot_get_property (GObject *obj, guint param_id,
237 			  GValue *value, GParamSpec *pspec)
238 {
239 	GogPiePlot *pie = GOG_PIE_PLOT (obj);
240 
241 	switch (param_id) {
242 	case PLOT_PROP_INITIAL_ANGLE:
243 		g_value_set_double (value, pie->initial_angle);
244 		break;
245 	case PLOT_PROP_DEFAULT_SEPARATION:
246 		g_value_set_double (value, pie->default_separation);
247 		break;
248 	case PLOT_PROP_IN_3D:
249 		g_value_set_boolean (value, pie->in_3d);
250 		break;
251 	case PLOT_PROP_SPAN:
252 		g_value_set_double (value, pie->span);
253 		break;
254 	case PLOT_PROP_SHOW_NEGS:
255 		g_value_set_string (value, gog_show_neg_mode_as_str (pie->show_negatives));
256 		break;
257 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
258 		 break;
259 	}
260 }
261 
262 static char const *
gog_pie_plot_type_name(G_GNUC_UNUSED GogObject const * item)263 gog_pie_plot_type_name (G_GNUC_UNUSED GogObject const *item)
264 {
265 	return N_("PlotPie");
266 }
267 
268 #ifdef GOFFICE_WITH_GTK
269 extern gpointer gog_pie_plot_pref (GogPiePlot *pie, GOCmdContext *cc);
270 static void
gog_pie_plot_populate_editor(GogObject * item,GOEditor * editor,G_GNUC_UNUSED GogDataAllocator * dalloc,GOCmdContext * cc)271 gog_pie_plot_populate_editor (GogObject *item,
272 			      GOEditor *editor,
273 			      G_GNUC_UNUSED GogDataAllocator *dalloc,
274 			      GOCmdContext *cc)
275 {
276 	GtkWidget *widget = gog_pie_plot_pref (GOG_PIE_PLOT (item), cc);
277 	go_editor_add_page (editor,
278 			     widget,
279 			     _("Properties"));
280 	g_object_unref (widget);
281 
282 	(GOG_OBJECT_CLASS(pie_parent_klass)->populate_editor) (item, editor, dalloc, cc);
283 }
284 #endif
285 
286 static void
gog_pie_plot_update(GogObject * obj)287 gog_pie_plot_update (GogObject *obj)
288 {
289 	gog_object_emit_changed (GOG_OBJECT (obj), FALSE);
290 }
291 
292 static void
gog_pie_plot_class_init(GogPlotClass * plot_klass)293 gog_pie_plot_class_init (GogPlotClass *plot_klass)
294 {
295 	GObjectClass *gobject_klass = (GObjectClass *) plot_klass;
296 	GogObjectClass *gog_klass = (GogObjectClass *) plot_klass;
297 
298 	pie_parent_klass = g_type_class_peek_parent (plot_klass);
299 	gobject_klass->set_property = gog_pie_plot_set_property;
300 	gobject_klass->get_property = gog_pie_plot_get_property;
301 
302 	gog_klass->update	= gog_pie_plot_update;
303 	gog_klass->type_name	= gog_pie_plot_type_name;
304 #ifdef GOFFICE_WITH_GTK
305 	gog_klass->populate_editor = gog_pie_plot_populate_editor;
306 #endif
307 	gog_klass->view_type	= gog_pie_view_get_type ();
308 
309 	g_object_class_install_property (gobject_klass, PLOT_PROP_INITIAL_ANGLE,
310 		g_param_spec_double ("initial-angle",
311 			_("Initial angle"),
312 			_("Degrees clockwise from 12 O'Clock."),
313 			-G_MAXFLOAT, G_MAXFLOAT, 0.,
314 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
315 	g_object_class_install_property (gobject_klass, PLOT_PROP_DEFAULT_SEPARATION,
316 		g_param_spec_double ("default-separation",
317 			_("Default separation"),
318 			_("Default amount a slice is extended as a percentage of the radius"),
319 			0, G_MAXFLOAT, 0.,
320 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
321 	g_object_class_install_property (gobject_klass, PLOT_PROP_IN_3D,
322 		g_param_spec_boolean ("in-3d",
323 			_("In 3D"),
324 			_("Draw 3DS wedges"),
325 			FALSE,
326 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
327 	g_object_class_install_property (gobject_klass, PLOT_PROP_SPAN,
328 		g_param_spec_double ("span",
329 			_("Span"),
330 			_("Total angle used as a percentage of the full circle"),
331 			10., 100., 100.,
332 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
333 	g_object_class_install_property (gobject_klass, PLOT_PROP_SHOW_NEGS,
334 		g_param_spec_string ("show-negs",
335 			_("Show negative values"),
336 			_("How negative values are displayed"),
337 			"absolute",
338 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
339 
340 	{
341 		static GogSeriesDimDesc dimensions[] = {
342 			{ N_("Labels"), GOG_SERIES_SUGGESTED, TRUE,
343 			  GOG_DIM_LABEL, GOG_MS_DIM_CATEGORIES },
344 			{ N_("Values"), GOG_SERIES_REQUIRED, FALSE,
345 			  GOG_DIM_VALUE, GOG_MS_DIM_VALUES }
346 		};
347 		plot_klass->desc.series.dim = dimensions;
348 		plot_klass->desc.series.num_dim = G_N_ELEMENTS (dimensions);
349 		plot_klass->desc.series.style_fields = GO_STYLE_OUTLINE | GO_STYLE_FILL;
350 	}
351 	plot_klass->desc.num_series_max = 1;
352 	plot_klass->series_type  = gog_pie_series_get_type ();
353 }
354 
355 static void
gog_pie_plot_init(GogPiePlot * pie)356 gog_pie_plot_init (GogPiePlot *pie)
357 {
358 	pie->base.vary_style_by_element = TRUE;
359 	pie->span = 100.;
360 	pie->show_negatives = GOG_SHOW_NEGS_ABSOLUTE;
361 }
362 
GSF_DYNAMIC_CLASS(GogPiePlot,gog_pie_plot,gog_pie_plot_class_init,gog_pie_plot_init,GOG_TYPE_PLOT)363 GSF_DYNAMIC_CLASS (GogPiePlot, gog_pie_plot,
364 	gog_pie_plot_class_init, gog_pie_plot_init,
365 	GOG_TYPE_PLOT)
366 
367 #ifdef GOFFICE_WITH_GTK
368 
369 static void
370 gog_pie_plot_set_default_separation (GogPiePlot *pie, double separation)
371 {
372 	g_return_if_fail (GOG_PIE_PLOT (pie) != NULL);
373 
374 	pie->default_separation = CLAMP (separation, 0.0, 5.0);
375 	gog_object_emit_changed (GOG_OBJECT (pie), FALSE);
376 }
377 
378 #endif
379 
380 /*****************************************************************************/
381 
382 enum {
383 	RING_PLOT_PROP_0,
384 	RING_PLOT_PROP_CENTER_SIZE,
385 };
386 
387 typedef GogPiePlotClass	GogRingPlotClass;
388 static GObjectClass *ring_parent_klass;
389 
390 static void
gog_ring_plot_set_property(GObject * obj,guint param_id,GValue const * value,GParamSpec * pspec)391 gog_ring_plot_set_property (GObject *obj, guint param_id,
392 			    GValue const *value, GParamSpec *pspec)
393 {
394 	GogRingPlot *ring = GOG_RING_PLOT (obj);
395 
396 	switch (param_id) {
397 	case RING_PLOT_PROP_CENTER_SIZE:
398 		ring->center_size = g_value_get_double (value);
399 		break;
400 
401 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
402 		 return; /* NOTE : RETURN */
403 	}
404 
405 	/* none of the attributes triggers a size change yet.
406 	 * When we add data labels we'll need it */
407 	gog_object_emit_changed (GOG_OBJECT (obj), FALSE);
408 }
409 
410 static void
gog_ring_plot_get_property(GObject * obj,guint param_id,GValue * value,GParamSpec * pspec)411 gog_ring_plot_get_property (GObject *obj, guint param_id,
412 			    GValue *value, GParamSpec *pspec)
413 {
414 	GogRingPlot *ring = GOG_RING_PLOT (obj);
415 
416 	switch (param_id) {
417 	case RING_PLOT_PROP_CENTER_SIZE:
418 		g_value_set_double (value, ring->center_size);
419 		break;
420 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
421 		 break;
422 	}
423 }
424 
425 static char const *
gog_ring_plot_type_name(G_GNUC_UNUSED GogObject const * item)426 gog_ring_plot_type_name (G_GNUC_UNUSED GogObject const *item)
427 {
428 	return N_("PlotRing");
429 }
430 
431 #ifdef GOFFICE_WITH_GTK
432 extern gpointer gog_ring_plot_pref (GogRingPlot *ring, GOCmdContext *cc);
433 static void
gog_ring_plot_populate_editor(GogObject * item,GOEditor * editor,G_GNUC_UNUSED GogDataAllocator * dalloc,GOCmdContext * cc)434 gog_ring_plot_populate_editor (GogObject *item,
435 			       GOEditor *editor,
436 		      G_GNUC_UNUSED GogDataAllocator *dalloc,
437 		      GOCmdContext *cc)
438 {
439 	GtkWidget *widget = gog_ring_plot_pref (GOG_RING_PLOT (item), cc);
440 	go_editor_add_page (editor,
441 			     widget,
442 			     _("Properties"));
443 	g_object_unref (widget);
444 }
445 #endif
446 
447 static void
gog_ring_plot_class_init(GogPiePlotClass * pie_plot_klass)448 gog_ring_plot_class_init (GogPiePlotClass *pie_plot_klass)
449 {
450 	GObjectClass *gobject_klass = (GObjectClass *) pie_plot_klass;
451 	GogObjectClass *gog_klass = (GogObjectClass *) pie_plot_klass;
452 	GogPlotClass *plot_klass = (GogPlotClass *) pie_plot_klass;
453 
454 	ring_parent_klass = g_type_class_peek_parent (pie_plot_klass);
455 	gobject_klass->set_property = gog_ring_plot_set_property;
456 	gobject_klass->get_property = gog_ring_plot_get_property;
457 
458 	gog_klass->type_name	= gog_ring_plot_type_name;
459 #ifdef GOFFICE_WITH_GTK
460 	gog_klass->populate_editor = gog_ring_plot_populate_editor;
461 #endif
462 
463 	g_object_class_install_property (gobject_klass, RING_PLOT_PROP_CENTER_SIZE,
464 		g_param_spec_double ("center-size",
465 			_("Center-size"),
466 			_("Size of the center hole as a percentage of the radius"),
467 			0, 100., 0.5,
468 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
469 
470 	plot_klass->desc.num_series_max = G_MAXINT;
471 	plot_klass->desc.series.style_fields = GO_STYLE_OUTLINE | GO_STYLE_FILL;
472 }
473 
474 static void
gog_ring_plot_init(GogRingPlot * ring)475 gog_ring_plot_init (GogRingPlot *ring)
476 {
477 	ring->center_size = 0.5;
478 }
479 
480 GSF_DYNAMIC_CLASS (GogRingPlot, gog_ring_plot,
481 	gog_ring_plot_class_init, gog_ring_plot_init,
482 	GOG_TYPE_PIE_PLOT)
483 
484 /*****************************************************************************/
485 
486 #ifdef GOFFICE_WITH_GTK
487 typedef struct {
488 	double x, y, r;
489 	double start_pos;
490        	double start_distance;
491 } MovePieData;
492 
493 static gboolean
find_element(GogView * view,double cx,double cy,double x,double y,unsigned int * index,GogPieSeries ** series)494 find_element (GogView *view, double cx, double cy, double x, double y,
495 	      unsigned int *index, GogPieSeries **series)
496 {
497 	GogPiePlot *pie = GOG_PIE_PLOT (view->model);
498 	GSList *ptr;
499 	double *vals, scale, len = 0, theta;
500 
501 	*series = NULL;
502 	*index = 0;
503 
504 	for (ptr = pie->base.series ; ptr != NULL ; ptr = ptr->next)
505 		if (gog_series_is_valid (GOG_SERIES (*series = ptr->data)))
506 			break;
507 	if (ptr == NULL)
508 		return FALSE;
509 
510 	theta = (atan2 (y - cy, x - cx)
511 		 * 180 / M_PI - pie->initial_angle + 90.) / pie->span / 3.6;
512 	if (theta < 0)
513 		theta += 1.;
514 
515 	vals = go_data_get_values ((*series)->base.values[1].data);
516 	scale = 1 / (*series)->total;
517 	for (*index = 0 ; *index < (*series)->base.num_elements; (*index)++) {
518 		len = vals[*index] * scale;
519 		if (len < 0.)
520 			len = pie->show_negatives? -len: 0.;
521 		if (go_finite (len) && len > 1e-3) {
522 			theta -= len;
523 			if (theta < 0)
524 				break;
525 		}
526 	}
527 	return TRUE;
528 }
529 
530 static gboolean
gog_tool_move_pie_point(GogView * view,double x,double y,GogObject ** gobj)531 gog_tool_move_pie_point (GogView *view, double x, double y, GogObject **gobj)
532 {
533 	GogPieSeries *series;
534 	double r = view->allocation.h, cx, cy;
535 	unsigned int index;
536 
537 	if (r > view->allocation.w)
538 		r = view->allocation.w;
539 	r /= 2.;
540 	cx = view->allocation.x + view->allocation.w/2.;
541 	cy = view->allocation.y + view->allocation.h/2.;
542 
543 	if (hypot (x - cx, y - cy) > fabs (r))
544 		return FALSE;
545 
546 	if (find_element (view, cx, cy, x, y, &index, &series))
547 		*gobj = GOG_OBJECT (gog_series_get_element (GOG_SERIES (series), index));
548 
549 	return TRUE;
550 }
551 
552 static void
gog_tool_move_pie_render(GogView * view)553 gog_tool_move_pie_render (GogView *view)
554 {
555 	GogViewAllocation rectangle;
556 	double r = view->allocation.h;
557 
558 	if (r > view->allocation.w)
559 		r = view->allocation.w;
560 
561 	rectangle.x = view->allocation.x + (view->allocation.w - r) / 2.0;
562 	rectangle.y = view->allocation.y + (view->allocation.h - r) / 2.0;
563 	rectangle.w = r;
564 	rectangle.h = r;
565 
566 	gog_renderer_draw_selection_rectangle (view->renderer, &rectangle);
567 }
568 
569 static void
gog_tool_move_pie_init(GogToolAction * action)570 gog_tool_move_pie_init (GogToolAction *action)
571 {
572 	GogPiePlot *pie = GOG_PIE_PLOT (action->view->model);
573 	MovePieData *data = g_new0 (MovePieData, 1);
574 	GogViewAllocation area = action->view->allocation;
575 
576 	data->r = area.h;
577 	if (data->r > area.w)
578 		data->r = area.w;
579 	data->r /= 2.;
580 	data->start_pos = (data->r * (0.5 + pie->default_separation)) / (1 + pie->default_separation);
581 	data->x = area.x + area.w / 2.;
582 	data->y = area.y + area.h / 2.;
583 	data->start_distance = hypot (action->start_x - data->x, action->start_y - data->y);
584 
585 	action->data = data;
586 }
587 
588 static void
gog_tool_move_pie_move(GogToolAction * action,double x,double y)589 gog_tool_move_pie_move (GogToolAction *action, double x, double y)
590 {
591 	GogPiePlot *pie = GOG_PIE_PLOT (action->view->model);
592 	MovePieData *data = action->data;
593 	double pos, separation;
594 
595 	pos = data->start_pos -
596 		((x - action->start_x) * (data->x - action->start_x) +
597 		 (y - action->start_y) * (data->y - action->start_y)) /
598 		data->start_distance;
599 	separation = (pos - 0.5 * data->r) / (data->r - pos);
600 
601 	gog_pie_plot_set_default_separation (pie, separation);
602 }
603 
604 static void
gog_tool_move_pie_double_click(GogToolAction * action)605 gog_tool_move_pie_double_click (GogToolAction *action)
606 {
607 	MovePieData *data = action->data;
608 	GogPieSeries *series;
609 	GogObject *obj;
610 	unsigned int index;
611 
612 	if (!find_element (action->view, data->x, data->y,
613 			   action->start_x, action->start_y,
614 			   &index, &series))
615 		return;
616 
617 	obj = GOG_OBJECT (gog_series_get_element (GOG_SERIES (series), index));
618 	if (obj == NULL) {
619 		obj = g_object_new (gog_pie_series_element_get_type (),
620 				    "index", index, NULL);
621 		gog_object_add_by_name (GOG_OBJECT (series), "Point", obj);
622 	}
623 }
624 
625 static GogTool gog_tool_move_pie = {
626 	N_("Move pie"),
627 	GDK_FLEUR,
628 	gog_tool_move_pie_point,
629 	gog_tool_move_pie_render,
630 	gog_tool_move_pie_init,
631 	gog_tool_move_pie_move,
632 	gog_tool_move_pie_double_click,
633 	NULL /*destroy*/
634 };
635 #endif
636 
637 /*****************************************************************************/
638 
639 typedef GogPlotView		GogPieView;
640 typedef GogPlotViewClass	GogPieViewClass;
641 
642 static GogViewClass *pie_view_parent_klass;
643 
644 static int
gog_pie_view_get_data_at_point(GogPlotView * view,double x,double y,GogSeries ** series)645 gog_pie_view_get_data_at_point (GogPlotView *view, double x, double y, GogSeries **series)
646 {
647 	GogPiePlot const *model = GOG_PIE_PLOT (view->base.model);
648 	GogPieSeries const *pseries = NULL;
649 	double r_tot, r_cur, r_int, r_ext, cx, cy, r, th, th0, theta, scale, *vals;
650 	double separated_cx, separated_cy;
651 	unsigned int index, k;
652 	double center_radius;
653 	double center_size = 0.0;
654 	unsigned num_series = 0;
655 	double default_sep, len;
656 	double separation_max = 0., separation;
657 	GSList *ptr;
658 	double outline_width_max = 0.;
659 	gboolean has_hole;
660 	GogPieSeriesElement *gpse;
661 	GList const *overrides;
662 	GogShowNegsMode mode = model->show_negatives;
663 	gboolean negative;
664 	GOStyle *style;
665 
666 	*series = NULL;
667 	/* compute number of valid series */
668 	for (ptr = model->base.series ; ptr != NULL ; ptr = ptr->next) {
669 		if (!gog_series_is_valid (GOG_SERIES (ptr->data)))
670 			continue;
671 		pseries = ptr->data;
672 		if ((style = go_styled_object_get_style (GO_STYLED_OBJECT (pseries))))
673 			outline_width_max = MAX (outline_width_max, gog_renderer_line_size (view->base.renderer, style->line.width));
674 		for (overrides = gog_series_get_overrides (GOG_SERIES (pseries));
675 		     overrides != NULL;
676 		     overrides = overrides->next) {
677 			separation = GOG_PIE_SERIES_ELEMENT (overrides->data)->separation;
678 			if (separation_max < separation)
679 				separation_max = separation;
680 			style = go_styled_object_get_style (GO_STYLED_OBJECT (overrides->data));
681 			if (outline_width_max < style->line.width)
682 				outline_width_max = style->line.width;
683 		}
684 		num_series++;
685 	}
686 	if (separation_max < -model->default_separation)
687 		separation_max = -model->default_separation;
688 
689 	if (num_series <= 0)
690 		return -1;
691 
692 	if (GOG_IS_RING_PLOT (model))
693 		center_size = GOG_RING_PLOT(model)->center_size;
694 	else if (num_series > 1)
695 		num_series = 1;
696 	/* calculate things when span < 100. */
697 	if (model->span < 100.) {
698 		double xmin, xmax, ymin, ymax, ratio;
699 		double begin, cur, end;
700 		if (num_series == 1) {
701 			pseries = model->base.series->data;
702 			begin = (model->initial_angle + pseries->initial_angle) * M_PI / 180. - M_PI / 2.;
703 			end = begin + model->span * M_PI / 50.;
704 		} else {
705 			/* WARNING: this code has not been checked */
706 			ptr = model->base.series;
707 			while (!gog_series_is_valid (GOG_SERIES (ptr->data)))
708 				ptr = ptr->next;
709 			pseries = ptr->data;
710 			begin = end = pseries->initial_angle;
711 			for (ptr = ptr->next; ptr != NULL; ptr = ptr->next) {
712 				if (!gog_series_is_valid (GOG_SERIES (ptr->data)))
713 					continue;
714 				pseries = ptr->data;
715 				cur = pseries->initial_angle;
716 				if (cur < begin)
717 					begin = cur;
718 				if (cur > end)
719 					end = cur;
720 			}
721 			begin = (model->initial_angle + begin) * M_PI / 180. - M_PI / 2.;
722 			end = ((model->initial_angle + end) / 180. + model->span / 50.) * M_PI - M_PI / 2.;
723 		}
724 		cur = ceil (begin / M_PI * 2 - 1e-10) * M_PI / 2;
725 		xmin = xmax = cos (begin);
726 		ymin = ymax = sin (begin);
727 		while (cur < end) {
728 			cx = cos (cur);
729 			cy = sin (cur);
730 			if (cx > xmax)
731 				xmax = cx;
732 			if (cx < xmin)
733 				xmin = cx;
734 			if (cy > ymax)
735 				ymax = cy;
736 			if (cy < ymin)
737 				ymin = cy;
738 			cur += M_PI / 2;
739 		}
740 		cx = cos (end);
741 		cy = sin (end);
742 		if (cx > xmax)
743 			xmax = cx;
744 		if (cx < xmin)
745 			xmin = cx;
746 		if (cy > ymax)
747 			ymax = cy;
748 		if (cy < ymin)
749 			ymin = cy;
750 		/* we ensure that the center will be visible */
751 		if (xmin > 0.)
752 			xmin = 0.;
753 		if (xmax < 0.)
754 			xmax = 0.;
755 		if (ymin > 0.)
756 			ymin = 0.;
757 		if (ymax < 0.)
758 			ymax = 0.;
759 		ratio = (ymax - ymin) / (xmax - xmin);
760 		if (view->base.allocation.h > view->base.allocation.w * ratio) {
761 			r_tot = view->base.allocation.w * MAX (xmax, -xmin) / (xmax - xmin);
762 			cx = view->base.allocation.x - view->base.allocation.w * xmin / (xmax - xmin);
763 			cy = view->base.allocation.y +
764 				(view->base.allocation.h + view->base.allocation.w * ratio) / 2.
765 				- view->base.allocation.w * ratio * ymax / (ymax - ymin);
766 		} else {
767 			r_tot = view->base.allocation.h * MAX (ymax, -ymin) / (ymax - ymin);
768 			cx = view->base.allocation.x +
769 				(view->base.allocation.w - view->base.allocation.h / ratio) / 2.
770 				- view->base.allocation.h / ratio * xmin / (xmax - xmin);
771 			cy = view->base.allocation.y - view->base.allocation.h * ymin / (ymax - ymin);
772 		}
773 		r_tot /= 1. + model->default_separation + separation_max;
774 	} else {
775 		/* centre things */
776 		cx = view->base.allocation.x + view->base.allocation.w/2.;
777 		cy = view->base.allocation.y + view->base.allocation.h/2.;
778 
779 		r_tot = view->base.allocation.h;
780 		if (r_tot > view->base.allocation.w)
781 			r_tot = view->base.allocation.w;
782 		r_tot /= 2. * (1. + model->default_separation + separation_max);
783 	}
784 
785 	r = hypot (x - cx, y - cy);
786 
787 	default_sep = r_tot * model->default_separation;
788 	center_radius = r_tot * center_size;
789 	r_cur = r_tot * (1. - center_size);
790 
791 	if (r < center_radius)
792 		return -1;
793 
794 	index = 1;
795 	th0 = (model->initial_angle + pseries->initial_angle) * M_PI / 180. - M_PI / 2.;
796 	th = atan2 (y - cy, x - cx);
797 	if (th < th0)
798 		th += 2 * M_PI;
799 
800 	for (ptr = model->base.series ; ptr != NULL ; ptr = ptr->next) {
801 		pseries = ptr->data;
802 
803 		if (!gog_series_is_valid (GOG_SERIES (pseries)))
804 			continue;
805 		if (index > num_series) /* people snuck extra series into a pie */
806 			break;
807 
808 		if (num_series == index)
809 			r_cur -= outline_width_max / 2.0;
810 		has_hole = center_radius > 0. || index > 1;
811 		r_int = (has_hole)? (center_radius + r_cur * ((double) index - 1.0) / (double) num_series): 0.;
812 		r_ext = center_radius + r_cur * (double) index / (double) num_series;
813 
814 		theta = th0;
815 
816 		scale = 2 * M_PI / 100 * model->span / pseries->total;
817 		vals = go_data_get_values (pseries->base.values[1].data);
818 
819 		overrides = gog_series_get_overrides (GOG_SERIES (pseries));
820 
821 		for (k = 0 ; k < pseries->base.num_elements; k++) {
822 			len = vals[k] * scale;
823 			negative = len < 0;
824 			if (negative) {
825 				if (mode == GOG_SHOW_NEGS_SKIP) {
826 					if ((overrides != NULL) &&
827 					    (GOG_SERIES_ELEMENT (overrides->data)->index == k))
828 						overrides = overrides->next;
829 					continue;
830 				}
831 				len = -len;
832 			}
833 			if (!go_finite (len) || len < 1e-3) {
834 				if ((overrides != NULL) &&
835 				    (GOG_SERIES_ELEMENT (overrides->data)->index == k))
836 					overrides = overrides->next;
837 				continue;
838 			}
839 
840 			gpse = NULL;
841 			if ((overrides != NULL) &&
842 			    (GOG_SERIES_ELEMENT (overrides->data)->index == k)) {
843 				gpse = GOG_PIE_SERIES_ELEMENT (overrides->data);
844 				overrides = overrides->next;
845 			    }
846 
847 			/* only separate the outer ring */
848 			separated_cx = cx;
849 			separated_cy = cy;
850 			if (num_series == index && (default_sep > 0. || gpse != NULL)) {
851 
852 				separation = default_sep;
853 
854 				if (gpse != NULL)
855 					separation += gpse->separation * r_tot;
856 
857 				separated_cx += separation * cos (theta + len/2.);
858 				separated_cy += separation * sin (theta + len/2.);
859 				r = hypot (x - separated_cx, y - separated_cy);
860 				th = atan2 (y - separated_cy, x - separated_cx);
861 				if (th < th0)
862 					th += 2 * M_PI;
863 			}
864 			theta += len;
865 			if (r > r_int && r <= r_ext && th > theta - len && th <= theta) {
866 				*series = GOG_SERIES (pseries);
867 				return k;
868 			}
869 			if (gpse) {
870 				r = hypot (x - cx, y - cy);
871 				th = atan2 (y - cy, x - cx);
872 				if (th < th0)
873 					th += 2 * M_PI;
874 			}
875 		}
876 		index ++;
877 	}
878 	return -1;
879 }
880 
881 #define MAX_ARC_SEGMENTS 64
882 
883 static void
gog_pie_view_render(GogView * view,GogViewAllocation const * bbox)884 gog_pie_view_render (GogView *view, GogViewAllocation const *bbox)
885 {
886 	GogPiePlot const *model = GOG_PIE_PLOT (view->model);
887 	GogPieSeries const *series = NULL;
888 	double separated_cx, separated_cy, cx, cy, r, theta, len, *vals, scale;
889 	double default_sep;
890 	unsigned k;
891 	GOPath *path;
892 	GogTheme *theme = gog_object_get_theme (GOG_OBJECT (model));
893 	GOStyle *style, *white_style = NULL, *elt_style = NULL;
894 	GSList *ptr;
895 	unsigned num_series = 0;
896 	unsigned index;
897 	double center_radius;
898 	double center_size = 0.0;
899 	double r_ext, r_int, r_tot;
900 	double outline_width_max;
901 	gboolean has_hole;
902 	double separation_max, separation;
903 	GogPieSeriesElement *gpse;
904 	GList const *overrides;
905 	GogShowNegsMode mode = model->show_negatives;
906 	gboolean negative;
907 
908 	/* compute number of valid series */
909 	for (ptr = model->base.series ; ptr != NULL ; ptr = ptr->next) {
910 		series = ptr->data;
911 		if (!gog_series_is_valid (GOG_SERIES (ptr->data)))
912 			continue;
913 		num_series++;
914 	}
915 
916 	if (num_series <= 0)
917 		return;
918 
919 	separation_max = .0;
920 	outline_width_max = .0;
921 	if ((style = go_styled_object_get_style (GO_STYLED_OBJECT (series))))
922 		outline_width_max = gog_renderer_line_size (view->renderer, style->line.width);
923 	if (mode == GOG_SHOW_NEGS_WHITE) {
924 		white_style = go_style_dup (style);
925 		white_style->fill.type = GO_STYLE_FILL_PATTERN;
926 		go_pattern_set_solid (&white_style->fill.pattern, GO_COLOR_WHITE);
927 	}
928 	for (overrides = gog_series_get_overrides (GOG_SERIES (series));
929 	     overrides != NULL;
930 	     overrides = overrides->next) {
931 		separation = GOG_PIE_SERIES_ELEMENT (overrides->data)->separation;
932 		if (separation_max < separation)
933 			separation_max = separation;
934 		style = go_styled_object_get_style (GO_STYLED_OBJECT (overrides->data));
935 		if (outline_width_max < style->line.width)
936 			outline_width_max = style->line.width;
937 	}
938 	if (separation_max < -model->default_separation)
939 		separation_max = -model->default_separation;
940 
941 	if (GOG_IS_RING_PLOT (model))
942 		center_size = GOG_RING_PLOT(model)->center_size;
943 	else if (num_series > 1)
944 		num_series = 1;
945 
946 	/* calculate things when span < 100. */
947 	if (model->span < 100.) {
948 		double xmin, xmax, ymin, ymax, ratio;
949 		double begin, cur, end;
950 		if (num_series == 1) {
951 			begin = (model->initial_angle + series->initial_angle) * M_PI / 180. - M_PI / 2.;
952 			end = begin + model->span * M_PI / 50.;
953 		} else {
954 			/* WARNING: this code has not been checked */
955 			ptr = model->base.series;
956 			while (!gog_series_is_valid (GOG_SERIES (ptr->data)))
957 				ptr = ptr->next;
958 			series = ptr->data;
959 			begin = end = series->initial_angle;
960 			for (ptr = ptr->next; ptr != NULL; ptr = ptr->next) {
961 				if (!gog_series_is_valid (GOG_SERIES (ptr->data)))
962 					continue;
963 				series = ptr->data;
964 				cur = series->initial_angle;
965 				if (cur < begin)
966 					begin = cur;
967 				if (cur > end)
968 					end = cur;
969 			}
970 			begin = (model->initial_angle + begin) * M_PI / 180. - M_PI / 2.;
971 			end = ((model->initial_angle + end) / 180. + model->span / 50.) * M_PI - M_PI / 2.;
972 		}
973 		cur = ceil (begin / M_PI * 2 - 1e-10) * M_PI / 2;
974 		xmin = xmax = cos (begin);
975 		ymin = ymax = sin (begin);
976 		while (cur < end) {
977 			cx = cos (cur);
978 			cy = sin (cur);
979 			if (cx > xmax)
980 				xmax = cx;
981 			if (cx < xmin)
982 				xmin = cx;
983 			if (cy > ymax)
984 				ymax = cy;
985 			if (cy < ymin)
986 				ymin = cy;
987 			cur += M_PI / 2;
988 		}
989 		cx = cos (end);
990 		cy = sin (end);
991 		if (cx > xmax)
992 			xmax = cx;
993 		if (cx < xmin)
994 			xmin = cx;
995 		if (cy > ymax)
996 			ymax = cy;
997 		if (cy < ymin)
998 			ymin = cy;
999 		/* we ensure that the center will be visible */
1000 		if (xmin > 0.)
1001 			xmin = 0.;
1002 		if (xmax < 0.)
1003 			xmax = 0.;
1004 		if (ymin > 0.)
1005 			ymin = 0.;
1006 		if (ymax < 0.)
1007 			ymax = 0.;
1008 		ratio = (ymax - ymin) / (xmax - xmin);
1009 		if (view->allocation.h > view->allocation.w * ratio) {
1010 			r_tot = view->allocation.w * MAX (xmax, -xmin) / (xmax - xmin);
1011 			cx = view->allocation.x - view->allocation.w * xmin / (xmax - xmin);
1012 			cy = view->allocation.y +
1013 				(view->allocation.h + view->allocation.w * ratio) / 2.
1014 				- view->allocation.w * ratio * ymax / (ymax - ymin);
1015 		} else {
1016 			r_tot = view->allocation.h * MAX (ymax, -ymin) / (ymax - ymin);
1017 			cx = view->allocation.x +
1018 				(view->allocation.w - view->allocation.h / ratio) / 2.
1019 				- view->allocation.h / ratio * xmin / (xmax - xmin);
1020 			cy = view->allocation.y - view->allocation.h * ymin / (ymax - ymin);
1021 		}
1022 		r_tot /= 1. + model->default_separation + separation_max;
1023 	} else {
1024 		/* centre things */
1025 		cx = view->allocation.x + view->allocation.w/2.;
1026 		cy = view->allocation.y + view->allocation.h/2.;
1027 
1028 		r_tot = view->allocation.h;
1029 		if (r_tot > view->allocation.w)
1030 			r_tot = view->allocation.w;
1031 		r_tot /= 2. * (1. + model->default_separation + separation_max);
1032 	}
1033 
1034 	path = go_path_new ();
1035 
1036 	default_sep = r_tot * model->default_separation;
1037 	center_radius = r_tot * center_size;
1038 	r = r_tot * (1. - center_size);
1039 
1040 	index = 1;
1041 	for (ptr = model->base.series ; ptr != NULL ; ptr = ptr->next) {
1042 		series = ptr->data;
1043 
1044 		if (!gog_series_is_valid (GOG_SERIES (series)))
1045 			continue;
1046 		if (index > num_series) /* people snuck extra series into a pie */
1047 			break;
1048 
1049 		if (num_series == index)
1050 			r -= outline_width_max / 2.0;
1051 
1052 		has_hole = center_radius > 0. || index > 1;
1053 		r_int = center_radius + r * ((double)index - 1.0) / (double)num_series;
1054 		r_ext = center_radius + r * (double)index / (double)num_series;
1055 
1056 		theta = (model->initial_angle + series->initial_angle) * M_PI / 180. - M_PI / 2.;
1057 
1058 		scale = 2 * M_PI / 100 * model->span / series->total;
1059 		vals = go_data_get_values (series->base.values[1].data);
1060 
1061 		style = GOG_STYLED_OBJECT (series)->style;
1062 		if (model->base.vary_style_by_element || mode == GOG_SHOW_NEGS_WHITE)
1063 			style = go_style_dup (style);
1064 		gog_renderer_push_style (view->renderer, style);
1065 
1066 		overrides = gog_series_get_overrides (GOG_SERIES (series));
1067 		for (k = 0 ; k < series->base.num_elements; k++) {
1068 			len = vals[k] * scale;
1069 			negative = len < 0;
1070 			if (negative) {
1071 				if (mode == GOG_SHOW_NEGS_SKIP) {
1072 					if ((overrides != NULL) &&
1073 					    (GOG_SERIES_ELEMENT (overrides->data)->index == k))
1074 						overrides = overrides->next;
1075 					continue;
1076 				}
1077 				len = -len;
1078 			}
1079 			if (!go_finite (len) || len < 1e-3) {
1080 				if ((overrides != NULL) &&
1081 				    (GOG_SERIES_ELEMENT (overrides->data)->index == k))
1082 					overrides = overrides->next;
1083 				continue;
1084 			}
1085 
1086 			gpse = NULL;
1087 			if ((overrides != NULL) &&
1088 			    (GOG_SERIES_ELEMENT (overrides->data)->index == k)) {
1089 				gpse = GOG_PIE_SERIES_ELEMENT (overrides->data);
1090 				overrides = overrides->next;
1091 				if (negative && mode == GOG_SHOW_NEGS_WHITE) {
1092 					elt_style = go_style_dup (go_styled_object_get_style (
1093 							GO_STYLED_OBJECT (gpse)));
1094 					elt_style->fill.type = GO_STYLE_FILL_PATTERN;
1095 					go_pattern_set_solid (&elt_style->fill.pattern, GO_COLOR_WHITE);
1096 					gog_renderer_push_style (view->renderer, elt_style);
1097 				} else
1098 					gog_renderer_push_style (view->renderer,
1099 						go_styled_object_get_style (
1100 							GO_STYLED_OBJECT (gpse)));
1101 			} else {
1102 				if (negative && mode == GOG_SHOW_NEGS_WHITE)
1103 					gog_renderer_push_style (view->renderer, white_style);
1104 				else if (model->base.vary_style_by_element)
1105 					gog_theme_fillin_style (theme, style, GOG_OBJECT (series),
1106 								model->base.index_num + k, GO_STYLE_FILL);
1107 			}
1108 
1109 			/* only separate the outer ring */
1110 			separated_cx = cx;
1111 			separated_cy = cy;
1112 			if (num_series == index && (default_sep > 0. || gpse != NULL)) {
1113 
1114 				separation = default_sep;
1115 
1116 				if (gpse != NULL)
1117 					separation += gpse->separation * r_tot;
1118 
1119 				separated_cx += separation * cos (theta + len/2.);
1120 				separated_cy += separation * sin (theta + len/2.);
1121 			}
1122 			theta += len;
1123 
1124 			go_path_ring_wedge (path, separated_cx, separated_cy,
1125 					    r_ext, r_ext,
1126 					    has_hole ? r_int : 0.0,
1127 					    has_hole ? r_int : 0.0,
1128 					    theta - len,
1129 					    theta);
1130 			gog_renderer_draw_shape (view->renderer, path);
1131 			go_path_clear (path);
1132 
1133 			if (gpse != NULL || (negative && mode == GOG_SHOW_NEGS_WHITE)) {
1134 				gog_renderer_pop_style (view->renderer);
1135 				if (elt_style) {
1136 					g_object_unref (elt_style);
1137 					elt_style = NULL;
1138 				}
1139 			}
1140 		}
1141 
1142 		gog_renderer_pop_style (view->renderer);
1143 		if (model->base.vary_style_by_element || mode == GOG_SHOW_NEGS_WHITE)
1144 			g_object_unref (style);
1145 		if (white_style)
1146 			g_object_unref (white_style);
1147 
1148 		index ++;
1149 	}
1150 
1151 	go_path_free (path);
1152 }
1153 
1154 static void
gog_pie_view_build_toolkit(GogView * view)1155 gog_pie_view_build_toolkit (GogView *view)
1156 {
1157 	(GOG_VIEW_CLASS (pie_view_parent_klass)->build_toolkit) (view);
1158 #ifdef GOFFICE_WITH_GTK
1159 	view->toolkit = g_slist_prepend (view->toolkit, &gog_tool_move_pie);
1160 #endif
1161 }
1162 
1163 static char*
gog_pie_view_get_tip_at_point(GogView * view,double x,double y)1164 gog_pie_view_get_tip_at_point (GogView *view, double x, double y)
1165 {
1166 	GogPieSeries *series = NULL;
1167 	int index = gog_pie_view_get_data_at_point (GOG_PLOT_VIEW (view), x, y, (GogSeries** )&series);
1168 	char *label, *ret;
1169 	double *vals, value;
1170 
1171 	if (index < 0)
1172 		return NULL;
1173 	vals = go_data_get_values (series->base.values[1].data);
1174 	value = fabs (vals[index]);
1175 	label = series->base.values[0].data? go_data_get_vector_string (series->base.values[0].data, index): NULL;
1176 	if (label && *label)
1177 		ret = g_strdup_printf (_("%s: %g (%.2f%%)"), label, value, value * 100 / series->total);
1178 	else
1179 		ret = g_strdup_printf (_("%g (%.2f%%)"), value, value * 100 / series->total);
1180 	g_free (label);
1181 	return ret;
1182 }
1183 
1184 static void
gog_pie_view_class_init(GogViewClass * view_klass)1185 gog_pie_view_class_init (GogViewClass *view_klass)
1186 {
1187 	GogPlotViewClass *pv_klass = (GogPlotViewClass *) view_klass;
1188 	pie_view_parent_klass = g_type_class_peek_parent (view_klass);
1189 
1190 	view_klass->render 		= gog_pie_view_render;
1191 	view_klass->build_toolkit 	= gog_pie_view_build_toolkit;
1192 	view_klass->get_tip_at_point    = gog_pie_view_get_tip_at_point;
1193 	pv_klass->get_data_at_point     = gog_pie_view_get_data_at_point;
1194 }
1195 
1196 GSF_DYNAMIC_CLASS (GogPieView, gog_pie_view,
1197 	gog_pie_view_class_init, NULL,
1198 	GOG_TYPE_PLOT_VIEW)
1199 
1200 /*****************************************************************************/
1201 
1202 typedef GogSeriesClass GogPieSeriesClass;
1203 enum {
1204 	SERIES_PROP_0,
1205 	SERIES_PROP_INITIAL_ANGLE,
1206 	SERIES_PROP_SEPARATION,
1207 };
1208 
1209 static GogObjectClass *series_parent_klass;
1210 static void
gog_pie_series_set_property(GObject * obj,guint param_id,GValue const * value,GParamSpec * pspec)1211 gog_pie_series_set_property (GObject *obj, guint param_id,
1212 			     GValue const *value, GParamSpec *pspec)
1213 {
1214 	GogPieSeries *pie = GOG_PIE_SERIES (obj);
1215 
1216 	switch (param_id) {
1217 	case SERIES_PROP_INITIAL_ANGLE:
1218 		pie->initial_angle = g_value_get_double (value);
1219 		break;
1220 	case SERIES_PROP_SEPARATION:
1221 		pie->separation = g_value_get_double (value);
1222 		break;
1223 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
1224 		 return; /* NOTE : RETURN */
1225 	}
1226 	/* none of the attributes triggers a size change yet.
1227 	 * When we add data labels we'll need it */
1228 	gog_object_emit_changed (GOG_OBJECT (obj), FALSE);
1229 }
1230 
1231 static void
gog_pie_series_get_property(GObject * obj,guint param_id,GValue * value,GParamSpec * pspec)1232 gog_pie_series_get_property (GObject *obj, guint param_id,
1233 			  GValue *value, GParamSpec *pspec)
1234 {
1235 	GogPieSeries *pie = GOG_PIE_SERIES (obj);
1236 
1237 	switch (param_id) {
1238 	case SERIES_PROP_INITIAL_ANGLE:
1239 		g_value_set_double (value, pie->initial_angle);
1240 		break;
1241 	case SERIES_PROP_SEPARATION:
1242 		g_value_set_double (value, pie->separation);
1243 		break;
1244 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
1245 		 break;
1246 	}
1247 }
1248 
1249 static void
gog_pie_series_update(GogObject * obj)1250 gog_pie_series_update (GogObject *obj)
1251 {
1252 	double *vals = NULL, total;
1253 	int len = 0;
1254 	GogPieSeries *series = GOG_PIE_SERIES (obj);
1255 	unsigned old_num = series->base.num_elements;
1256 	GogShowNegsMode mode = GOG_PIE_PLOT (series->base.plot)->show_negatives;
1257 
1258 	if (series->base.values[1].data != NULL) {
1259 		vals = go_data_get_values (series->base.values[1].data);
1260 		len = go_data_get_vector_size (series->base.values[1].data);
1261 	}
1262 	series->base.num_elements = len;
1263 
1264 	for (total = 0. ; len-- > 0 ;) {
1265 		double val = vals[len];
1266 		if (go_finite (val)) {
1267 			if (val < 0)
1268 				val = (mode == GOG_SHOW_NEGS_SKIP)? 0.: -val;
1269 			total += val;
1270 		}
1271 	}
1272 	series->total = total;
1273 
1274 	/* queue plot for redraw */
1275 	gog_object_request_update (GOG_OBJECT (series->base.plot));
1276 	if (old_num != series->base.num_elements)
1277 		gog_plot_request_cardinality_update (series->base.plot);
1278 
1279 	if (series_parent_klass->update)
1280 		series_parent_klass->update (obj);
1281 }
1282 
1283 static void
gog_pie_series_class_init(GObjectClass * gobject_klass)1284 gog_pie_series_class_init (GObjectClass *gobject_klass)
1285 {
1286 	GogObjectClass *gog_klass = (GogObjectClass *)gobject_klass;
1287 	GogSeriesClass *series_klass = (GogSeriesClass *)gobject_klass;
1288 
1289 	series_parent_klass = g_type_class_peek_parent (gobject_klass);
1290 	gog_klass->update = gog_pie_series_update;
1291 	series_klass->series_element_type = GOG_TYPE_PIE_SERIES_ELEMENT;
1292 
1293 	gobject_klass->set_property = gog_pie_series_set_property;
1294 	gobject_klass->get_property = gog_pie_series_get_property;
1295 
1296 	g_object_class_install_property (gobject_klass, SERIES_PROP_INITIAL_ANGLE,
1297 		g_param_spec_double ("initial-angle",
1298 			_("Initial-angle"),
1299 			_("Degrees clockwise from 12 O'Clock"),
1300 			0, G_MAXFLOAT, 0.,
1301 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
1302 	g_object_class_install_property (gobject_klass, SERIES_PROP_SEPARATION,
1303 		g_param_spec_double ("separation",
1304 			_("Separation"),
1305 			_("Default amount a slice is extended as a percentage of the radius"),
1306 			0, G_MAXFLOAT, 0.,
1307 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
1308 }
1309 
GSF_DYNAMIC_CLASS(GogPieSeries,gog_pie_series,gog_pie_series_class_init,NULL,GOG_TYPE_SERIES)1310 GSF_DYNAMIC_CLASS (GogPieSeries, gog_pie_series,
1311 	   gog_pie_series_class_init, NULL,
1312 	   GOG_TYPE_SERIES)
1313 
1314 G_MODULE_EXPORT void
1315 go_plugin_init (GOPlugin *plugin, GOCmdContext *cc)
1316 {
1317 	GTypeModule *module = go_plugin_get_type_module (plugin);
1318 	gog_pie_series_element_register_type (module);
1319 	gog_pie_plot_register_type (module);
1320 	gog_pie_view_register_type (module);
1321 	gog_pie_series_register_type (module);
1322 	gog_ring_plot_register_type (module);
1323 
1324 	register_embedded_stuff ();
1325 }
1326 
1327 G_MODULE_EXPORT void
go_plugin_shutdown(GOPlugin * plugin,GOCmdContext * cc)1328 go_plugin_shutdown (GOPlugin *plugin, GOCmdContext *cc)
1329 {
1330 	unregister_embedded_stuff ();
1331 }
1332