1 /*
2  * gog-plot.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 <goffice/graph/gog-plot-impl.h>
24 #include <goffice/graph/gog-plot-engine.h>
25 #include <goffice/graph/gog-series-impl.h>
26 #include <goffice/graph/gog-chart.h>
27 #include <goffice/graph/gog-axis.h>
28 #include <goffice/graph/gog-theme.h>
29 #include <goffice/graph/gog-graph.h>
30 #include <goffice/graph/gog-object-xml.h>
31 #include <goffice/graph/gog-renderer.h>
32 #include <goffice/data/go-data.h>
33 #include <goffice/math/go-math.h>
34 #include <goffice/utils/go-persist.h>
35 #include <goffice/utils/go-style.h>
36 #include <goffice/utils/go-styled-object.h>
37 #include <glib/gi18n-lib.h>
38 
39 #ifdef GOFFICE_WITH_GTK
40 #include <gtk/gtk.h>
41 #endif
42 
43 #include <gsf/gsf-impl-utils.h>
44 #include <string.h>
45 
46 #define GOG_PLOT_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), GOG_TYPE_PLOT, GogPlotClass))
47 
48 /**
49  * SECTION: gog-plot
50  * @short_description: A plot.
51  * @See_also: #GogChart, #GogSeries
52  *
53  * This is the object that visualizes data.
54  * To manipulate the data shown in the plot, use gog_plot_new_series() and
55  * gog_plot_clear_series()
56  *
57  * Plots are implemented as plug-ins, so make sure the plug-in system is
58  * initialized before trying to create one. See go_plugins_init()
59  *
60  * GOffice ships a number of common plot implementations by default.
61  */
62 
63 /**
64  * GogPlotClass:
65  * @base: base class
66  * @desc: #GogPlotDesc
67  * @series_type: series type.
68  * @axis_set: set of use axes.
69  * @axis_get_bounds: requests the axis bounds
70  * @supports_vary_style_by_element: %TRUE if each element has its own style.
71  * @enum_in_reverse: %TRUE if the plot prefers to display series in reverse
72  * order for legends (e.g. stacked plots want top element to be the
73  * last series.
74  * @foreach_elem: enumerates the elements visible in the legend.
75  * @update_3d: updates 3D (and contour) plots.
76  * @guru_helper: customizes a new plot in the graph editor dialog.
77  * @get_percent: get the value as a percentage.
78  **/
79 
80 /**
81  * GogPlotViewClass:
82  * @base: base class
83  * @get_data_at_point: returns the data index at the given position, -1 if none.
84  **/
85 
86 /**
87  * GogPlotBoundInfo:
88  * @is_discrete: whether the data are discrete.
89  * @center_on_ticks: whether to center data on ticks.
90  * @fmt: #GOFormat to use.
91  * @date_conv: the used #GODateConventions.
92  *
93  * Used by plots to give formating informations to each axis.
94  * GogPlotBoundInfo::val are the values limits, GogPlotBoundInfo::logical are
95  * the actual needed display limits.
96  **/
97 
98 /**
99  * GogPlotDesc:
100  * @series: series description.
101  **/
102 
103 /**
104  * GogPlotRenderingOrder:
105  * @GOG_PLOT_RENDERING_LAST: render after axis and grid lines.
106  * @GOG_PLOT_RENDERING_BEFORE_AXIS: render before axis but after grid lines.
107  * @GOG_PLOT_RENDERING_BEFORE_GRID: render before grid lines.
108  **/
109 
110 enum {
111 	PLOT_PROP_0,
112 	PLOT_PROP_VARY_STYLE_BY_ELEMENT,
113 	PLOT_PROP_AXIS_X,
114 	PLOT_PROP_AXIS_Y,
115 	PLOT_PROP_AXIS_Z,
116 	PLOT_PROP_AXIS_CIRCULAR,
117 	PLOT_PROP_AXIS_RADIAL,
118 	PLOT_PROP_AXIS_PSEUDO_3D,
119 	PLOT_PROP_AXIS_COLOR,
120 	PLOT_PROP_AXIS_BUBBLE,
121 	PLOT_PROP_GROUP,
122 	PLOT_PROP_DEFAULT_INTERPOLATION,
123 	PLOT_PROP_GURU_HINTS
124 };
125 
126 static GObjectClass *plot_parent_klass;
127 
128 static gboolean gog_plot_set_axis_by_id (GogPlot *plot, GogAxisType type, unsigned id);
129 static unsigned gog_plot_get_axis_id (GogPlot const *plot, GogAxisType type);
130 
131 static void
gog_plot_finalize(GObject * obj)132 gog_plot_finalize (GObject *obj)
133 {
134 	GogPlot *plot = GOG_PLOT (obj);
135 
136 	g_slist_free (plot->series); /* GogObject does the unref */
137 
138 	gog_plot_axis_clear (plot, GOG_AXIS_SET_ALL); /* just in case */
139 	g_free (plot->plot_group);
140 	g_free (plot->guru_hints);
141 
142 	(*plot_parent_klass->finalize) (obj);
143 }
144 
145 static gboolean
role_series_can_add(GogObject const * parent)146 role_series_can_add (GogObject const *parent)
147 {
148 	GogPlot *plot = GOG_PLOT (parent);
149 	return g_slist_length (plot->series) < plot->desc.num_series_max;
150 }
151 
152 static GogObject *
role_series_allocate(GogObject * plot)153 role_series_allocate (GogObject *plot)
154 {
155 	GogPlotClass *klass = GOG_PLOT_GET_CLASS (plot);
156 	GType type = klass->series_type;
157 
158 	if (type == 0)
159 		type = GOG_TYPE_SERIES;
160 	return g_object_new (type, NULL);
161 }
162 
163 static void
role_series_post_add(GogObject * parent,GogObject * child)164 role_series_post_add (GogObject *parent, GogObject *child)
165 {
166 	GogPlot *plot = GOG_PLOT (parent);
167 	GogSeries *series = GOG_SERIES (child);
168 	unsigned num_dim;
169 	num_dim = plot->desc.series.num_dim;
170 
171 	/* Alias things so that dim -1 is valid */
172 	series->values = g_new0 (GogDatasetElement, num_dim+1) + 1;
173 	series->plot = plot;
174 	series->interpolation = plot->interpolation;
175 
176 	/* if there are other series associated with the plot, and there are
177 	 * shared dimensions, clone them over.  */
178 	if (series->plot->series != NULL) {
179 		GogGraph *graph = gog_object_get_graph (GOG_OBJECT (plot));
180 		GogSeriesDesc const *desc = &plot->desc.series;
181 		GogSeries const *src = plot->series->data;
182 		unsigned i;
183 
184 		for (i = num_dim; i-- > 0 ; ) /* name is never shared */
185 			if (desc->dim[i].is_shared)
186 				gog_dataset_set_dim_internal (GOG_DATASET (series),
187 					i, src->values[i].data, graph);
188 
189 		gog_series_check_validity (series);
190 	}
191 
192 	/* APPEND to keep order, there won't be that many */
193 	plot->series = g_slist_append (plot->series, series);
194 	gog_plot_request_cardinality_update (plot);
195 }
196 
197 static void
role_series_pre_remove(GogObject * parent,GogObject * series)198 role_series_pre_remove (GogObject *parent, GogObject *series)
199 {
200 	GogPlot *plot = GOG_PLOT (parent);
201 	plot->series = g_slist_remove (plot->series, series);
202 	gog_plot_request_cardinality_update (plot);
203 }
204 
205 #ifdef GOFFICE_WITH_GTK
206 
207 static void
cb_axis_changed(GtkComboBox * combo,GogPlot * plot)208 cb_axis_changed (GtkComboBox *combo, GogPlot *plot)
209 {
210 	GtkTreeIter iter;
211 	GValue value;
212 	GtkTreeModel *model = gtk_combo_box_get_model (combo);
213 
214 	memset (&value, 0, sizeof (GValue));
215 	gtk_combo_box_get_active_iter (combo, &iter);
216 	gtk_tree_model_get_value (model, &iter, 1, &value);
217 	gog_plot_set_axis_by_id (plot,
218 				 GPOINTER_TO_UINT (g_object_get_data (G_OBJECT(combo), "axis-type")),
219 				 g_value_get_uint (&value));
220 }
221 
222 static void
gog_plot_populate_editor(GogObject * obj,GOEditor * editor,G_GNUC_UNUSED GogDataAllocator * dalloc,GOCmdContext * cc)223 gog_plot_populate_editor (GogObject *obj,
224 			  GOEditor *editor,
225 			  G_GNUC_UNUSED GogDataAllocator *dalloc,
226 			  GOCmdContext *cc)
227 {
228 	static const char *axis_labels[GOG_AXIS_TYPES] = {
229 		N_("X axis:"),
230 		N_("Y axis:"),
231 		N_("Z axis:"),
232 		N_("Circular axis:"),
233 		N_("Radial axis:"),
234 		N_("Pseudo 3D axis:"),
235 		N_("Color axis:"),
236 		N_("Bubble axis:")
237 	};
238 
239 	GogAxisType type;
240 	GogPlot *plot = GOG_PLOT (obj);
241 	unsigned count = 0, axis_count;
242 	GSList *axes, *ptr;
243 	GogChart *chart = GOG_CHART (gog_object_get_parent (obj));
244 	GogAxis *axis;
245 	GtkListStore *store;
246 	GtkTreeIter iter;
247 	GtkCellRenderer *cell;
248 	unsigned axis_set;
249 
250 	g_return_if_fail (chart != NULL);
251 
252 	axis_set = gog_chart_get_axis_set (chart) & GOG_AXIS_SET_FUNDAMENTAL;
253 	if (axis_set == GOG_AXIS_SET_XY || axis_set == GOG_AXIS_SET_RADAR) {
254 		GtkWidget *combo;
255 		GtkWidget *grid = gtk_grid_new ();
256 
257 		for (type = 0 ; type < GOG_AXIS_TYPES ; type++) {
258 			if (plot->axis[type] != NULL) {
259 				gtk_grid_attach (GTK_GRID (grid), gtk_label_new (_(axis_labels[type])),
260 						  0, count, 1, 1);
261 
262 				store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_UINT);
263 				combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
264 				g_object_unref (store);
265 
266 				cell = gtk_cell_renderer_text_new ();
267 				gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE);
268 				gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell,
269 								"text", 0,
270 								NULL);
271 
272 				axes = gog_chart_get_axes (chart, type);
273 				axis_count = 0;
274 				for (ptr = axes; ptr != NULL; ptr = ptr->next) {
275 					axis = GOG_AXIS (ptr->data);
276 					gtk_list_store_prepend (store, &iter);
277 					gtk_list_store_set (store, &iter,
278 							    0, gog_object_get_name (GOG_OBJECT (axis)),
279 							    1, gog_object_get_id (GOG_OBJECT (axis)),
280 							    -1);
281 					if (axis == plot->axis[type])
282 						gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &iter);
283 					axis_count++;
284 				}
285 				if (axis_count < 2)
286 					gtk_widget_set_sensitive (GTK_WIDGET (combo), FALSE);
287 				g_slist_free (axes);
288 				gtk_grid_attach (GTK_GRID (grid), combo,
289 						  1, count,1 ,1);
290 				g_object_set_data (G_OBJECT (combo), "axis-type", GUINT_TO_POINTER (type));
291 				g_signal_connect (G_OBJECT (combo), "changed",
292 						  G_CALLBACK (cb_axis_changed), plot);
293 				count++;
294 			}
295 		}
296 
297 		if (count > 0) {
298 			gtk_grid_set_column_spacing (GTK_GRID (grid), 12);
299 			gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
300 			gtk_container_set_border_width (GTK_CONTAINER (grid), 12);
301 			gtk_widget_show_all (grid);
302 			go_editor_add_page (editor, grid, _("Axes"));
303 		}
304 		else
305 			g_object_unref (grid);
306 	}
307 
308 	(GOG_OBJECT_CLASS(plot_parent_klass)->populate_editor) (obj, editor, dalloc, cc);
309 }
310 #endif
311 
312 static void
gog_plot_set_property(GObject * obj,guint param_id,GValue const * value,GParamSpec * pspec)313 gog_plot_set_property (GObject *obj, guint param_id,
314 		       GValue const *value, GParamSpec *pspec)
315 {
316 	GogPlot *plot = GOG_PLOT (obj);
317 	gboolean b_tmp;
318 
319 	switch (param_id) {
320 	case PLOT_PROP_VARY_STYLE_BY_ELEMENT:
321 		b_tmp = g_value_get_boolean (value) &&
322 			gog_plot_supports_vary_style_by_element (plot);
323 		if (plot->vary_style_by_element ^ b_tmp) {
324 			plot->vary_style_by_element = b_tmp;
325 			gog_plot_request_cardinality_update (plot);
326 		}
327 		break;
328 	case PLOT_PROP_AXIS_X:
329 		gog_plot_set_axis_by_id (plot, GOG_AXIS_X, g_value_get_uint (value));
330 		break;
331 	case PLOT_PROP_AXIS_Y:
332 		gog_plot_set_axis_by_id (plot, GOG_AXIS_Y, g_value_get_uint (value));
333 		break;
334 	case PLOT_PROP_AXIS_Z:
335 		gog_plot_set_axis_by_id (plot, GOG_AXIS_Z, g_value_get_uint (value));
336 		break;
337 	case PLOT_PROP_AXIS_CIRCULAR:
338 		gog_plot_set_axis_by_id (plot, GOG_AXIS_CIRCULAR, g_value_get_uint (value));
339 		break;
340 	case PLOT_PROP_AXIS_RADIAL:
341 		gog_plot_set_axis_by_id (plot, GOG_AXIS_RADIAL, g_value_get_uint (value));
342 		break;
343 	case PLOT_PROP_AXIS_PSEUDO_3D:
344 		gog_plot_set_axis_by_id (plot, GOG_AXIS_PSEUDO_3D, g_value_get_uint (value));
345 		break;
346 	case PLOT_PROP_AXIS_COLOR:
347 		gog_plot_set_axis_by_id (plot, GOG_AXIS_COLOR, g_value_get_uint (value));
348 		break;
349 	case PLOT_PROP_AXIS_BUBBLE:
350 		gog_plot_set_axis_by_id (plot, GOG_AXIS_BUBBLE, g_value_get_uint (value));
351 		break;
352 	case PLOT_PROP_GROUP:
353 		g_free (plot->plot_group);
354 		plot->plot_group = g_value_dup_string (value);
355 		break;
356 	case PLOT_PROP_DEFAULT_INTERPOLATION:
357 		plot->interpolation = go_line_interpolation_from_str (g_value_get_string (value));
358 		break;
359 	case PLOT_PROP_GURU_HINTS:
360 		g_free (plot->guru_hints);
361 		plot->guru_hints = g_value_dup_string (value);
362 		break;
363 
364 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
365 		 return; /* NOTE : RETURN */
366 	}
367 }
368 
369 static void
gog_plot_get_property(GObject * obj,guint param_id,GValue * value,GParamSpec * pspec)370 gog_plot_get_property (GObject *obj, guint param_id,
371 		       GValue *value, GParamSpec *pspec)
372 {
373 	GogPlot *plot = GOG_PLOT (obj);
374 	switch (param_id) {
375 	case PLOT_PROP_VARY_STYLE_BY_ELEMENT:
376 		g_value_set_boolean (value,
377 			plot->vary_style_by_element &&
378 			gog_plot_supports_vary_style_by_element (plot));
379 		break;
380 	case PLOT_PROP_AXIS_X:
381 		g_value_set_uint (value, gog_plot_get_axis_id (plot, GOG_AXIS_X));
382 		break;
383 	case PLOT_PROP_AXIS_Y:
384 		g_value_set_uint (value, gog_plot_get_axis_id (plot, GOG_AXIS_Y));
385 		break;
386 	case PLOT_PROP_AXIS_Z:
387 		g_value_set_uint (value, gog_plot_get_axis_id (plot, GOG_AXIS_Z));
388 		break;
389 	case PLOT_PROP_AXIS_CIRCULAR:
390 		g_value_set_uint (value, gog_plot_get_axis_id (plot, GOG_AXIS_CIRCULAR));
391 		break;
392 	case PLOT_PROP_AXIS_RADIAL:
393 		g_value_set_uint (value, gog_plot_get_axis_id (plot, GOG_AXIS_RADIAL));
394 		break;
395 	case PLOT_PROP_AXIS_PSEUDO_3D:
396 		g_value_set_uint (value, gog_plot_get_axis_id (plot, GOG_AXIS_PSEUDO_3D));
397 		break;
398 	case PLOT_PROP_AXIS_COLOR:
399 		g_value_set_uint (value, gog_plot_get_axis_id (plot, GOG_AXIS_COLOR));
400 		break;
401 	case PLOT_PROP_AXIS_BUBBLE:
402 		g_value_set_uint (value, gog_plot_get_axis_id (plot, GOG_AXIS_BUBBLE));
403 		break;
404 	case PLOT_PROP_GROUP:
405 		g_value_set_string (value, plot->plot_group);
406 		break;
407 	case PLOT_PROP_DEFAULT_INTERPOLATION:
408 		g_value_set_string (value, go_line_interpolation_as_str (plot->interpolation));
409 		break;
410 	case PLOT_PROP_GURU_HINTS:
411 		g_value_set_string (value, plot->guru_hints);
412 		break;
413 
414 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
415 		 break;
416 	}
417 }
418 
419 static void
gog_plot_children_reordered(GogObject * obj)420 gog_plot_children_reordered (GogObject *obj)
421 {
422 	GSList *ptr, *accum = NULL;
423 	GogPlot *plot = GOG_PLOT (obj);
424 
425 	for (ptr = obj->children; ptr != NULL ; ptr = ptr->next)
426 		if (GOG_IS_SERIES (ptr->data))
427 			accum = g_slist_prepend (accum, ptr->data);
428 	g_slist_free (plot->series);
429 	plot->series = g_slist_reverse (accum);
430 
431 	gog_plot_request_cardinality_update (plot);
432 }
433 
434 static void
gog_plot_class_init(GogObjectClass * gog_klass)435 gog_plot_class_init (GogObjectClass *gog_klass)
436 {
437 	static GogObjectRole const roles[] = {
438 		{ N_("Series"), "GogSeries",	0,
439 		  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
440 		  role_series_can_add, NULL,
441 		  role_series_allocate,
442 		  role_series_post_add, role_series_pre_remove, NULL },
443 	};
444 	GObjectClass *gobject_klass = (GObjectClass *) gog_klass;
445 	GogPlotClass *plot_klass = (GogPlotClass *) gog_klass;
446 
447 	plot_parent_klass = g_type_class_peek_parent (gog_klass);
448 	gobject_klass->finalize		= gog_plot_finalize;
449 	gobject_klass->set_property	= gog_plot_set_property;
450 	gobject_klass->get_property	= gog_plot_get_property;
451 #ifdef GOFFICE_WITH_GTK
452 	gog_klass->populate_editor	= gog_plot_populate_editor;
453 #endif
454 	plot_klass->axis_set 		= GOG_AXIS_SET_NONE;
455 	plot_klass->guru_helper		= NULL;
456 
457 	g_object_class_install_property (gobject_klass, PLOT_PROP_VARY_STYLE_BY_ELEMENT,
458 		g_param_spec_boolean ("vary-style-by-element",
459 			_("Vary style by element"),
460 			_("Use a different style for each segment"),
461 			FALSE,
462 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT | GOG_PARAM_FORCE_SAVE));
463 	g_object_class_install_property (gobject_klass, PLOT_PROP_AXIS_X,
464 		g_param_spec_uint ("x-axis",
465 			_("X axis"),
466 			_("Reference to X axis"),
467 			0, G_MAXINT, 0,
468 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
469 	g_object_class_install_property (gobject_klass, PLOT_PROP_AXIS_Y,
470 		g_param_spec_uint ("y-axis",
471 			_("Y axis"),
472 			_("Reference to Y axis"),
473 			0, G_MAXINT, 0,
474 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
475 	g_object_class_install_property (gobject_klass, PLOT_PROP_AXIS_Z,
476 		g_param_spec_uint ("z-axis",
477 			_("Z axis"),
478 			_("Reference to Z axis"),
479 			0, G_MAXINT, 0,
480 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
481 	g_object_class_install_property (gobject_klass, PLOT_PROP_AXIS_CIRCULAR,
482 		g_param_spec_uint ("circ-axis",
483 			_("Circular axis"),
484 			_("Reference to circular axis"),
485 			0, G_MAXINT, 0,
486 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
487 	g_object_class_install_property (gobject_klass, PLOT_PROP_AXIS_RADIAL,
488 		g_param_spec_uint ("radial-axis",
489 			_("Radial axis"),
490 			_("Reference to radial axis"),
491 			0, G_MAXINT, 0,
492 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
493 	g_object_class_install_property (gobject_klass, PLOT_PROP_AXIS_PSEUDO_3D,
494 		g_param_spec_uint ("pseudo-3d-axis",
495 			_("Pseudo-3D axis"),
496 			_("Reference to pseudo-3D axis"),
497 			0, G_MAXINT, 0,
498 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
499 	g_object_class_install_property (gobject_klass, PLOT_PROP_AXIS_COLOR,
500 		g_param_spec_uint ("color-axis",
501 			_("Color axis"),
502 			_("Reference to color axis"),
503 			0, G_MAXINT, 0,
504 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
505 	g_object_class_install_property (gobject_klass, PLOT_PROP_AXIS_BUBBLE,
506 		g_param_spec_uint ("bubble-axis",
507 			_("Bubble axis"),
508 			_("Reference to bubble axis"),
509 			0, G_MAXINT, 0,
510 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
511 	g_object_class_install_property (gobject_klass, PLOT_PROP_GROUP,
512 		g_param_spec_string ("plot-group",
513 			_("Plot group"),
514 			_("Name of plot group if any"),
515 			NULL,
516 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
517 	g_object_class_install_property (gobject_klass, PLOT_PROP_GURU_HINTS,
518 		g_param_spec_string ("guru-hints",
519 			_("Guru hints"),
520 			_("Semicolon separated list of hints for automatic addition of objects in "
521 			  "guru dialog"),
522 			NULL,
523 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
524         g_object_class_install_property (gobject_klass, PLOT_PROP_DEFAULT_INTERPOLATION,
525 		 g_param_spec_string ("interpolation",
526 			_("Default interpolation"),
527 			_("Default type of series line interpolation"),
528 			"linear",
529 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
530 
531 	gog_klass->children_reordered = gog_plot_children_reordered;
532 	gog_object_register_roles (gog_klass, roles, G_N_ELEMENTS (roles));
533 	GOG_PLOT_CLASS (gog_klass)->update_3d = NULL;
534 }
535 
536 static void
gog_plot_init(GogPlot * plot,GogPlotClass const * derived_plot_klass)537 gog_plot_init (GogPlot *plot, GogPlotClass const *derived_plot_klass)
538 {
539 	/* keep a local copy so that we can over-ride things if desired */
540 	plot->desc = derived_plot_klass->desc;
541 	/* start as true so that we can queue an update when it changes */
542 	plot->cardinality_valid = TRUE;
543 	plot->rendering_order = GOG_PLOT_RENDERING_LAST;
544 	plot->plot_group = NULL;
545 	plot->guru_hints = NULL;
546 	plot->interpolation = GO_LINE_INTERPOLATION_LINEAR;
547 }
548 
GSF_CLASS_ABSTRACT(GogPlot,gog_plot,gog_plot_class_init,gog_plot_init,GOG_TYPE_OBJECT)549 GSF_CLASS_ABSTRACT (GogPlot, gog_plot,
550 		    gog_plot_class_init, gog_plot_init,
551 		    GOG_TYPE_OBJECT)
552 
553 GogPlot *
554 gog_plot_new_by_type (GogPlotType const *type)
555 {
556 	GogPlot *res;
557 
558 	g_return_val_if_fail (type != NULL, NULL);
559 
560 	res = gog_plot_new_by_name (type->engine);
561 	if (res != NULL && type->properties != NULL)
562 		g_hash_table_foreach (type->properties,
563 			(GHFunc) gog_object_set_arg, res);
564 	return res;
565 }
566 
567 /****************************************************************
568  * convenience routines
569  **/
570 
571 /**
572  * gog_plot_new_series:
573  * @plot: #GogPlot
574  *
575  * Returns: (transfer none): a new GogSeries of a type consistent with @plot.
576  **/
577 GogSeries *
gog_plot_new_series(GogPlot * plot)578 gog_plot_new_series (GogPlot *plot)
579 {
580 	GogObject *res;
581 
582 	g_return_val_if_fail (GOG_IS_PLOT (plot), NULL);
583 
584 	res = gog_object_add_by_name (GOG_OBJECT (plot), "Series", NULL);
585 	return res ? GOG_SERIES (res) : NULL;
586 }
587 
588 /**
589  * gog_plot_description: (skip)
590  * @plot: #GogPlot
591  *
592  * Returns: (transfer none): the #GogPlotDesc for @plot.
593  **/
594 GogPlotDesc const *
gog_plot_description(GogPlot const * plot)595 gog_plot_description (GogPlot const *plot)
596 {
597 	g_return_val_if_fail (GOG_IS_PLOT (plot), NULL);
598 	return &plot->desc;
599 }
600 
601 static GogChart *
gog_plot_get_chart(GogPlot const * plot)602 gog_plot_get_chart (GogPlot const *plot)
603 {
604 	return GOG_CHART (GOG_OBJECT (plot)->parent);
605 }
606 
607 /* protected */
608 void
gog_plot_request_cardinality_update(GogPlot * plot)609 gog_plot_request_cardinality_update (GogPlot *plot)
610 {
611 	g_return_if_fail (GOG_IS_PLOT (plot));
612 
613 	if (plot->cardinality_valid) {
614 		GogChart *chart = gog_plot_get_chart (plot);
615 		plot->cardinality_valid = FALSE;
616 		gog_object_request_update (GOG_OBJECT (plot));
617 		if (chart != NULL)
618 			gog_chart_request_cardinality_update (chart);
619 	}
620 }
621 
622 /**
623  * gog_plot_update_cardinality:
624  * @plot: #GogPlot
625  * @index_num: index offset for this plot
626  *
627  * Update cardinality and cache result. See @gog_chart_get_cardinality.
628  **/
629 void
gog_plot_update_cardinality(GogPlot * plot,int index_num)630 gog_plot_update_cardinality (GogPlot *plot, int index_num)
631 {
632 	GogSeries *series;
633 	GSList	  *ptr, *children;
634 	gboolean   is_valid;
635 	unsigned   size = 0, no_legend = 0, i, j;
636 
637 	g_return_if_fail (GOG_IS_PLOT (plot));
638 
639 	plot->cardinality_valid = TRUE;
640 	plot->index_num = i = j = index_num;
641 
642 	for (ptr = plot->series; ptr != NULL ; ptr = ptr->next) {
643 		series = GOG_SERIES (ptr->data);
644 		is_valid = gog_series_is_valid (GOG_SERIES (series));
645 		if (plot->vary_style_by_element) {
646 			if (is_valid && size < series->num_elements)
647 				size = series->num_elements;
648 			gog_series_set_index (series, plot->index_num, FALSE);
649 		} else {
650 			gog_series_set_index (series, i++, FALSE);
651 			if (!gog_series_has_legend (series))
652 				no_legend++;
653 			j++;
654 		}
655 		/* now add the trend lines if any */
656 		children = GOG_OBJECT (series)->children;
657 		while (children) {
658 			if (GOG_IS_TREND_LINE (children->data)) {
659 				j++;
660 				if (!gog_trend_line_has_legend (GOG_TREND_LINE (children->data)))
661 					no_legend++;
662 			}
663 			children = children->next;
664 		}
665 	}
666 
667 	plot->full_cardinality =
668 		plot->vary_style_by_element ? size : (j - plot->index_num);
669 	plot->visible_cardinality = plot->full_cardinality - no_legend;
670 }
671 
672 /**
673  * gog_plot_get_cardinality:
674  * @plot: #GogPlot
675  * @full: placeholder for full cardinality
676  * @visible: placeholder for visible cardinality
677  *
678  * Return the number of logical elements in the plot.
679  * See @gog_chart_get_cardinality.
680  *
681  * @full and @visible may be %NULL.
682  **/
683 void
gog_plot_get_cardinality(GogPlot * plot,unsigned * full,unsigned * visible)684 gog_plot_get_cardinality (GogPlot *plot, unsigned *full, unsigned *visible)
685 {
686 	g_return_if_fail (GOG_IS_PLOT (plot));
687 
688 	if (!plot->cardinality_valid)
689 		g_warning ("[GogPlot::get_cardinality] Invalid cardinality");
690 
691 	if (full != NULL)
692 		*full = plot->full_cardinality;
693 	if (visible != NULL)
694 		*visible = plot->visible_cardinality;
695 }
696 
697 static gboolean
gog_plot_enum_in_reverse(GogPlot const * plot)698 gog_plot_enum_in_reverse (GogPlot const *plot)
699 {
700 	GogPlotClass *klass = GOG_PLOT_GET_CLASS (plot);
701 	return klass != NULL && klass->enum_in_reverse && (klass->enum_in_reverse) (plot);
702 }
703 
704 /**
705  * gog_plot_foreach_elem:
706  * @plot: #GogPlot
707  * @only_visible: whether to restrict to visible elements.
708  * @handler: (scope call): #GogEnumFunc
709  * @data: user data for @func
710  *
711  * Executes @funcfor each plot element. Used to build a legend.
712  **/
713 void
gog_plot_foreach_elem(GogPlot * plot,gboolean only_visible,GogEnumFunc func,gpointer data)714 gog_plot_foreach_elem (GogPlot *plot, gboolean only_visible,
715 		       GogEnumFunc func, gpointer data)
716 {
717 	GSList *ptr, *children;
718 	GogSeries const *series;
719 	GOStyle *style, *tmp_style;
720 	GOData *labels;
721 	unsigned i, n, num_labels = 0;
722 	char *label = NULL;
723 	GogTheme *theme = gog_object_get_theme (GOG_OBJECT (plot));
724 	GogPlotClass *klass = GOG_PLOT_GET_CLASS (plot);
725 	GList *overrides;
726 	PangoAttrList *pl;
727 
728 	g_return_if_fail (GOG_IS_PLOT (plot));
729 	if (!plot->cardinality_valid)
730 		gog_plot_get_cardinality (plot, NULL, NULL);
731 
732 	if (klass->foreach_elem) {
733 		klass->foreach_elem (plot, only_visible, func, data);
734 		return;
735 	}
736 
737 	ptr = plot->series;
738 	if (ptr == NULL)
739 		return;
740 
741 	if (!plot->vary_style_by_element) {
742 		GSList *tmp = NULL;
743 
744 		unsigned i = plot->index_num;
745 
746 		if (gog_plot_enum_in_reverse (plot))
747 			ptr = tmp = g_slist_reverse (g_slist_copy (ptr));
748 
749 		for (; ptr != NULL ; ptr = ptr->next) {
750 			if (!only_visible || gog_series_has_legend (ptr->data)) {
751 				GOData *dat = gog_dataset_get_dim (GOG_DATASET (ptr->data), -1);
752 				func (i, go_styled_object_get_style (ptr->data),
753 				      gog_object_get_name (ptr->data),
754 				      (dat? go_data_get_scalar_markup (dat): NULL),
755 				      data);
756 				i++;
757 			}
758 			/* now add the trend lines if any */
759 			children = GOG_OBJECT (ptr->data)->children;
760 			while (children) {
761 				if (GOG_IS_TREND_LINE (children->data) &&
762 				    gog_trend_line_has_legend (GOG_TREND_LINE (children->data))) {
763 					func (i, go_styled_object_get_style (children->data),
764 					      gog_object_get_name (children->data), NULL, data);
765 					i++;
766 				}
767 				children = children->next;
768 			}
769 		}
770 
771 		g_slist_free (tmp);
772 
773 		return;
774 	}
775 
776 	series = ptr->data; /* start with the first */
777 	labels = NULL;
778 	if (series->values[0].data != NULL) {
779 		labels = series->values[0].data;
780 		num_labels = go_data_get_vector_size (labels);
781 	}
782 	style = go_style_dup (series->base.style);
783 	n = only_visible ? plot->visible_cardinality : plot->full_cardinality;
784 	for (overrides = series->overrides, i = 0; i < n ; i++) {
785 		if (overrides != NULL &&
786 		    (GOG_SERIES_ELEMENT (overrides->data)->index == i)) {
787 			tmp_style = GOG_STYLED_OBJECT (overrides->data)->style;
788 			overrides = overrides->next;
789 		} else
790 			tmp_style = style;
791 
792 		gog_theme_fillin_style (theme, tmp_style, GOG_OBJECT (series),
793 			plot->index_num + i, tmp_style->interesting_fields);
794 		if (labels != NULL) {
795 			label = (i < num_labels) ? go_data_get_vector_string (labels, i) : g_strdup ("");
796 			pl = (i < num_labels)? go_data_get_vector_markup (labels, i): NULL;
797 		} else {
798 			label = NULL;
799 			pl = NULL;
800 		}
801 		if (label == NULL)
802 			label = g_strdup_printf ("%d", i);
803 		(func) (i, tmp_style, label, pl, data);
804 		g_free (label);
805 	}
806 	g_object_unref (style);
807 }
808 
809 /**
810  * gog_plot_get_series:
811  * @plot: #GogPlot
812  *
813  * Returns: (transfer none) (element-type GogSeries) : A list of the series in @plot.  Do not modify or free the list.
814  **/
815 GSList const *
gog_plot_get_series(GogPlot const * plot)816 gog_plot_get_series (GogPlot const *plot)
817 {
818 	g_return_val_if_fail (GOG_IS_PLOT (plot), NULL);
819 	return plot->series;
820 }
821 
822 /**
823  * gog_plot_get_axis_bounds:
824  * @plot: #GogPlot
825  * @axis: #GogAxisType
826  * @bounds: #GogPlotBoundInfo
827  *
828  * Queries @plot for its axis preferences for @axis and stores the results in
829  * @bounds.  All elements of @bounds are initialized to sane values before the
830  * query _EXCEPT_ ::fmt.  The caller is responsible for initializing it.  This
831  * is done so that once on plot has selected a format the others need not do
832  * the lookup too if so desired.
833  *
834  * Caller is responsible for unrefing ::fmt.
835  *
836  * Returns: (transfer none): The data providing the bound (does not add a ref)
837  **/
838 GOData *
gog_plot_get_axis_bounds(GogPlot * plot,GogAxisType axis,GogPlotBoundInfo * bounds)839 gog_plot_get_axis_bounds (GogPlot *plot, GogAxisType axis,
840 			  GogPlotBoundInfo *bounds)
841 {
842 	GogPlotClass *klass = GOG_PLOT_GET_CLASS (plot);
843 
844 	g_return_val_if_fail (klass != NULL, NULL);
845 	g_return_val_if_fail (bounds != NULL, NULL);
846 
847 	bounds->val.minima =  DBL_MAX;
848 	bounds->val.maxima = -DBL_MAX;
849 	bounds->logical.maxima = go_nan;
850 	bounds->logical.minima = go_nan;
851 	bounds->is_discrete = FALSE;
852 	bounds->center_on_ticks = TRUE;
853 	bounds->date_conv = NULL;
854 
855 	if (klass->axis_get_bounds == NULL)
856 		return NULL;
857 	return (klass->axis_get_bounds) (plot, axis, bounds);
858 }
859 
860 gboolean
gog_plot_supports_vary_style_by_element(GogPlot const * plot)861 gog_plot_supports_vary_style_by_element (GogPlot const *plot)
862 {
863 	GogPlotClass *klass = GOG_PLOT_GET_CLASS (plot);
864 
865 	g_return_val_if_fail (klass != NULL, FALSE);
866 
867 	if (klass->supports_vary_style_by_element)
868 		return (klass->supports_vary_style_by_element) (plot);
869 	return TRUE; /* default */
870 }
871 
872 GogAxisSet
gog_plot_axis_set_pref(GogPlot const * plot)873 gog_plot_axis_set_pref (GogPlot const *plot)
874 {
875 	GogPlotClass *klass = GOG_PLOT_GET_CLASS (plot);
876 
877 	g_return_val_if_fail (klass != NULL, GOG_AXIS_SET_UNKNOWN);
878 	return klass->axis_set;
879 }
880 
881 gboolean
gog_plot_axis_set_is_valid(GogPlot const * plot,GogAxisSet axis_set)882 gog_plot_axis_set_is_valid (GogPlot const *plot, GogAxisSet axis_set)
883 {
884 	GogPlotClass *klass = GOG_PLOT_GET_CLASS (plot);
885 
886 	g_return_val_if_fail (klass != NULL, FALSE);
887 	return (axis_set == klass->axis_set);
888 }
889 
890 /**
891  * gog_plot_set_axis:
892  * @plot: #GogPlot
893  * @axis: #GogAxis
894  *
895  * Connect @axis and @plot.
896  **/
897 void
gog_plot_set_axis(GogPlot * plot,GogAxis * axis)898 gog_plot_set_axis (GogPlot *plot, GogAxis *axis)
899 {
900 	GogAxisType type;
901 	g_return_if_fail (GOG_IS_PLOT (plot));
902 	g_return_if_fail (GOG_IS_AXIS (axis));
903 
904 	type = gog_axis_get_atype (axis);
905 	g_return_if_fail (type != GOG_AXIS_UNKNOWN);
906 
907 	if (plot->axis[type] == axis)
908 		return;
909 
910 	if (plot->axis[type] != NULL)
911 		gog_axis_del_contributor (plot->axis[type], GOG_OBJECT (plot));
912 	plot->axis[type] = axis;
913 	gog_axis_add_contributor (axis, GOG_OBJECT (plot));
914 }
915 
916 static gboolean
gog_plot_set_axis_by_id(GogPlot * plot,GogAxisType type,unsigned id)917 gog_plot_set_axis_by_id (GogPlot *plot, GogAxisType type, unsigned id)
918 {
919 	GogChart const *chart;
920 	GogAxis *axis;
921 	GSList *axes, *ptr;
922 	gboolean found = FALSE;
923 
924 	if (id == 0)
925 		return FALSE;
926 
927 	g_return_val_if_fail (GOG_IS_PLOT (plot), FALSE);
928 	g_return_val_if_fail (GOG_OBJECT (plot)->parent != NULL, FALSE);
929 
930 	chart = gog_plot_get_chart (plot);
931 	g_return_val_if_fail (GOG_CHART (chart) != NULL, FALSE);
932 
933 	axes = gog_chart_get_axes (chart, type);
934 	g_return_val_if_fail (axes != NULL, FALSE);
935 
936 	for (ptr = axes; ptr != NULL && !found; ptr = ptr->next) {
937 		axis = GOG_AXIS (ptr->data);
938 		if (gog_object_get_id (GOG_OBJECT (axis)) == id) {
939 			gog_plot_set_axis (plot, axis);
940 			found = TRUE;
941 		}
942 	}
943 	g_slist_free (axes);
944 	return found;
945 }
946 
947 gboolean
gog_plot_axis_set_assign(GogPlot * plot,GogAxisSet axis_set)948 gog_plot_axis_set_assign (GogPlot *plot, GogAxisSet axis_set)
949 {
950 	GogPlotClass *klass = GOG_PLOT_GET_CLASS (plot);
951 	GogChart const *chart;
952 	GogAxisType type;
953 
954 	g_return_val_if_fail (klass != NULL, FALSE);
955 
956 	chart = gog_plot_get_chart (plot);
957 	for (type = 0 ; type < GOG_AXIS_TYPES ; type++) {
958 		if (plot->axis[type] != NULL) {
959 			if (!(axis_set & (1 << type))) {
960 				gog_axis_del_contributor (plot->axis[type], GOG_OBJECT (plot));
961 				plot->axis[type] = NULL;
962 			}
963 		} else if (axis_set & (1 << type)) {
964 			GSList *axes = gog_chart_get_axes (chart, type);
965 			if (axes != NULL) {
966 				gog_axis_add_contributor (axes->data, GOG_OBJECT (plot));
967 				plot->axis[type] = axes->data;
968 				g_slist_free (axes);
969 			}
970 		}
971 	}
972 
973 	return (axis_set == klass->axis_set);
974 }
975 
976 /**
977  * gog_plot_axis_clear:
978  * @plot: #GogPlot
979  * @filter: #GogAxisSet
980  *
981  * A utility method to clear all existing axis associations flagged by @filter
982  **/
983 void
gog_plot_axis_clear(GogPlot * plot,GogAxisSet filter)984 gog_plot_axis_clear (GogPlot *plot, GogAxisSet filter)
985 {
986 	GogAxisType type;
987 
988 	g_return_if_fail (GOG_IS_PLOT (plot));
989 
990 	for (type = 0 ; type < GOG_AXIS_TYPES ; type++)
991 		if (plot->axis[type] != NULL && ((1 << type) & filter)) {
992 			gog_axis_del_contributor (plot->axis[type], GOG_OBJECT (plot));
993 			plot->axis[type] = NULL;
994 		}
995 }
996 
997 static unsigned
gog_plot_get_axis_id(GogPlot const * plot,GogAxisType type)998 gog_plot_get_axis_id (GogPlot const *plot, GogAxisType type)
999 {
1000 	GogAxis *axis = gog_plot_get_axis (plot, type);
1001 
1002 	return axis != NULL ? gog_object_get_id (GOG_OBJECT (axis)) : 0;
1003 }
1004 
1005 /**
1006  * gog_plot_get_axis:
1007  * @plot: #GogPlot
1008  * @type: #GogAxisType
1009  *
1010  * Returns: (transfer none): the axis if any.
1011  */
1012 GogAxis	*
gog_plot_get_axis(GogPlot const * plot,GogAxisType type)1013 gog_plot_get_axis (GogPlot const *plot, GogAxisType type)
1014 {
1015 	g_return_val_if_fail (GOG_IS_PLOT (plot), NULL);
1016 	g_return_val_if_fail (type < GOG_AXIS_TYPES, NULL);
1017 	g_return_val_if_fail (GOG_AXIS_UNKNOWN < type, NULL);
1018 	return plot->axis[type];
1019 }
1020 
1021 void
gog_plot_update_3d(GogPlot * plot)1022 gog_plot_update_3d (GogPlot *plot)
1023 {
1024 	GogPlotClass *klass = GOG_PLOT_GET_CLASS (plot);
1025 	g_return_if_fail (GOG_IS_PLOT (plot));
1026 
1027 	if (klass->update_3d)
1028 		klass->update_3d (plot);
1029 }
1030 
1031 static void
gog_plot_guru_helper_add_grid_line(GogPlot * plot,gboolean major)1032 gog_plot_guru_helper_add_grid_line (GogPlot *plot, gboolean major)
1033 {
1034 	GogAxisType type;
1035 
1036 	for (type = 0; type < GOG_AXIS_TYPES; type++) {
1037 		if ((((type == GOG_AXIS_X) |
1038 		      (type == GOG_AXIS_Y) |
1039 		      (type == GOG_AXIS_CIRCULAR) |
1040 		      (type == GOG_AXIS_RADIAL))) &&
1041 		    plot->axis[type] != NULL &&
1042 		    gog_axis_get_grid_line (plot->axis[type], major) == NULL)
1043 		{
1044 			gog_object_add_by_name (GOG_OBJECT (plot->axis[type]),
1045 						major ? "MajorGrid": "MinorGrid", NULL);
1046 		}
1047 	}
1048 }
1049 
1050 void
gog_plot_guru_helper(GogPlot * plot)1051 gog_plot_guru_helper (GogPlot *plot)
1052 {
1053 	GogPlotClass *klass;
1054 	char **hints;
1055 	char *hint;
1056 	unsigned i;
1057 
1058 	g_return_if_fail (GOG_IS_PLOT (plot));
1059 	klass = GOG_PLOT_GET_CLASS (plot);
1060 
1061 	if (plot->guru_hints == NULL)
1062 		return;
1063 
1064 	hints = g_strsplit (plot->guru_hints, ";", 0);
1065 
1066 	for (i = 0; i < g_strv_length (hints); i++) {
1067 		hint = g_strstrip (hints[i]);
1068 		if (strcmp (hints[i], "backplane") == 0) {
1069 			GogChart *chart = GOG_CHART (gog_object_get_parent (GOG_OBJECT (plot)));
1070 			if (chart != NULL && gog_chart_get_grid (chart) == NULL)
1071 				gog_object_add_by_name (GOG_OBJECT (chart), "Backplane", NULL);
1072 		} else if (strcmp (hints[i], "major-grid") == 0) {
1073 			gog_plot_guru_helper_add_grid_line (plot, TRUE);
1074 		} else if (strcmp (hints[i], "minor-grid") == 0) {
1075 			gog_plot_guru_helper_add_grid_line (plot, FALSE);
1076 		} else if (klass->guru_helper)
1077 			klass->guru_helper (plot, hint);
1078 	}
1079 
1080 	g_strfreev (hints);
1081 }
1082 
gog_plot_clear_series(GogPlot * plot)1083 void gog_plot_clear_series (GogPlot *plot)
1084 {
1085 	while (plot->series) {
1086 		GogObject *gobj = GOG_OBJECT (plot->series->data);
1087 		gog_object_clear_parent (gobj);
1088 		g_object_unref (gobj);
1089 	}
1090 }
1091 
1092 double
gog_plot_get_percent_value(GogPlot * plot,unsigned series,unsigned index)1093 gog_plot_get_percent_value (GogPlot *plot, unsigned series, unsigned index)
1094 {
1095 	GogPlotClass *klass;
1096 	g_return_val_if_fail (GOG_IS_PLOT (plot), go_nan);
1097 	klass = GOG_PLOT_GET_CLASS (plot);
1098 	return (klass->get_percent)? klass->get_percent (plot, series, index): go_nan;
1099 }
1100 
1101 /*****************************************************************************/
1102 
1103 #ifdef GOFFICE_WITH_GTK
1104 typedef struct {
1105 	GogViewAllocation	 start_position;
1106 	GogViewAllocation	 chart_allocation;
1107 	GogChart		*chart;
1108 } MovePlotAreaData;
1109 
1110 static gboolean
gog_tool_move_plot_area_point(GogView * view,double x,double y,GogObject ** gobj)1111 gog_tool_move_plot_area_point (GogView *view, double x, double y, GogObject **gobj)
1112 {
1113 	GogViewAllocation const *plot_area = gog_chart_view_get_plot_area (view->parent);
1114 
1115 	return (x >= plot_area->x &&
1116 		x <= (plot_area->x + plot_area->w) &&
1117 		y >= plot_area->y &&
1118 		y <= (plot_area->y + plot_area->h));
1119 }
1120 
1121 static void
gog_tool_move_plot_area_render(GogView * view)1122 gog_tool_move_plot_area_render (GogView *view)
1123 {
1124 	GogViewAllocation const *plot_area = gog_chart_view_get_plot_area (view->parent);
1125 
1126 	gog_renderer_draw_selection_rectangle (view->renderer, plot_area);
1127 }
1128 
1129 static void
gog_tool_move_plot_area_init(GogToolAction * action)1130 gog_tool_move_plot_area_init (GogToolAction *action)
1131 {
1132 	MovePlotAreaData *data = g_new0 (MovePlotAreaData, 1);
1133 
1134 	data->chart = GOG_CHART (action->view->parent->model);
1135 	data->chart_allocation = action->view->parent->allocation;
1136 	data->start_position = *gog_chart_view_get_plot_area (action->view->parent);
1137 	data->start_position.x = (data->start_position.x - data->chart_allocation.x) / data->chart_allocation.w;
1138 	data->start_position.w = data->start_position.w / data->chart_allocation.w;
1139 	data->start_position.y = (data->start_position.y - data->chart_allocation.y) / data->chart_allocation.h;
1140 	data->start_position.h = data->start_position.h / data->chart_allocation.h;
1141 
1142 	action->data = data;
1143 }
1144 
1145 static void
gog_tool_move_plot_area_move(GogToolAction * action,double x,double y)1146 gog_tool_move_plot_area_move (GogToolAction *action, double x, double y)
1147 {
1148 	GogViewAllocation plot_area;
1149 	MovePlotAreaData *data = action->data;
1150 
1151 	plot_area.w = data->start_position.w;
1152 	plot_area.h = data->start_position.h;
1153 	plot_area.x = data->start_position.x + (x - action->start_x) / data->chart_allocation.w;
1154 	if (plot_area.x < 0.0)
1155 		plot_area.x = 0.0;
1156 	else if (plot_area.x + plot_area.w > 1.0)
1157 		plot_area.x = 1.0 - plot_area.w;
1158 	plot_area.y = data->start_position.y + (y - action->start_y) / data->chart_allocation.h;
1159 	if (plot_area.y < 0.0)
1160 		plot_area.y = 0.0;
1161 	else if (plot_area.y + plot_area.h > 1.0)
1162 		plot_area.y = 1.0 - plot_area.h;
1163 	gog_chart_set_plot_area (data->chart, &plot_area);
1164 }
1165 
1166 static void
gog_tool_move_plot_area_double_click(GogToolAction * action)1167 gog_tool_move_plot_area_double_click (GogToolAction *action)
1168 {
1169 	MovePlotAreaData *data = action->data;
1170 
1171 	gog_chart_set_plot_area (data->chart, NULL);
1172 }
1173 
1174 static GogTool gog_tool_move_plot_area = {
1175 	N_("Move plot area"),
1176 	GDK_FLEUR,
1177 	gog_tool_move_plot_area_point,
1178 	gog_tool_move_plot_area_render,
1179 	gog_tool_move_plot_area_init,
1180 	gog_tool_move_plot_area_move,
1181 	gog_tool_move_plot_area_double_click,
1182 	NULL /*destroy*/
1183 };
1184 
1185 static gboolean
gog_tool_resize_plot_area_point(GogView * view,double x,double y,GogObject ** gobj)1186 gog_tool_resize_plot_area_point (GogView *view, double x, double y, GogObject **gobj)
1187 {
1188 	GogViewAllocation const *plot_area = gog_chart_view_get_plot_area (view->parent);
1189 
1190 	return gog_renderer_in_grip (x, y,
1191 				     plot_area->x + plot_area->w,
1192 				     plot_area->y + plot_area->h);
1193 }
1194 
1195 static void
gog_tool_resize_plot_area_render(GogView * view)1196 gog_tool_resize_plot_area_render (GogView *view)
1197 {
1198 	GogViewAllocation const *plot_area = gog_chart_view_get_plot_area (view->parent);
1199 
1200 	gog_renderer_draw_grip (view->renderer,
1201 				plot_area->x + plot_area->w,
1202 				plot_area->y + plot_area->h);
1203 }
1204 
1205 static void
gog_tool_resize_plot_area_move(GogToolAction * action,double x,double y)1206 gog_tool_resize_plot_area_move (GogToolAction *action, double x, double y)
1207 {
1208 	GogViewAllocation plot_area;
1209 	MovePlotAreaData *data = action->data;
1210 
1211 	plot_area.x = data->start_position.x;
1212 	plot_area.y = data->start_position.y;
1213 	plot_area.w = data->start_position.w + (x - action->start_x) / data->chart_allocation.w;
1214 	if (plot_area.w + plot_area.x > 1.0)
1215 		plot_area.w = 1.0 - plot_area.x;
1216 	else if (plot_area.w < 0.0)
1217 		plot_area.w = 0.0;
1218 	plot_area.h = data->start_position.h + (y - action->start_y) / data->chart_allocation.h;
1219 	if (plot_area.h + plot_area.y > 1.0)
1220 		plot_area.h = 1.0 - plot_area.y;
1221 	else if (plot_area.h < 0.0)
1222 		plot_area.h = 0.0;
1223 	gog_chart_set_plot_area (data->chart, &plot_area);
1224 }
1225 
1226 static GogTool gog_tool_resize_plot_area = {
1227 	N_("Resize plot area"),
1228 	GDK_BOTTOM_RIGHT_CORNER,
1229 	gog_tool_resize_plot_area_point,
1230 	gog_tool_resize_plot_area_render,
1231 	gog_tool_move_plot_area_init,
1232 	gog_tool_resize_plot_area_move,
1233 	NULL /* double-click */,
1234 	NULL /*destroy*/
1235 };
1236 #endif
1237 
1238 /*****************************************************************************/
1239 
1240 static GogViewClass *pview_parent_klass;
1241 
1242 static void
gog_plot_view_build_toolkit(GogView * view)1243 gog_plot_view_build_toolkit (GogView *view)
1244 {
1245 #ifdef GOFFICE_WITH_GTK
1246 	view->toolkit = g_slist_prepend (view->toolkit, &gog_tool_move_plot_area);
1247 	view->toolkit = g_slist_prepend (view->toolkit, &gog_tool_resize_plot_area);
1248 #endif
1249 }
1250 
1251 static void
gog_plot_view_class_init(GogPlotViewClass * pview_klass)1252 gog_plot_view_class_init (GogPlotViewClass *pview_klass)
1253 {
1254 	GogViewClass *view_klass = (GogViewClass *) pview_klass;
1255 
1256 	pview_parent_klass = g_type_class_peek_parent (pview_klass);
1257 
1258 	view_klass->build_toolkit 	= gog_plot_view_build_toolkit;
1259 }
1260 
GSF_CLASS_ABSTRACT(GogPlotView,gog_plot_view,gog_plot_view_class_init,NULL,GOG_TYPE_VIEW)1261 GSF_CLASS_ABSTRACT (GogPlotView, gog_plot_view,
1262 		    gog_plot_view_class_init, NULL,
1263 		    GOG_TYPE_VIEW)
1264 
1265 
1266 /**
1267  * gog_plot_view_get_data_at_point:
1268  * @view: #GogPlotView
1269  * @x: x position
1270  * @y: y position
1271  * @series: where to store the series
1272  *
1273  * Search a data represented at (@x,@y) in @view and set @series on success.
1274  *
1275  * return value: index of the found data in @series or -1.
1276  **/
1277 int
1278 gog_plot_view_get_data_at_point (GogPlotView *view, double x, double y, GogSeries **series)
1279 {
1280 	GogPlotViewClass *klass = GOG_PLOT_VIEW_GET_CLASS (view);
1281 	g_return_val_if_fail (klass != NULL, -1);
1282 	return (klass->get_data_at_point)? klass->get_data_at_point (view, x, y, series): -1;
1283 }
1284