1 /*
2  * go-graph-data.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/goffice.h>
24 
25 #include <gsf/gsf-impl-utils.h>
26 #include <glib/gi18n-lib.h>
27 
28 #include <string.h>
29 
30 /**
31  * SECTION: gog-series
32  * @short_description: A single data series.
33  *
34  * A #GogSeries represents a data series that can be added to a #GogPlot.
35  */
36 
37 /**
38  * GogSeriesFillType:
39  * @GOG_SERIES_FILL_TYPE_Y_ORIGIN: from origin ox the X-axis.
40  * @GOG_SERIES_FILL_TYPE_X_ORIGIN: from the origin of the Y-axis
41  * @GOG_SERIES_FILL_TYPE_BOTTOM: from the chart bottom.
42  * @GOG_SERIES_FILL_TYPE_LEFT: from the chart left side.
43  * @GOG_SERIES_FILL_TYPE_TOP: from the chart top.
44  * @GOG_SERIES_FILL_TYPE_RIGHT:, from the chart right side.
45  * @GOG_SERIES_FILL_TYPE_ORIGIN: from the origin (for radar and polar plots).
46  * @GOG_SERIES_FILL_TYPE_CENTER: from the center (for radar and polar plots).
47  * @GOG_SERIES_FILL_TYPE_EDGE: from the edge (for radar and polar plots).
48  * @GOG_SERIES_FILL_TYPE_SELF: close the series curve and fills it.
49  * @GOG_SERIES_FILL_TYPE_NEXT: from next series.
50  * @GOG_SERIES_FILL_TYPE_X_AXIS_MIN: from X-axis minimum.
51  * @GOG_SERIES_FILL_TYPE_X_AXIS_MAX: from X-axis maximum.
52  * @GOG_SERIES_FILL_TYPE_Y_AXIS_MIN: from Y-axis minimum.
53  * @GOG_SERIES_FILL_TYPE_Y_AXIS_MAX: from Y-axis maximum.
54  * @GOG_SERIES_FILL_TYPE_INVALID: invalid, should not happen.
55  **/
56 
57 /**
58  * GogSeriesClass:
59  * @base: base class
60  * @has_interpolation: supports interpolation.
61  * @has_fill_type: %TRUE if filling is supported.
62  * @valid_fill_type_list: list of supported #GogSeriesFillType values.
63  * @series_element_type: #GType for the series element if supported.
64  * @dim_changed: called when data changed for the series.
65  * @get_xy_data: get X and Y data.
66  * @get_interpolation_params: get interpolation parameters if any, only applies
67  * to constrained cubic spline interpolation.
68  **/
69 
70 /**
71  * GogSeriesElementClass:
72  * @base: base class
73  * @gse_populate_editor: populates editor.
74  **/
75 
76 /**
77  * GogSeriesPriority:
78  * @GOG_SERIES_REQUIRED: it must be there.
79  * @GOG_SERIES_SUGGESTED: allocator will fill it in, but use need not.
80  * @GOG_SERIES_OPTIONAL: optional data.
81  * @GOG_SERIES_ERRORS: optional data for error bars.
82  *
83  * Applies to data declarations in #GogSeriesDimDesc.
84  **/
85 
86 /**
87  * GogSeriesDesc:
88  * @dim: dimensions descriptions.
89  **/
90 
91 /**
92  * GogSeriesDimDesc:
93  * @name: name.
94  * @priority: priority.
95  * @is_shared: whether the dimension is shared among the series.
96  * @val_type: data type.
97  * @ms_type: data type for foreign formats.
98  **/
99 
100 /* Keep in sync with GogSeriesFillType enum */
101 static struct {
102 	GogSeriesFillType  type;
103 	char const 	  *name;
104 	char const 	  *label;
105 } _fill_type_infos[] = {
106 	{GOG_SERIES_FILL_TYPE_Y_ORIGIN,	"y-origin",	N_("Y origin")},
107 	{GOG_SERIES_FILL_TYPE_X_ORIGIN,	"x-origin",	N_("X origin")},
108 	{GOG_SERIES_FILL_TYPE_BOTTOM,	"bottom",	N_("Bottom")},
109 	{GOG_SERIES_FILL_TYPE_LEFT,	"left",		N_("Left")},
110 	{GOG_SERIES_FILL_TYPE_TOP,	"top",		N_("Top")},
111 	{GOG_SERIES_FILL_TYPE_RIGHT,	"right",	N_("Right")},
112 	{GOG_SERIES_FILL_TYPE_ORIGIN,	"origin",	N_("Origin")},
113 	{GOG_SERIES_FILL_TYPE_CENTER,	"center",	N_("Center")},
114 	{GOG_SERIES_FILL_TYPE_EDGE,	"edge",		N_("Edge")},
115 	{GOG_SERIES_FILL_TYPE_SELF,	"self",		N_("Self")},
116 	{GOG_SERIES_FILL_TYPE_NEXT,	"next",		N_("Next series")},
117 	{GOG_SERIES_FILL_TYPE_X_AXIS_MIN,	"x-axis-min",	N_("X axis minimum")},
118 	{GOG_SERIES_FILL_TYPE_X_AXIS_MAX,	"x-axis-max",	N_("X axis maximum")},
119 	{GOG_SERIES_FILL_TYPE_Y_AXIS_MIN,	"y-axis-min",	N_("Y axis minimum")},
120 	{GOG_SERIES_FILL_TYPE_Y_AXIS_MAX,	"y-axis-max",	N_("Y axis maximum")},
121 	{GOG_SERIES_FILL_TYPE_INVALID,	"invalid",	""}
122 };
123 
124 #ifdef GOFFICE_WITH_GTK
125 #endif
126 
127 int gog_series_get_valid_element_index (GogSeries const *series, int old_index, int desired_index);
128 
129 /*****************************************************************************/
130 static GObjectClass *gse_parent_klass;
131 
132 enum {
133 	ELEMENT_PROP_0,
134 	ELEMENT_INDEX
135 };
136 
element_compare(GogSeriesElement * gse_a,GogSeriesElement * gse_b)137 static gint element_compare (GogSeriesElement *gse_a, GogSeriesElement *gse_b)
138 {
139 	return gse_a->index - gse_b->index;
140 }
141 
142 static void
gog_series_element_set_index(GogSeriesElement * gse,int ind)143 gog_series_element_set_index (GogSeriesElement *gse, int ind)
144 {
145 	gse->index = ind;
146 	go_styled_object_apply_theme (GO_STYLED_OBJECT (gse), gse->base.style);
147 	go_styled_object_style_changed (GO_STYLED_OBJECT (gse));
148 }
149 
150 static void
gog_series_element_set_property(GObject * obj,guint param_id,GValue const * value,GParamSpec * pspec)151 gog_series_element_set_property (GObject *obj, guint param_id,
152 				 GValue const *value, GParamSpec *pspec)
153 {
154 	GogSeriesElement *gse = GOG_SERIES_ELEMENT (obj);
155 	GogObject *gobj = GOG_OBJECT (obj);
156 
157 	switch (param_id) {
158 	case ELEMENT_INDEX :
159 		gog_series_element_set_index (gse, g_value_get_int (value));
160 		if (gobj->parent != NULL) {
161 			GogSeries *series = GOG_SERIES (gobj->parent);
162 			series->overrides = g_list_remove (series->overrides, gse);
163 			series->overrides = g_list_insert_sorted (series->overrides, gse,
164 				(GCompareFunc) element_compare);
165 		}
166 		break;
167 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
168 		 return; /* NOTE : RETURN */
169 	}
170 
171 	gog_object_emit_changed (GOG_OBJECT (obj), FALSE);
172 }
173 
174 static void
gog_series_element_get_property(GObject * obj,guint param_id,GValue * value,GParamSpec * pspec)175 gog_series_element_get_property (GObject *obj, guint param_id,
176 				 GValue *value, GParamSpec *pspec)
177 {
178 	GogSeriesElement *gse = GOG_SERIES_ELEMENT (obj);
179 
180 	switch (param_id) {
181 	case ELEMENT_INDEX :
182 		g_value_set_int (value, gse->index);
183 		break;
184 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
185 		 break;
186 	}
187 }
188 
189 #ifdef GOFFICE_WITH_GTK
190 static void
cb_index_changed(GtkSpinButton * spin_button,GogSeriesElement * element)191 cb_index_changed (GtkSpinButton *spin_button, GogSeriesElement *element)
192 {
193 	int index;
194 	int value = gtk_spin_button_get_value (spin_button);
195 
196 	if ((int) element->index == value)
197 		return;
198 
199 	index = gog_series_get_valid_element_index (
200 		GOG_SERIES (gog_object_get_parent (GOG_OBJECT (element))),
201 		element->index, value);
202 
203 	if (index != value)
204 		gtk_spin_button_set_value (spin_button, index);
205 
206 	g_object_set (element, "index", (int) index, NULL);
207 }
208 
209 static void
gog_series_element_populate_editor(GogObject * gobj,GOEditor * editor,GogDataAllocator * dalloc,GOCmdContext * cc)210 gog_series_element_populate_editor (GogObject *gobj,
211 				    GOEditor *editor,
212 			   GogDataAllocator *dalloc,
213 			   GOCmdContext *cc)
214 {
215 	static guint series_element_pref_page = 1;
216 	GtkWidget *w, *gse_grid = NULL, *grid;
217 	GogSeriesElementClass *klass = GOG_SERIES_ELEMENT_GET_CLASS (gobj);
218 
219 	if (klass->gse_populate_editor)
220 		gse_grid = (*klass->gse_populate_editor) (gobj, editor, cc);
221 
222 	(GOG_OBJECT_CLASS(gse_parent_klass)->populate_editor) (gobj, editor, dalloc, cc);
223 
224 	if (gse_grid == NULL) {
225 		grid = gtk_grid_new ();
226 		gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
227 		gtk_grid_set_column_spacing (GTK_GRID (grid), 12);
228 		gtk_container_set_border_width (GTK_CONTAINER (grid), 12);
229 	} else {
230 		grid = gse_grid;
231 		gtk_grid_insert_row (GTK_GRID (grid), 0);
232 	}
233 	w = gtk_label_new (_("Index:"));
234 	g_object_set (G_OBJECT (w), "xalign", 0., NULL);
235 	gtk_grid_attach (GTK_GRID (grid), w, 0, 0, 1, 1);
236 	w = gtk_spin_button_new_with_range (0, G_MAXINT, 1);
237 	gtk_spin_button_set_value (GTK_SPIN_BUTTON (w),
238 				   GOG_SERIES_ELEMENT(gobj)->index);
239 	g_signal_connect (G_OBJECT (w),
240 			  "value_changed",
241 			  G_CALLBACK (cb_index_changed), gobj);
242 	gtk_grid_attach (GTK_GRID (grid), w, 1, 0, 1, 1);
243 	gtk_widget_show_all (grid);
244 
245 	if (gse_grid == NULL)
246 		go_editor_add_page (editor, grid, _("Settings"));
247 
248 	go_editor_set_store_page (editor, &series_element_pref_page);
249 }
250 #endif
251 
252 static void
gog_series_element_init_style(GogStyledObject * gso,GOStyle * style)253 gog_series_element_init_style (GogStyledObject *gso, GOStyle *style)
254 {
255 	GogSeries const *series = GOG_SERIES (GOG_OBJECT (gso)->parent);
256 	GOStyle *parent_style;
257 
258 	g_return_if_fail (series != NULL);
259 
260 	parent_style = go_styled_object_get_style (GO_STYLED_OBJECT (series));
261 	style->interesting_fields = parent_style->interesting_fields;
262 	gog_theme_fillin_style (gog_object_get_theme (GOG_OBJECT (gso)),
263 		style, GOG_OBJECT (gso), GOG_SERIES_ELEMENT (gso)->index,
264 	        style->interesting_fields);
265 }
266 
267 static void
gog_series_element_class_init(GogSeriesElementClass * klass)268 gog_series_element_class_init (GogSeriesElementClass *klass)
269 {
270 	GObjectClass *gobject_klass = (GObjectClass *) klass;
271 	GogObjectClass *gog_klass = (GogObjectClass *) klass;
272 	GogStyledObjectClass *style_klass = (GogStyledObjectClass *) klass;
273 	gse_parent_klass = g_type_class_peek_parent (klass);
274 
275 	gobject_klass->set_property = gog_series_element_set_property;
276 	gobject_klass->get_property = gog_series_element_get_property;
277 
278 #ifdef GOFFICE_WITH_GTK
279 	gog_klass->populate_editor 	= gog_series_element_populate_editor;
280 #endif
281 	style_klass->init_style	    	= gog_series_element_init_style;
282 
283 	gog_klass->use_parent_as_proxy  = TRUE;
284 
285 	g_object_class_install_property (gobject_klass, ELEMENT_INDEX,
286 		g_param_spec_int ("index",
287 			_("Index"),
288 			_("Index of the corresponding data element"),
289 			0, G_MAXINT, 0,
290 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT | GOG_PARAM_FORCE_SAVE));
291 }
292 
GSF_CLASS_ABSTRACT(GogSeriesElement,gog_series_element,gog_series_element_class_init,NULL,GOG_TYPE_STYLED_OBJECT)293 GSF_CLASS_ABSTRACT (GogSeriesElement, gog_series_element,
294 	   gog_series_element_class_init, NULL /*gog_series_element_init*/,
295 	   GOG_TYPE_STYLED_OBJECT)
296 
297 /*****************************************************************************/
298 
299 static gboolean
300 regression_curve_can_add (GogObject const *parent)
301 {
302 	GogSeries *series = GOG_SERIES (parent);
303 	return (series->acceptable_children & GOG_SERIES_ACCEPT_TREND_LINE) != 0;
304 }
305 
306 static void
regression_curve_post_add(GogObject * parent,GogObject * child)307 regression_curve_post_add (GogObject *parent, GogObject *child)
308 {
309 	gog_object_request_update (child);
310 }
311 
312 static void
regression_curve_pre_remove(GogObject * parent,GogObject * child)313 regression_curve_pre_remove (GogObject *parent, GogObject *child)
314 {
315 }
316 
317 /*****************************************************************************/
318 
319 static GObjectClass *series_parent_klass;
320 
321 enum {
322 	SERIES_PROP_0,
323 	SERIES_PROP_HAS_LEGEND,
324 	SERIES_PROP_INTERPOLATION,
325 	SERIES_PROP_INTERPOLATION_SKIP_INVALID,
326 	SERIES_PROP_FILL_TYPE
327 };
328 
329 static gboolean
role_series_element_can_add(GogObject const * parent)330 role_series_element_can_add (GogObject const *parent)
331 {
332 	GogSeriesClass *klass = GOG_SERIES_GET_CLASS (parent);
333 
334 	return ((gog_series_get_valid_element_index(GOG_SERIES (parent), -1, 0) >= 0) &&
335 		(klass->series_element_type > 0));
336 }
337 
338 static GogObject *
role_series_element_allocate(GogObject * series)339 role_series_element_allocate (GogObject *series)
340 {
341 	GogSeriesClass *klass = GOG_SERIES_GET_CLASS (series);
342 	GType type = klass->series_element_type;
343 	GogObject *gse;
344 
345 	if (type == 0)
346 		return NULL;
347 
348 	gse = g_object_new (type, NULL);
349 	if (gse != NULL)
350 		gog_series_element_set_index (GOG_SERIES_ELEMENT (gse),
351 			gog_series_get_valid_element_index (GOG_SERIES (series), -1, 0));
352 	return gse;
353 }
354 
355 static void
role_series_element_post_add(GogObject * parent,GogObject * child)356 role_series_element_post_add (GogObject *parent, GogObject *child)
357 {
358 	GogSeries *series = GOG_SERIES (parent);
359 	go_styled_object_set_style (GO_STYLED_OBJECT (child),
360 		go_styled_object_get_style (GO_STYLED_OBJECT (parent)));
361 	series->overrides = g_list_insert_sorted (series->overrides, child,
362 		(GCompareFunc) element_compare);
363 }
364 
365 static void
role_series_element_pre_remove(GogObject * parent,GogObject * child)366 role_series_element_pre_remove (GogObject *parent, GogObject *child)
367 {
368 	GogSeries *series = GOG_SERIES (parent);
369 	series->overrides = g_list_remove (series->overrides, child);
370 }
371 
372 static gboolean
role_series_labels_can_add(GogObject const * parent)373 role_series_labels_can_add (GogObject const *parent)
374 {
375 	GogSeries *series = GOG_SERIES (parent);
376 
377 	return (series->allowed_pos != 0 && gog_object_get_child_by_name (parent, "Data labels") == NULL);
378 }
379 
380 static void
role_series_labels_post_add(GogObject * parent,GogObject * child)381 role_series_labels_post_add (GogObject *parent, GogObject *child)
382 {
383 	GogSeries *series = GOG_SERIES (parent);
384 	GogSeriesDesc const *desc;
385 	unsigned i;
386 	GogSeriesLabels *labels = GOG_SERIES_LABELS (child);
387 	gog_series_labels_set_allowed_position (labels, series->allowed_pos);
388 	gog_series_labels_set_default_position (labels, series->default_pos);
389 	/* default is to show values */
390 
391 	/* Are there any shared dimensions */
392 	desc = &series->plot->desc.series;
393 	for (i = 0; i < desc->num_dim; i++)
394 		if (desc->dim[i].ms_type == GOG_MS_DIM_VALUES)
395 			break;
396 	labels->format = (i != desc->num_dim)?
397 		                            g_strdup_printf ("%%%u", i):
398 		                            g_strdup ("");
399 	labels->supports_percent = GOG_PLOT_GET_CLASS (series->plot)->get_percent != NULL;
400 }
401 
402 static void
gog_series_finalize(GObject * obj)403 gog_series_finalize (GObject *obj)
404 {
405 	GogSeries *series = GOG_SERIES (obj);
406 
407 	if (series->values != NULL) {
408 		gog_dataset_finalize (GOG_DATASET (obj));
409 		g_free (series->values - 1); /* it was aliased */
410 		series->values = NULL;
411 	}
412 
413 	g_list_free (series->overrides);
414 
415 	(*series_parent_klass->finalize) (obj);
416 }
417 
418 static void
gog_series_set_property(GObject * obj,guint param_id,GValue const * value,GParamSpec * pspec)419 gog_series_set_property (GObject *obj, guint param_id,
420 			 GValue const *value, GParamSpec *pspec)
421 {
422 	GogSeries *series = GOG_SERIES (obj);
423 	gboolean b_tmp;
424 	char const *name;
425 	unsigned int i;
426 
427 	switch (param_id) {
428 	case SERIES_PROP_HAS_LEGEND :
429 		b_tmp = g_value_get_boolean (value);
430 		if (series->has_legend ^ b_tmp) {
431 			series->has_legend = b_tmp;
432 			if (series->plot != NULL)
433 				gog_plot_request_cardinality_update (series->plot);
434 		}
435 		break;
436 	case SERIES_PROP_INTERPOLATION:
437 		series->interpolation = go_line_interpolation_from_str (g_value_get_string (value));
438 		/* check if the interpolation mode is compatible with the plot type ans
439 		 * replace if not */
440 		if (gog_plot_axis_set_pref (gog_series_get_plot (series)) & 1 << GOG_AXIS_RADIAL &&
441 		    !go_line_interpolation_supports_radial (series->interpolation))
442 			series->interpolation = GO_LINE_INTERPOLATION_SPLINE;
443 		else if (g_object_get_data (obj, "no-bezier-interpolation") != NULL &&
444 		         (series->interpolation == GO_LINE_INTERPOLATION_CLOSED_SPLINE ||
445 		          series->interpolation == GO_LINE_INTERPOLATION_SPLINE ||
446 		          series->interpolation == GO_LINE_INTERPOLATION_ODF_SPLINE))
447 			series->interpolation = GO_LINE_INTERPOLATION_CUBIC_SPLINE;
448 		break;
449 	case SERIES_PROP_INTERPOLATION_SKIP_INVALID:
450 		series->interpolation_skip_invalid = g_value_get_boolean (value);
451 		break;
452 	case SERIES_PROP_FILL_TYPE:
453 		name = g_value_get_string (value);
454 		for (i = 0; i < G_N_ELEMENTS (_fill_type_infos); i++)
455 			if (strcmp (_fill_type_infos[i].name, name) == 0)
456 			       series->fill_type = _fill_type_infos[i].type;
457 		break;
458 
459 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
460 		 return; /* NOTE : RETURN */
461 	}
462 
463 	gog_object_emit_changed (GOG_OBJECT (obj), FALSE);
464 }
465 
466 static void
gog_series_get_property(GObject * obj,guint param_id,GValue * value,GParamSpec * pspec)467 gog_series_get_property (GObject *obj, guint param_id,
468 			 GValue *value, GParamSpec *pspec)
469 {
470 	GogSeries *series = GOG_SERIES (obj);
471 
472 	switch (param_id) {
473 	case SERIES_PROP_HAS_LEGEND :
474 		g_value_set_boolean (value, series->has_legend);
475 		break;
476 	case SERIES_PROP_INTERPOLATION:
477 		g_value_set_string (value, go_line_interpolation_as_str (series->interpolation));
478 		break;
479 	case SERIES_PROP_INTERPOLATION_SKIP_INVALID:
480 		g_value_set_boolean (value, series->interpolation_skip_invalid);
481 		break;
482 	case SERIES_PROP_FILL_TYPE:
483 		g_value_set_string (value, _fill_type_infos[series->fill_type].name);
484 		break;
485 
486 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
487 		 break;
488 	}
489 }
490 
491 #ifdef GOFFICE_WITH_GTK
492 static unsigned
make_dim_editor(GtkGrid * grid,unsigned row,GogDataEditor * deditor,char const * name,GogSeriesPriority priority,gboolean is_shared)493 make_dim_editor (GtkGrid *grid, unsigned row, GogDataEditor *deditor,
494 		 char const *name, GogSeriesPriority priority, gboolean is_shared)
495 {
496 	GtkWidget *editor = GTK_WIDGET (deditor);
497 	char *txt = g_strdup_printf (
498 		((priority != GOG_SERIES_REQUIRED) ? "(_%s):" : "_%s:"), _(name));
499 	GtkWidget *label = gtk_label_new_with_mnemonic (txt);
500 	g_free (txt);
501 
502 	gtk_grid_attach (grid, label,
503 		0, row, 1, 1);
504 	gtk_widget_set_hexpand (editor, TRUE);
505 	gtk_grid_attach (grid, editor,
506 		1, row, 1, 1);
507 	gtk_label_set_mnemonic_widget (GTK_LABEL (label), editor);
508 	gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
509 
510 	go_atk_setup_label (label, editor);
511 
512 	return row + 1;
513 }
514 
515 static void
cb_show_in_legend(GtkToggleButton * b,GObject * series)516 cb_show_in_legend (GtkToggleButton *b, GObject *series)
517 {
518 	g_object_set (series,
519 		"has-legend", gtk_toggle_button_get_active (b),
520 		NULL);
521 }
522 
523 static void
cb_line_interpolation_changed(GtkComboBox * box,GogSeries * series)524 cb_line_interpolation_changed (GtkComboBox *box, GogSeries *series)
525 {
526 	GtkBuilder *gui = g_object_get_data (G_OBJECT (box), "gui");
527 	GtkWidget *widget = GTK_WIDGET (g_object_get_data (G_OBJECT(box), "skip-button"));
528 	GtkWidget *grid = go_gtk_builder_get_widget (gui, "clamps-grid");
529 	GtkTreeIter iter;
530 	gtk_combo_box_get_active_iter (box, &iter);
531 	gtk_tree_model_get (gtk_combo_box_get_model (box), &iter, 1, &series->interpolation, -1);
532 	gtk_widget_set_sensitive (widget, !go_line_interpolation_auto_skip (series->interpolation));
533 	widget = GTK_WIDGET (g_object_get_data (G_OBJECT(box), "fill-type"));
534 	if (series->interpolation == GO_LINE_INTERPOLATION_CLAMPED_CUBIC_SPLINE)
535 		gtk_widget_show (grid);
536 	else
537 		gtk_widget_hide (grid);
538 	if (widget)
539 		gtk_widget_set_sensitive (widget, !go_line_interpolation_auto_skip (series->interpolation));
540 	gog_object_emit_changed (GOG_OBJECT (series), FALSE);
541 }
542 
543 static void
cb_line_interpolation_skip_changed(GtkToggleButton * btn,GogSeries * series)544 cb_line_interpolation_skip_changed (GtkToggleButton *btn, GogSeries *series)
545 {
546 	series->interpolation_skip_invalid = gtk_toggle_button_get_active (btn);
547 	gog_object_emit_changed (GOG_OBJECT (series), FALSE);
548 }
549 
550 static void
cb_fill_type_changed(GtkComboBox * combo,GObject * obj)551 cb_fill_type_changed (GtkComboBox *combo, GObject *obj)
552 {
553 	gog_series_set_fill_type (GOG_SERIES (obj),
554 				  gog_series_get_fill_type_from_combo (GOG_SERIES (obj), combo));
555 }
556 
557 static void
gog_series_populate_editor(GogObject * gobj,GOEditor * editor,GogDataAllocator * dalloc,GOCmdContext * cc)558 gog_series_populate_editor (GogObject *gobj,
559 			    GOEditor *editor,
560 		   GogDataAllocator *dalloc,
561 		   GOCmdContext *cc)
562 {
563 	static guint series_pref_page = 1;
564 	GtkWidget *w;
565 	GtkGrid  *grid;
566 	unsigned i, row = 0;
567 	gboolean has_shared = FALSE;
568 	GogSeries *series = GOG_SERIES (gobj);
569 	GogSeriesClass *series_class = GOG_SERIES_GET_CLASS (series);
570 	GogDataset *set = GOG_DATASET (gobj);
571 	GogSeriesDesc const *desc;
572 	GogDataType data_type;
573 	GtkComboBox *combo = NULL;
574 
575 	g_return_if_fail (series->plot != NULL);
576 
577 	/* Are there any shared dimensions */
578 	desc = &series->plot->desc.series;
579 	for (i = 0; i < desc->num_dim; i++)
580 		if (desc->dim[i].is_shared) {
581 			has_shared = TRUE;
582 			break;
583 		}
584 
585 	w = gtk_grid_new ();
586 	grid = GTK_GRID (w);
587 	gtk_grid_set_row_spacing (grid, 6);
588 	gtk_grid_set_column_spacing (grid, 12);
589 	gtk_container_set_border_width (GTK_CONTAINER (grid), 12);
590 
591 	row = make_dim_editor (grid, row,
592 		gog_data_allocator_editor (dalloc, set, -1, GOG_DATA_SCALAR),
593 		N_("Name"), TRUE, FALSE);
594 
595 	/* first the unshared entries */
596 	for (i = 0; i < desc->num_dim; i++) {
597 		data_type = (desc->dim[i].val_type == GOG_DIM_MATRIX)?
598 				GOG_DATA_MATRIX: GOG_DATA_VECTOR;
599 		if (!desc->dim[i].is_shared && (desc->dim[i].priority != GOG_SERIES_ERRORS))
600 			row = make_dim_editor (grid, row,
601 				gog_data_allocator_editor (dalloc, set, i, data_type),
602 				desc->dim[i].name, desc->dim[i].priority, FALSE);
603 	}
604 
605 	if (has_shared) {
606 		gtk_grid_attach (grid, gtk_separator_new (GTK_ORIENTATION_HORIZONTAL),
607 			0, row, 2, 1);
608 		row++;
609 	}
610 
611 	/* then the shared entries */
612 	for (i = 0; i < desc->num_dim; i++) {
613 		data_type = (desc->dim[i].val_type == GOG_DIM_MATRIX)?
614 				GOG_DATA_MATRIX: GOG_DATA_VECTOR;
615 		if (desc->dim[i].is_shared)
616 			row = make_dim_editor (grid, row,
617 				gog_data_allocator_editor (dalloc, set, i, data_type),
618 				desc->dim[i].name, desc->dim[i].priority, TRUE);
619 	}
620 
621 	gtk_grid_attach (grid, gtk_separator_new (GTK_ORIENTATION_HORIZONTAL),
622 		0, row, 2, 1);
623 	row++;
624 	w = gtk_check_button_new_with_mnemonic (_("_Show in Legend"));
625 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w),
626 		gog_series_has_legend (series));
627 	g_signal_connect (G_OBJECT (w),
628 		"toggled",
629 		G_CALLBACK (cb_show_in_legend), series);
630 	gtk_grid_attach (grid, w,
631 		0, row, 2, 1);
632 	gtk_widget_show_all (GTK_WIDGET (grid));
633 
634 	go_editor_add_page (editor, GTK_WIDGET (grid), _("Data"));
635 
636 	(GOG_OBJECT_CLASS(series_parent_klass)->populate_editor) (gobj, editor, dalloc, cc);
637 
638 	grid = GTK_GRID (go_editor_get_registered_widget (editor, "line-grid"));
639 	if (grid == NULL)
640 		grid = GTK_GRID (go_editor_get_registered_widget (editor, "outline-grid"));
641 	if (series_class->has_interpolation && grid != NULL) {
642 		GtkBuilder *gui;
643 		GtkWidget *widget;
644 
645 		gui = go_gtk_builder_load_internal ("res:go:graph/gog-series-prefs.ui", GETTEXT_PACKAGE, cc);
646 		if (gui != NULL) {
647 			unsigned i;
648 			GogAxisSet set = gog_plot_axis_set_pref (gog_series_get_plot (series));
649 			GogDataset *clamp_set = gog_series_get_interpolation_params (series);
650 			GtkListStore *model;
651 			GtkTreeIter iter;
652 			GtkCellRenderer *renderer;
653 			/* FIXME: change when we can filter out non supported modes, see #698100, comment #3 */
654 			gboolean no_bezier = g_object_get_data (G_OBJECT (gobj), "no-bezier-interpolation") != NULL;
655 			widget = go_gtk_builder_get_widget (gui, "interpolation-prefs");
656 			gtk_grid_attach (grid, widget, 0, 3, 4, 1);
657 			/* create an interpolation type combo and populate it */
658 			combo = GTK_COMBO_BOX (gtk_builder_get_object (gui, "interp-combo"));
659 			renderer = gtk_cell_renderer_text_new ();
660 			gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, FALSE);
661 			gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer,
662 					"text", 0,
663 					NULL);
664 			model = GTK_LIST_STORE (gtk_combo_box_get_model (combo));
665 			for (i = 0; i < GO_LINE_INTERPOLATION_MAX; i++) {
666 				if ((set & 1 << GOG_AXIS_RADIAL && !go_line_interpolation_supports_radial (i)) ||
667 				    (no_bezier &&
668 					    (i == GO_LINE_INTERPOLATION_CLOSED_SPLINE ||
669 					     i == GO_LINE_INTERPOLATION_SPLINE ||
670 					     i == GO_LINE_INTERPOLATION_ODF_SPLINE)))
671 					continue;
672 				gtk_list_store_append (model, &iter);
673 				gtk_list_store_set (model, &iter,
674 				                    0, _(go_line_interpolation_as_label (i)),
675 				                    1, i, -1);
676 				if (i == series->interpolation)
677 					gtk_combo_box_set_active_iter (combo, &iter);
678 			}
679 			g_signal_connect (combo, "changed",
680 					  G_CALLBACK (cb_line_interpolation_changed), series);
681 			w = go_gtk_builder_get_widget (gui, "interpolation-skip-invalid");
682 			g_object_set_data (G_OBJECT (combo), "skip-button", w);
683 			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), series->interpolation_skip_invalid);
684 			if (go_line_interpolation_auto_skip (series->interpolation))
685 				gtk_widget_set_sensitive (w, FALSE);
686 			g_signal_connect (w, "toggled",
687 					  G_CALLBACK (cb_line_interpolation_skip_changed), series);
688 			gtk_widget_show_all (widget);
689 			/* FIXME: a very bad hack to hide this check box for line and area plots */
690 			if (no_bezier)
691 				gtk_widget_hide (w);
692 			if (clamp_set) {
693 				GtkWidget *w;
694 				widget = go_gtk_builder_get_widget (gui, "clamps-grid");
695 				w = GTK_WIDGET (gog_data_allocator_editor (dalloc, clamp_set, 0, GOG_DATA_SCALAR));
696 				gtk_widget_set_tooltip_text (w, _("Derivative at first point of the clamped cubic spline."));
697 				gtk_widget_show (w);
698 				gtk_widget_set_hexpand (w, TRUE);
699 				gtk_grid_attach (GTK_GRID (widget), w, 1, 0, 2, 1);
700 				w = GTK_WIDGET (gog_data_allocator_editor (dalloc, clamp_set, 1, GOG_DATA_SCALAR));
701 				gtk_widget_set_tooltip_text (w, _("Derivative at last point of the clamped cubic spline."));
702 				gtk_widget_show (w);
703 				gtk_widget_set_hexpand (w, TRUE);
704 				gtk_grid_attach (GTK_GRID (widget), w, 1, 1, 2, 1);
705 				if (series->interpolation != GO_LINE_INTERPOLATION_CLAMPED_CUBIC_SPLINE)
706 					gtk_widget_hide (widget);
707 			}
708 			g_object_set_data (G_OBJECT (combo), "gui", gui);
709 			g_signal_connect_swapped (G_OBJECT (combo), "destroy", G_CALLBACK (g_object_unref), gui);
710 		}
711 	}
712 
713 	grid = GTK_GRID (go_editor_get_registered_widget (editor, "fill-grid"));
714 	if (series_class->has_fill_type && grid != NULL) {
715 		GtkBuilder *gui;
716 		GtkWidget *widget;
717 
718 		gui = go_gtk_builder_load_internal ("res:go:graph/gog-series-prefs.ui", GETTEXT_PACKAGE, cc);
719 		if (gui != NULL) {
720 			widget = go_gtk_builder_get_widget (gui, "fill_type_combo");
721 				gog_series_populate_fill_type_combo (GOG_SERIES (series), GTK_COMBO_BOX (widget));
722 			g_signal_connect (G_OBJECT (widget), "changed",
723 					  G_CALLBACK (cb_fill_type_changed), series);
724 			if (combo)
725 				g_object_set_data (G_OBJECT (combo), "fill-type", widget);
726 			if (series->interpolation == GO_LINE_INTERPOLATION_CLOSED_SPLINE)
727 				gtk_widget_set_sensitive (widget, FALSE);
728 			widget = go_gtk_builder_get_widget (gui, "fill_type_prefs");
729 			gtk_grid_attach (grid, widget, 0, 1, 3, 1);
730 			g_object_set_data (G_OBJECT (widget), "gui", gui);
731 
732 			g_signal_connect_swapped (G_OBJECT (widget), "destroy",
733 						  G_CALLBACK (g_object_unref), gui);
734 		}
735 	}
736 
737 	go_editor_set_store_page (editor, &series_pref_page);
738 }
739 #endif
740 
741 static void
gog_series_update(GogObject * obj)742 gog_series_update (GogObject *obj)
743 {
744 	GogSeries *series = GOG_SERIES (obj);
745 	GogObjectRole const *role = gog_object_find_role_by_name (obj, "Data labels");
746 	GSList *l = gog_object_get_children (obj, role), *ptr;
747 	series->needs_recalc = FALSE;
748 	for (ptr = l; ptr; ptr = ptr->next)
749 		gog_object_request_update (ptr->data);
750 	g_slist_free (l);
751 }
752 
753 static void
gog_series_child_added(GogObject * parent,GogObject * child)754 gog_series_child_added (GogObject *parent, GogObject *child)
755 {
756 	if (GOG_IS_TREND_LINE (child))
757 		gog_plot_request_cardinality_update (GOG_SERIES (parent)->plot);
758 }
759 
760 static void
gog_series_init_style(GogStyledObject * gso,GOStyle * style)761 gog_series_init_style (GogStyledObject *gso, GOStyle *style)
762 {
763 	GogSeries const *series = (GogSeries const *)gso;
764 	style->interesting_fields = series->plot->desc.series.style_fields;
765 	gog_theme_fillin_style (gog_object_get_theme (GOG_OBJECT (gso)),
766 		style, GOG_OBJECT (gso), series->index,
767 	        style->interesting_fields);
768 }
769 
770 static void
gog_series_class_init(GogSeriesClass * klass)771 gog_series_class_init (GogSeriesClass *klass)
772 {
773 	static GogObjectRole const roles[] = {
774 		{ N_("Point"), "GogSeriesElement",	0,
775 		  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
776 		  role_series_element_can_add, NULL,
777 		  role_series_element_allocate,
778 		  role_series_element_post_add,
779 		  role_series_element_pre_remove, NULL },
780 		{ N_("Regression curve"), "GogRegCurve",	1,
781 		  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_TYPE,
782 		  regression_curve_can_add,
783 		  NULL,
784 		  NULL,
785 		  regression_curve_post_add,
786 		  regression_curve_pre_remove,
787 		  NULL },
788 		{ N_("Trend line"), "GogTrendLine",	2,
789 		  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_TYPE,
790 		  regression_curve_can_add,
791 		  NULL,
792 		  NULL,
793 		  regression_curve_post_add,
794 		  regression_curve_pre_remove,
795 		  NULL },
796 		{ N_("Data labels"), "GogSeriesLabels",	3,
797 		  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
798 		  role_series_labels_can_add,
799 		  NULL,
800 		  NULL,
801 		  role_series_labels_post_add,
802 		  NULL,
803 		  NULL }
804 	};
805 	unsigned int i;
806 
807 	GObjectClass *gobject_klass = (GObjectClass *) klass;
808 	GogObjectClass *gog_klass = (GogObjectClass *) klass;
809 	GogStyledObjectClass *style_klass = (GogStyledObjectClass *) klass;
810 
811 	series_parent_klass = g_type_class_peek_parent (klass);
812 	gobject_klass->finalize		= gog_series_finalize;
813 	gobject_klass->set_property	= gog_series_set_property;
814 	gobject_klass->get_property	= gog_series_get_property;
815 	klass->has_interpolation	= FALSE;
816 
817 #ifdef GOFFICE_WITH_GTK
818 	gog_klass->populate_editor	= gog_series_populate_editor;
819 #endif
820 	gog_klass->update		= gog_series_update;
821 	gog_klass->child_added		= gog_series_child_added;
822 	gog_klass->child_removed	= gog_series_child_added;
823 	style_klass->init_style 	= gog_series_init_style;
824 	/* series do not have views, so just forward signals from the plot */
825 	gog_klass->use_parent_as_proxy  = TRUE;
826 
827 	gog_object_register_roles (gog_klass, roles, G_N_ELEMENTS (roles));
828 
829 	g_object_class_install_property (gobject_klass, SERIES_PROP_HAS_LEGEND,
830 		g_param_spec_boolean ("has-legend",
831 			_("Has-legend"),
832 			_("Should the series show up in legends"),
833 			TRUE,
834 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
835         g_object_class_install_property (gobject_klass, SERIES_PROP_INTERPOLATION,
836 		 g_param_spec_string ("interpolation",
837 			_("Interpolation"),
838 			_("Type of line interpolation"),
839 			"linear",
840 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT | GOG_PARAM_FORCE_SAVE));
841 	g_object_class_install_property (gobject_klass, SERIES_PROP_INTERPOLATION_SKIP_INVALID,
842 		g_param_spec_boolean ("interpolation-skip-invalid",
843 			_("Interpolation skip invalid"),
844 			_("Should the series interpolation ignore the invalid data"),
845 			FALSE,
846 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
847 	g_object_class_install_property (gobject_klass, SERIES_PROP_FILL_TYPE,
848 		g_param_spec_string ("fill-type",
849 			_("Fill type"),
850 			_("How to fill the area"),
851 			"invalid",
852 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
853 
854 	klass->valid_fill_type_list = NULL;
855 
856 	/* Check for consistency between enum and infos */
857 	for (i = 0; i < G_N_ELEMENTS (_fill_type_infos); i++)
858 		g_assert (_fill_type_infos[i].type == i);
859 }
860 
861 static void
gog_series_init(GogSeries * series)862 gog_series_init (GogSeries *series)
863 {
864 	series->is_valid = FALSE;
865 	series->has_legend = TRUE;
866 	series->plot = NULL;
867 	series->values = NULL;
868 	series->index = -1;
869 	series->acceptable_children = 0;
870 	series->interpolation = GO_LINE_INTERPOLATION_LINEAR;
871 	series->default_pos = GOG_SERIES_LABELS_CENTERED;
872 }
873 
874 static void
gog_series_dataset_dims(GogDataset const * set,int * first,int * last)875 gog_series_dataset_dims (GogDataset const *set, int *first, int *last)
876 {
877 	GogSeries const *series = GOG_SERIES (set);
878 	*first = -1;
879 	if (series->plot == NULL || series->values == NULL)
880 		*last = -2;
881 	else
882 		*last = (series->plot->desc.series.num_dim - 1);
883 }
884 
885 static GogDatasetElement *
gog_series_dataset_get_elem(GogDataset const * set,int dim_i)886 gog_series_dataset_get_elem (GogDataset const *set, int dim_i)
887 {
888 	GogSeries const *series = GOG_SERIES (set);
889 
890 	g_return_val_if_fail (dim_i >= -1, NULL);
891 
892 	if (dim_i >= (int)series->plot->desc.series.num_dim)
893 		return NULL;
894 	return series->values + dim_i;
895 }
896 
897 static void
gog_series_dataset_set_dim(GogDataset * set,int dim_i,GOData * val,GError ** err)898 gog_series_dataset_set_dim (GogDataset *set, int dim_i,
899 			    GOData *val, GError **err)
900 {
901 	GogSeriesDesc const *desc;
902 	GogSeries *series = GOG_SERIES (set);
903 	GogGraph *graph = gog_object_get_graph (GOG_OBJECT (series));
904 
905 	g_return_if_fail (GOG_IS_PLOT (series->plot));
906 	g_return_if_fail (dim_i >= -1);
907 
908 	if (dim_i < 0) {
909 		char *name = NULL;
910 		if (NULL != series->values[-1].data)
911 			name = go_data_get_scalar_string (series->values[-1].data);
912 		gog_object_set_name (GOG_OBJECT (series), name, err);
913 		return;
914 	}
915 
916 	gog_series_check_validity (series);
917 
918 	/* clone shared dimensions into other series in the plot, and
919 	 * invalidate if necessary */
920 	desc = &series->plot->desc.series;
921 	g_return_if_fail (dim_i < (int) desc->num_dim);
922 
923 	if (desc->dim[dim_i].is_shared) {
924 		GSList *ptr = series->plot->series;
925 
926 		val = series->values[dim_i].data;
927 		for (; ptr != NULL ; ptr = ptr->next) {
928 			gog_dataset_set_dim_internal (GOG_DATASET (ptr->data),
929 				dim_i, val, graph);
930 			gog_series_check_validity (GOG_SERIES (ptr->data));
931 		}
932 	}
933 }
934 
935 static void
gog_series_dataset_dim_changed(GogDataset * set,int dim_i)936 gog_series_dataset_dim_changed (GogDataset *set, int dim_i)
937 {
938 	GogSeries *series = GOG_SERIES (set);
939 
940 	if (dim_i >= 0) {
941 		GogSeriesClass	*klass = GOG_SERIES_GET_CLASS (set);
942 		GogPlot *plot = GOG_PLOT (GOG_OBJECT (set)->parent);
943 		/* FIXME: we probaly need a signal which will be connected
944 		 * to axis and legend objects and let them check if resize
945 		 * is really needed (similar to child-name-changed
946 		 * connected to legend). For now, let resize for every label
947 		 * change */
948 		gboolean resize = plot != NULL ?
949 			plot->desc.series.dim[dim_i].val_type == GOG_DIM_LABEL :
950 			FALSE;
951 
952 		if (!series->needs_recalc) {
953 			series->needs_recalc = TRUE;
954 			gog_object_emit_changed (GOG_OBJECT (set), resize);
955 		}
956 		if (klass->dim_changed != NULL)
957 			(klass->dim_changed) (GOG_SERIES (set), dim_i);
958 
959 		gog_object_request_update (GOG_OBJECT (set));
960 	} else {
961 		GOData *name_src = series->values[-1].data;
962 		char *name = (name_src != NULL)
963 			? go_data_get_scalar_string (name_src) : NULL;
964 		gog_object_set_name (GOG_OBJECT (set), name, NULL);
965 	}
966 	gog_series_check_validity (series);
967 }
968 
969 static void
gog_series_dataset_init(GogDatasetClass * iface)970 gog_series_dataset_init (GogDatasetClass *iface)
971 {
972 	iface->get_elem	   = gog_series_dataset_get_elem;
973 	iface->set_dim	   = gog_series_dataset_set_dim;
974 	iface->dims	   = gog_series_dataset_dims;
975 	iface->dim_changed = gog_series_dataset_dim_changed;
976 }
977 
GSF_CLASS_FULL(GogSeries,gog_series,NULL,NULL,gog_series_class_init,NULL,gog_series_init,GOG_TYPE_STYLED_OBJECT,G_TYPE_FLAG_ABSTRACT,GSF_INTERFACE (gog_series_dataset_init,GOG_TYPE_DATASET))978 GSF_CLASS_FULL (GogSeries, gog_series,
979 		NULL, NULL, gog_series_class_init, NULL,
980 		gog_series_init, GOG_TYPE_STYLED_OBJECT, G_TYPE_FLAG_ABSTRACT,
981 		GSF_INTERFACE (gog_series_dataset_init, GOG_TYPE_DATASET))
982 
983 /**
984  * gog_series_get_plot:
985  * @series: #GogSeries
986  *
987  * Returns: (transfer none): the possibly NULL plot that contains this series.
988  **/
989 GogPlot *
990 gog_series_get_plot (GogSeries const *series)
991 {
992 	g_return_val_if_fail (GOG_IS_SERIES (series), NULL);
993 	return series->plot;
994 }
995 
996 /**
997  * gog_series_is_valid:
998  * @series: #GogSeries
999  *
1000  * Returns: the current cached validity.  Does not recheck
1001  **/
1002 gboolean
gog_series_is_valid(GogSeries const * series)1003 gog_series_is_valid (GogSeries const *series)
1004 {
1005 	g_return_val_if_fail (GOG_IS_SERIES (series), FALSE);
1006 	return series->is_valid;
1007 }
1008 
1009 /**
1010  * gog_series_check_validity:
1011  * @series: #GogSeries
1012  *
1013  * Updates the is_valid flag for a series.
1014  * This is an internal utility that should not really be necessary for general
1015  * usage.
1016  **/
1017 void
gog_series_check_validity(GogSeries * series)1018 gog_series_check_validity (GogSeries *series)
1019 {
1020 	unsigned i;
1021 	GogSeriesDesc const *desc;
1022 
1023 	g_return_if_fail (GOG_IS_SERIES (series));
1024 	g_return_if_fail (GOG_IS_PLOT (series->plot));
1025 
1026 	desc = &series->plot->desc.series;
1027 	for (i = series->plot->desc.series.num_dim; i-- > 0; )
1028 		if (desc->dim[i].priority == GOG_SERIES_REQUIRED && (
1029 		      series->values[i].data == NULL ||
1030 		      !go_data_has_value (series->values[i].data))) {
1031 			series->is_valid = FALSE;
1032 			return;
1033 		}
1034 	series->is_valid = TRUE;
1035 }
1036 
1037 /**
1038  * gog_series_has_legend:
1039  * @series: #GogSeries
1040  *
1041  * Returns: TRUE if the series has a visible legend entry
1042  **/
1043 gboolean
gog_series_has_legend(GogSeries const * series)1044 gog_series_has_legend (GogSeries const *series)
1045 {
1046 	g_return_val_if_fail (GOG_IS_SERIES (series), FALSE);
1047 	return series->has_legend;
1048 }
1049 
1050 /**
1051  * gog_series_set_index:
1052  * @series: #GogSeries
1053  * @ind: >= 0 assigns a new index, < 0 resets to auto
1054  * @is_manual: gboolean
1055  *
1056  * If @ind >= 0 attempt to assign the new index.  Auto
1057  * indicies (@is_manual == FALSE) will not override the current
1058  * index if it is manual.  An @index < 0, will reset the index to
1059  * automatic and potentially queue a revaluation of the parent
1060  * chart's cardinality.
1061  **/
1062 void
gog_series_set_index(GogSeries * series,int ind,gboolean is_manual)1063 gog_series_set_index (GogSeries *series, int ind, gboolean is_manual)
1064 {
1065 	g_return_if_fail (GOG_IS_SERIES (series));
1066 
1067 	if (ind < 0) {
1068 		if (series->manual_index && series->plot != NULL)
1069 			gog_plot_request_cardinality_update (series->plot);
1070 		series->manual_index = FALSE;
1071 		return;
1072 	}
1073 
1074 	if (is_manual)
1075 		series->manual_index = TRUE;
1076 	else if (series->manual_index)
1077 		return;
1078 
1079 	series->index = ind;
1080 	go_styled_object_apply_theme (GO_STYLED_OBJECT (series), series->base.style);
1081 	go_styled_object_style_changed (GO_STYLED_OBJECT (series));
1082 }
1083 
1084 /**
1085  * gog_series_get_name:
1086  * @series: a #GogSeries
1087  *
1088  * Gets the _source_ of the name associated with the series.
1089  * NOTE : this is _NOT_ the actual name.
1090  *
1091  * return value: (transfer none): a #GODataScalar, without added reference.
1092  **/
1093 GOData *
gog_series_get_name(GogSeries const * series)1094 gog_series_get_name (GogSeries const *series)
1095 {
1096 	g_return_val_if_fail (GOG_IS_SERIES (series), NULL);
1097 
1098 	return series->values[-1].data;
1099 }
1100 
1101 /**
1102  * gog_series_set_name:
1103  * @series: a #GogSeries
1104  * @name_src: (transfer full): a #GODataScalar
1105  * @err: a #GError
1106  *
1107  * Absorbs a ref to @name_src.
1108  *
1109  **/
1110 void
gog_series_set_name(GogSeries * series,GODataScalar * name_src,GError ** err)1111 gog_series_set_name (GogSeries *series, GODataScalar *name_src, GError **err)
1112 {
1113 	gog_dataset_set_dim (GOG_DATASET (series), -1, GO_DATA (name_src), err);
1114 }
1115 
1116 /**
1117  * gog_series_set_dim:
1118  * @series: #GogSeries
1119  * @dim_i: Which dimension
1120  * @val: (transfer full): #GOData
1121  * @err: (allow-none): optional #GError pointer
1122  *
1123  * Absorbs a ref to @val
1124  **/
1125 void
gog_series_set_dim(GogSeries * series,int dim_i,GOData * val,GError ** err)1126 gog_series_set_dim (GogSeries *series, int dim_i, GOData *val, GError **err)
1127 {
1128 	gog_dataset_set_dim (GOG_DATASET (series), dim_i, val, err);
1129 }
1130 
1131 int
gog_series_map_XL_dim(GogSeries const * series,GogMSDimType ms_type)1132 gog_series_map_XL_dim (GogSeries const *series, GogMSDimType ms_type)
1133 {
1134 	GogSeriesDesc const *desc = &series->plot->desc.series;
1135 	unsigned i = desc->num_dim;
1136 
1137 	if (ms_type == GOG_MS_DIM_LABELS)
1138 		return -1;
1139 	while (i-- > 0)
1140 		if (desc->dim[i].ms_type == ms_type)
1141 			return i;
1142 	return -2;
1143 }
1144 
1145 void
gog_series_set_XL_dim(GogSeries * series,GogMSDimType ms_type,GOData * val,GError ** err)1146 gog_series_set_XL_dim (GogSeries *series, GogMSDimType ms_type, GOData *val, GError **err)
1147 {
1148 	int dim;
1149 	g_return_if_fail (series !=NULL);
1150 	dim = gog_series_map_XL_dim (series, ms_type);
1151 	if (dim >= -1) {
1152 		gog_series_set_dim (series, dim, val, err);
1153 		return;
1154 	}
1155 	g_object_unref (val);
1156 }
1157 
1158 /**
1159  * gog_series_num_elements:
1160  * @series: #GogSeries
1161  *
1162  * Returns: the number of elements in the series
1163  **/
1164 unsigned
gog_series_num_elements(GogSeries const * series)1165 gog_series_num_elements (GogSeries const *series)
1166 {
1167 	return series->num_elements;
1168 }
1169 
1170 /**
1171  * gog_series_get_overrides:
1172  * @series: #GogSeries
1173  *
1174  * Overrides are data owning their own style, overriding the series style.
1175  * Returns: (element-type GogSeriesElement) (transfer none): the series
1176  * overrides.
1177  **/
1178 GList const *
gog_series_get_overrides(GogSeries const * series)1179 gog_series_get_overrides (GogSeries const *series)
1180 {
1181 	return series->overrides;
1182 }
1183 
1184 int
gog_series_get_valid_element_index(GogSeries const * series,int old_index,int desired_index)1185 gog_series_get_valid_element_index (GogSeries const *series, int old_index, int desired_index)
1186 {
1187 	int index;
1188 	GList *ptr;
1189 
1190 	g_return_val_if_fail (GOG_IS_SERIES (series), -1);
1191 
1192 	if ((desired_index >= (int) series->num_elements) ||
1193 	    (desired_index < 0))
1194 		return old_index;
1195 
1196 	if (desired_index > old_index)
1197 		for (ptr = series->overrides; ptr != NULL; ptr = ptr->next) {
1198 			index = GOG_SERIES_ELEMENT (ptr->data)->index;
1199 			if (index > desired_index)
1200 				break;
1201 			if (index == desired_index)
1202 				desired_index++;
1203 		}
1204 	else
1205 		for (ptr = g_list_last (series->overrides); ptr != NULL; ptr = ptr->prev) {
1206 			index = GOG_SERIES_ELEMENT (ptr->data)->index;
1207 			if (index < desired_index)
1208 				break;
1209 			if (index == desired_index)
1210 				desired_index--;
1211 		}
1212 
1213 	if ((desired_index >= 0) &&
1214 	    (desired_index < (int) series->num_elements))
1215 		return desired_index;
1216 
1217 	return old_index;
1218 }
1219 
1220 /**
1221  * gog_series_get_element:
1222  * @series: #GogSeries
1223  * @index: the element index
1224  *
1225  * Returns: (transfer none): the #GogSeriesElement corresponding to @index if
1226  * any
1227  **/
1228 GogSeriesElement *
gog_series_get_element(GogSeries const * series,int index)1229 gog_series_get_element (GogSeries const *series, int index)
1230 {
1231 	GList *ptr;
1232 	GogSeriesElement *element;
1233 
1234 	g_return_val_if_fail (GOG_IS_SERIES (series), NULL);
1235 
1236 	for (ptr = series->overrides; ptr != NULL; ptr = ptr->next) {
1237 		element = GOG_SERIES_ELEMENT (ptr->data);
1238 		if ((int) element->index == index)
1239 			return element;
1240 	}
1241 
1242 	return NULL;
1243 }
1244 
1245 static unsigned int
gog_series_get_data(GogSeries const * series,int * indices,double ** data,int n_vectors)1246 gog_series_get_data (GogSeries const *series, int *indices, double **data, int n_vectors)
1247 {
1248 	GOData *vector;
1249 	int i, n_points = 0, vector_n_points;
1250 	int first, last;
1251 	int index;
1252 	gboolean is_set = FALSE;
1253 
1254 	if (!gog_series_is_valid (series))
1255 		return 0;
1256 
1257 	gog_dataset_dims (GOG_DATASET (series), &first, &last);
1258 
1259 	for (i = 0; i < n_vectors; i++) {
1260 		index = indices != NULL ? indices[i] : i;
1261 		if (index >= first && index <= last &&
1262 		    (vector = series->values[index].data) != NULL) {
1263 			data[i] = (go_data_has_value (vector))? go_data_get_values (vector): NULL;
1264 			vector_n_points = go_data_get_vector_size (vector);
1265 			if (!is_set) {
1266 				is_set = TRUE;
1267 				n_points = vector_n_points;
1268 			} else
1269 				n_points = MIN (vector_n_points, n_points);
1270 		} else
1271 			data[i] = NULL;
1272 	}
1273 
1274 	return n_points;
1275 }
1276 
1277 unsigned
gog_series_get_xy_data(GogSeries const * series,double const ** x,double const ** y)1278 gog_series_get_xy_data (GogSeries const  *series,
1279 			double const 	**x,
1280 			double const 	**y)
1281 {
1282 	GogSeriesClass	*klass = GOG_SERIES_GET_CLASS (series);
1283 	double *data[2];
1284 	unsigned int n_points;
1285 	int first, last;
1286 
1287 	g_return_val_if_fail (klass != NULL, 0);
1288 
1289 	if (klass->get_xy_data != NULL) {
1290 		if (!gog_series_is_valid (GOG_SERIES (series)))
1291 			return 0;
1292 
1293 		return (klass->get_xy_data) (series, x, y);
1294 	}
1295 
1296 	gog_dataset_dims (GOG_DATASET (series), &first, &last);
1297 
1298 	g_return_val_if_fail (first <= 0, 0);
1299 	g_return_val_if_fail (last >= 1, 0);
1300 
1301 	n_points = gog_series_get_data (series, NULL, data, 2);
1302 
1303 	*x = data[0];
1304 	*y = data[1];
1305 
1306 	return n_points;
1307 }
1308 
1309 unsigned
gog_series_get_xyz_data(GogSeries const * series,double const ** x,double const ** y,double const ** z)1310 gog_series_get_xyz_data (GogSeries const  *series,
1311 			 double const 	 **x,
1312 			 double const 	 **y,
1313 			 double const 	 **z)
1314 {
1315 	double *data[3];
1316 	unsigned int n_points;
1317 
1318 	n_points = gog_series_get_data (series, NULL, data, 3);
1319 
1320 	*x = data[0];
1321 	*y = data[1];
1322 	*z = data[2];
1323 
1324 	return n_points;
1325 }
1326 
1327 GogSeriesFillType
gog_series_get_fill_type(GogSeries const * series)1328 gog_series_get_fill_type (GogSeries const *series)
1329 {
1330 	g_return_val_if_fail (GOG_IS_SERIES (series), GOG_SERIES_FILL_TYPE_INVALID);
1331 
1332 	return series->fill_type;
1333 }
1334 
1335 void
gog_series_set_fill_type(GogSeries * series,GogSeriesFillType fill_type)1336 gog_series_set_fill_type (GogSeries *series, GogSeriesFillType fill_type)
1337 {
1338 	GogSeriesClass *series_klass;
1339 
1340 	g_return_if_fail (GOG_IS_SERIES (series));
1341 	if (series->fill_type == fill_type)
1342 		return;
1343 	g_return_if_fail (fill_type >= 0 && fill_type < GOG_SERIES_FILL_TYPE_INVALID);
1344 
1345 	series_klass = GOG_SERIES_GET_CLASS (series);
1346 	g_return_if_fail (series_klass->valid_fill_type_list != NULL);
1347 
1348 	series->fill_type = fill_type;
1349 	gog_object_request_update (GOG_OBJECT (series));
1350 }
1351 
1352 #ifdef GOFFICE_WITH_GTK
1353 void
gog_series_populate_fill_type_combo(GogSeries const * series,GtkComboBox * combo)1354 gog_series_populate_fill_type_combo (GogSeries const *series, GtkComboBox *combo)
1355 {
1356 	GogSeriesClass *series_klass;
1357 	GogSeriesFillType fill_type;
1358 	unsigned int i;
1359 
1360 	g_return_if_fail (GOG_IS_SERIES (series));
1361 	series_klass = GOG_SERIES_GET_CLASS (series);
1362 	g_return_if_fail (series_klass->valid_fill_type_list != NULL);
1363 
1364 	gtk_list_store_clear (GTK_LIST_STORE (gtk_combo_box_get_model (combo)));
1365 	for (i = 0; series_klass->valid_fill_type_list[i] != GOG_SERIES_FILL_TYPE_INVALID; i++) {
1366 		fill_type = series_klass->valid_fill_type_list[i];
1367 		if (fill_type < GOG_SERIES_FILL_TYPE_INVALID) {
1368 			go_gtk_combo_box_append_text (combo, _(_fill_type_infos[fill_type].label));
1369 			if (fill_type == series->fill_type)
1370 				gtk_combo_box_set_active (combo, i);
1371 		}
1372 	}
1373 }
1374 
1375 GogSeriesFillType
gog_series_get_fill_type_from_combo(GogSeries const * series,GtkComboBox * combo)1376 gog_series_get_fill_type_from_combo (GogSeries const *series, GtkComboBox *combo)
1377 {
1378 	GogSeriesClass *series_klass;
1379 
1380 	g_return_val_if_fail (GOG_IS_SERIES (series), GOG_SERIES_FILL_TYPE_INVALID);
1381 	series_klass = GOG_SERIES_GET_CLASS (series);
1382 	g_return_val_if_fail (series_klass->valid_fill_type_list != NULL, GOG_SERIES_FILL_TYPE_INVALID);
1383 
1384 	return series_klass->valid_fill_type_list[gtk_combo_box_get_active (combo)];
1385 }
1386 #endif
1387 
1388 /**
1389  * gog_series_get_interpolation_params:
1390  * @series: #GogSeries
1391  *
1392  * Only constrained cubic spline interpolation use parameters (for the
1393  * slopes at each end).
1394  * Returns: (transfer none): the interpolation parameters.
1395  **/
1396 GogDataset *
gog_series_get_interpolation_params(GogSeries const * series)1397 gog_series_get_interpolation_params (GogSeries const *series)
1398 {
1399 	GogSeriesClass *series_klass;
1400 
1401 	g_return_val_if_fail (GOG_IS_SERIES (series), NULL);
1402 	series_klass = GOG_SERIES_GET_CLASS (series);
1403 	return (series_klass->get_interpolation_params)?
1404 			series_klass->get_interpolation_params (series):
1405 			NULL;
1406 }
1407