1 /*
2  * gog-chart.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-chart-impl.h>
24 #include <goffice/graph/gog-chart-map.h>
25 #include <goffice/graph/gog-chart-map-3d.h>
26 #include <goffice/graph/gog-3d-box.h>
27 #include <goffice/graph/gog-plot-impl.h>
28 #include <goffice/graph/gog-graph-impl.h>
29 #include <goffice/utils/go-style.h>
30 #include <goffice/graph/gog-view.h>
31 #include <goffice/graph/gog-axis.h>
32 #include <goffice/graph/gog-axis-line-impl.h>
33 #include <goffice/graph/gog-grid.h>
34 #include <goffice/graph/gog-grid-line.h>
35 #include <goffice/graph/gog-renderer.h>
36 #include <goffice/math/go-math.h>
37 #include <goffice/math/go-matrix3x3.h>
38 #include <goffice/utils/go-persist.h>
39 
40 #include <gsf/gsf-impl-utils.h>
41 #include <glib/gi18n-lib.h>
42 #include <string.h>
43 #include <math.h>
44 
45 #ifdef GOFFICE_WITH_LASEM
46 #include <goffice/graph/gog-equation.h>
47 #endif
48 
49 #ifdef GOFFICE_WITH_GTK
50 #include <goffice/gtk/goffice-gtk.h>
51 #endif
52 
53 /**
54  * SECTION: gog-chart
55  * @short_description: A chart.
56  * @See_also: #GogPlot
57  *
58  * #GogChart wraps one or more #GogPlot objects, so that you can superimpose
59  * them on top of each other. In addition, the chart can have a title and a legend.
60 */
61 
62 static const struct {
63 	char const *name;
64 	GogAxisSet const axis_set;
65 } axis_set_desc[] = {
66 	{ "none",	GOG_AXIS_SET_NONE},
67 	{ "x",		GOG_AXIS_SET_X},
68 	{ "xy", 	GOG_AXIS_SET_XY},
69 	{ "xyz",	GOG_AXIS_SET_XYZ},
70 	{ "radar",	GOG_AXIS_SET_RADAR},
71 	{ "pseudo-3d",	GOG_AXIS_SET_XY_pseudo_3d},
72 	{ "xy-color",	GOG_AXIS_SET_XY_COLOR}
73 };
74 
75 GogAxisSet
gog_axis_set_from_str(char const * str)76 gog_axis_set_from_str (char const *str)
77 {
78 	GogAxisSet axis_set = GOG_AXIS_SET_NONE;
79 	unsigned i;
80 	gboolean found = FALSE;
81 
82 	if (str == NULL)
83 		return GOG_AXIS_SET_NONE;
84 
85 	for (i = 0; i < G_N_ELEMENTS (axis_set_desc); i++)
86 		if (strcmp (axis_set_desc[i].name, str) == 0) {
87 			axis_set = axis_set_desc[i].axis_set;
88 			found = TRUE;
89 			break;
90 		}
91 	if (!found)
92 		g_warning ("[GogAxisSet::from_str] unknown axis set (%s)", str);
93 	return axis_set;
94 }
95 
96 /*****************************************************************************/
97 enum {
98 	CHART_PROP_0,
99 	CHART_PROP_CARDINALITY_VALID,
100 	CHART_PROP_PLOT_AREA,
101 	CHART_PROP_PLOT_AREA_IS_MANUAL,
102 	CHART_PROP_X_POS,
103 	CHART_PROP_Y_POS,
104 	CHART_PROP_ROWS,
105 	CHART_PROP_COLUMNS
106 };
107 
108 static GType gog_chart_view_get_type (void);
109 static GObjectClass *chart_parent_klass;
110 
111 static void
gog_chart_update(GogObject * obj)112 gog_chart_update (GogObject *obj)
113 {
114 	GogChart *chart = GOG_CHART (obj);
115 	unsigned full = chart->full_cardinality;
116 	unsigned visible = chart->visible_cardinality;
117 
118 	gog_chart_get_cardinality (chart, NULL, NULL);
119 
120 	if (full != chart->full_cardinality ||
121 	    visible != chart->visible_cardinality)
122 		g_object_notify (G_OBJECT (chart), "cardinality-valid");
123 }
124 
125 static void
gog_chart_finalize(GObject * obj)126 gog_chart_finalize (GObject *obj)
127 {
128 	GogChart *chart = GOG_CHART (obj);
129 
130 	/* on exit the role remove routines are not called */
131 	g_slist_free (chart->plots);
132 	g_slist_free (chart->axes);
133 
134 	(chart_parent_klass->finalize) (obj);
135 }
136 
137 static void
gog_chart_set_property(GObject * obj,guint param_id,GValue const * value,GParamSpec * pspec)138 gog_chart_set_property (GObject *obj, guint param_id,
139 			 GValue const *value, GParamSpec *pspec)
140 {
141 	GogChart *chart = GOG_CHART (obj);
142 	char **str_doubles;
143 	char const *str;
144 	gboolean changed = FALSE;
145 
146 	switch (param_id) {
147 	case CHART_PROP_PLOT_AREA:
148 		str = g_value_get_string (value);
149 		str_doubles = g_strsplit (str, " ", 4);
150 		if (g_strv_length (str_doubles) != 4) {
151 			g_strfreev (str_doubles);
152 			break;
153 		}
154 		chart->plot_area.x = g_ascii_strtod (str_doubles[0], NULL);
155 		chart->plot_area.y = g_ascii_strtod (str_doubles[1], NULL);
156 		chart->plot_area.w = g_ascii_strtod (str_doubles[2], NULL);
157 		chart->plot_area.h = g_ascii_strtod (str_doubles[3], NULL);
158 		g_strfreev (str_doubles);
159 		break;
160 	case CHART_PROP_PLOT_AREA_IS_MANUAL:
161 		chart->is_plot_area_manual = g_value_get_boolean (value);
162 		break;
163 	case CHART_PROP_X_POS:
164 		chart->x_pos = g_value_get_int (value);
165 		changed = TRUE;
166 		break;
167 	case CHART_PROP_Y_POS:
168 		chart->y_pos = g_value_get_int (value);
169 		changed = TRUE;
170 		break;
171 	case CHART_PROP_COLUMNS:
172 		chart->cols = g_value_get_int (value);
173 		changed = TRUE;
174 		break;
175 	case CHART_PROP_ROWS:
176 		chart->rows = g_value_get_int (value);
177 		changed = TRUE;
178 		break;
179 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
180 		 return; /* NOTE : RETURN */
181 	}
182 
183 	if (changed) {
184 		gog_graph_validate_chart_layout (GOG_GRAPH (GOG_OBJECT (chart)->parent));
185 		gog_object_emit_changed (GOG_OBJECT (obj), TRUE);
186 	}
187 }
188 
189 static void
gog_chart_get_property(GObject * obj,guint param_id,GValue * value,GParamSpec * pspec)190 gog_chart_get_property (GObject *obj, guint param_id,
191 			GValue *value, GParamSpec *pspec)
192 {
193 	GogChart *chart = GOG_CHART (obj);
194 	GString *string;
195 	char buffer[G_ASCII_DTOSTR_BUF_SIZE];
196 
197 	switch (param_id) {
198 	case CHART_PROP_CARDINALITY_VALID:
199 		g_value_set_boolean (value, chart->cardinality_valid);
200 		break;
201 	case CHART_PROP_PLOT_AREA:
202 		string = g_string_new ("");
203 		g_string_append (string, g_ascii_dtostr (buffer, sizeof (buffer), chart->plot_area.x));
204 		g_string_append_c (string, ' ');
205 		g_string_append (string, g_ascii_dtostr (buffer, sizeof (buffer), chart->plot_area.y));
206 		g_string_append_c (string, ' ');
207 		g_string_append (string, g_ascii_dtostr (buffer, sizeof (buffer), chart->plot_area.w));
208 		g_string_append_c (string, ' ');
209 		g_string_append (string, g_ascii_dtostr (buffer, sizeof (buffer), chart->plot_area.h));
210 		g_value_set_string (value, string->str);
211 		g_string_free (string, TRUE);
212 		break;
213 	case CHART_PROP_PLOT_AREA_IS_MANUAL:
214 		g_value_set_boolean (value, chart->is_plot_area_manual);
215 		break;
216 	case CHART_PROP_X_POS:
217 		g_value_set_int (value, chart->x_pos);
218 		break;
219 	case CHART_PROP_Y_POS:
220 		g_value_set_int (value, chart->y_pos);
221 		break;
222 	case CHART_PROP_COLUMNS:
223 		g_value_set_int (value, chart->cols);
224 		break;
225 	case CHART_PROP_ROWS:
226 		g_value_set_int (value, chart->rows);
227 		break;
228 
229 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
230 		 break;
231 	}
232 }
233 
234 #ifdef GOFFICE_WITH_GTK
235 typedef struct {
236 	GtkBuilder	*gui;
237 	GtkWidget	*x_spin, *y_spin, *w_spin, *h_spin;
238 	gulong		 w_spin_signal, h_spin_signal;
239 	GtkWidget	*position_select_combo;
240 	GtkWidget	*manual_setting_grid;
241 	GogChart	*chart;
242 } PlotAreaPrefState;
243 
244 static void
plot_area_pref_state_free(PlotAreaPrefState * state)245 plot_area_pref_state_free (PlotAreaPrefState *state)
246 {
247 	g_object_unref (state->chart);
248 	g_object_unref (state->gui);
249 }
250 
251 static void
cb_plot_area_changed(GtkWidget * spin,PlotAreaPrefState * state)252 cb_plot_area_changed (GtkWidget *spin, PlotAreaPrefState *state)
253 {
254 	GogViewAllocation pos;
255 	double value;
256 	double max;
257 
258        	value = gtk_spin_button_get_value (GTK_SPIN_BUTTON (spin)) / 100.0;
259 
260        	gog_chart_get_plot_area (state->chart, &pos);
261 	if (spin == state->x_spin) {
262 		pos.x = value;
263 		max = 1.0 - pos.x;
264 		g_signal_handler_block (state->w_spin, state->w_spin_signal);
265 		gtk_spin_button_set_range (GTK_SPIN_BUTTON (state->w_spin), 0.0, max * 100.0);
266 		if (pos.w > max) pos.w = max;
267 		gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->w_spin), pos.w * 100.0);
268 		g_signal_handler_unblock (state->w_spin, state->w_spin_signal);
269 	}
270 	else if (spin == state->y_spin) {
271 		pos.y = value;
272 		max = 1.0 - pos.y;
273 		g_signal_handler_block (state->h_spin, state->h_spin_signal);
274 		gtk_spin_button_set_range (GTK_SPIN_BUTTON (state->h_spin), 0.0, max * 100.0);
275 		if (pos.h > max) pos.h = max;
276 		gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->h_spin), pos.w * 100.0);
277 		g_signal_handler_unblock (state->h_spin, state->h_spin_signal);
278 	}
279 	else if (spin == state->w_spin) {
280 		pos.w = value;
281 	}
282 	else if (spin == state->h_spin) {
283 		pos.h = value;
284 	}
285 	gog_chart_set_plot_area (state->chart, &pos);
286 	gtk_combo_box_set_active (GTK_COMBO_BOX (state->position_select_combo), 1);
287 	gtk_widget_show (state->manual_setting_grid);
288 }
289 
290 static void
cb_manual_position_changed(GtkComboBox * combo,PlotAreaPrefState * state)291 cb_manual_position_changed (GtkComboBox *combo, PlotAreaPrefState *state)
292 {
293 	if (gtk_combo_box_get_active (combo) == 1) {
294 		GogViewAllocation plot_area;
295 
296 		gog_chart_get_plot_area (state->chart, &plot_area);
297 		gog_chart_set_plot_area (state->chart, &plot_area);
298 		gtk_widget_show (state->manual_setting_grid);
299 	} else {
300 		gog_chart_set_plot_area (state->chart, NULL);
301 		gtk_widget_hide (state->manual_setting_grid);
302 	}
303 }
304 
305 static void
gog_chart_populate_editor(GogObject * gobj,GOEditor * editor,G_GNUC_UNUSED GogDataAllocator * dalloc,GOCmdContext * cc)306 gog_chart_populate_editor (GogObject *gobj,
307 			   GOEditor *editor,
308 			   G_GNUC_UNUSED GogDataAllocator *dalloc,
309 			   GOCmdContext *cc)
310 {
311 	static guint chart_pref_page = 0;
312 
313 	GtkBuilder *gui;
314 	PlotAreaPrefState *state;
315 	gboolean is_plot_area_manual;
316 	GogViewAllocation plot_area;
317 	GtkWidget *w;
318 	GogChart *chart = GOG_CHART (gobj);
319 
320 	g_return_if_fail (chart != NULL);
321 
322 	gui = go_gtk_builder_load_internal ("res:go:graph/gog-plot-prefs.ui", GETTEXT_PACKAGE, cc);
323 	g_return_if_fail (gui != NULL);
324 
325 	(GOG_OBJECT_CLASS(chart_parent_klass)->populate_editor) (gobj, editor, dalloc, cc);
326 
327 	state = g_new  (PlotAreaPrefState, 1);
328 	state->chart = chart;
329 	state->gui = gui;
330 
331 	g_object_ref (chart);
332 	is_plot_area_manual = gog_chart_get_plot_area (chart, &plot_area);
333 
334 	state->x_spin = go_gtk_builder_get_widget (gui, "x_spin");
335 	gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->x_spin),
336 				   plot_area.x * 100.0);
337 	g_signal_connect (G_OBJECT (state->x_spin), "value-changed",
338 			  G_CALLBACK (cb_plot_area_changed), state);
339 
340 	state->y_spin = go_gtk_builder_get_widget (gui, "y_spin");
341 	gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->y_spin),
342 				   plot_area.y * 100.0);
343 	g_signal_connect (G_OBJECT (state->y_spin), "value-changed",
344 			  G_CALLBACK (cb_plot_area_changed), state);
345 
346 	state->w_spin = go_gtk_builder_get_widget (gui, "w_spin");
347 	gtk_spin_button_set_range (GTK_SPIN_BUTTON (state->w_spin),
348 				   0.0, (1.0 - plot_area.x) * 100.0);
349 	gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->w_spin),
350 				   100.0 * plot_area.w);
351 	state->w_spin_signal = g_signal_connect (G_OBJECT (state->w_spin), "value-changed",
352 						 G_CALLBACK (cb_plot_area_changed), state);
353 
354 	state->h_spin = go_gtk_builder_get_widget (gui, "h_spin");
355 	gtk_spin_button_set_range (GTK_SPIN_BUTTON (state->h_spin),
356 				   0.0, (1.0 - plot_area.y) * 100.0);
357 	gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->h_spin),
358 				   100.0 * plot_area.h);
359 	state->h_spin_signal = g_signal_connect (G_OBJECT (state->h_spin), "value-changed",
360 						 G_CALLBACK (cb_plot_area_changed), state);
361 
362 	state->position_select_combo = go_gtk_builder_get_widget (gui, "position_select_combo");
363 	gtk_combo_box_set_active (GTK_COMBO_BOX (state->position_select_combo),
364 				  is_plot_area_manual ? 1 : 0);
365 	state->manual_setting_grid = go_gtk_builder_get_widget (gui, "manual-setting-grid");
366 	if (!is_plot_area_manual)
367 		gtk_widget_hide (state->manual_setting_grid);
368 
369 	g_signal_connect (G_OBJECT (state->position_select_combo),
370 			  "changed", G_CALLBACK (cb_manual_position_changed), state);
371 
372 	w = go_gtk_builder_get_widget (gui, "gog-plot-prefs");
373 	g_signal_connect_swapped (G_OBJECT (w), "destroy", G_CALLBACK (plot_area_pref_state_free), state);
374 	go_editor_add_page (editor, w, _("Plot area"));
375 
376 	go_editor_set_store_page (editor, &chart_pref_page);
377 }
378 #endif
379 
380 static void
gog_chart_children_reordered(GogObject * obj)381 gog_chart_children_reordered (GogObject *obj)
382 {
383 	GSList *ptr, *accum = NULL;
384 	GogChart *chart = GOG_CHART (obj);
385 
386 	for (ptr = obj->children; ptr != NULL ; ptr = ptr->next)
387 		if (GOG_IS_PLOT (ptr->data))
388 			accum = g_slist_prepend (accum, ptr->data);
389 	g_slist_free (chart->plots);
390 	chart->plots = g_slist_reverse (accum);
391 
392 	gog_chart_request_cardinality_update (chart);
393 }
394 
395 static gboolean
role_plot_can_add(GogObject const * parent)396 role_plot_can_add (GogObject const *parent)
397 {
398 	GogChart *chart = GOG_CHART (parent);
399 	return (chart->axis_set & (1 << GOG_AXIS_Z)) == 0 || g_slist_length (chart->plots) == 0;
400 }
401 
402 static void
role_plot_post_add(GogObject * parent,GogObject * plot)403 role_plot_post_add (GogObject *parent, GogObject *plot)
404 {
405 	GogChart *chart = GOG_CHART (parent);
406 	gboolean ok = TRUE;
407 	GogPlotClass *plot_klass = GOG_PLOT_CLASS (G_OBJECT_GET_CLASS (plot));
408 	GogAxisSet axis_set = plot_klass->axis_set & ~GOG_AXIS_SET_FUNDAMENTAL;
409 
410 	if (axis_set) {
411 		int i = GOG_AXIS_VIRTUAL, j = 1 << GOG_AXIS_VIRTUAL;
412 		for (; i < GOG_AXIS_TYPES; i++, j <<= 1)
413 			if ((axis_set & j) != 0 && (chart->axis_set & j) == 0) {
414 				GogObject *axis = GOG_OBJECT (g_object_new (GOG_TYPE_AXIS, "type", i, NULL));
415 				chart->axis_set |= j;
416 				switch (i) {
417 				case GOG_AXIS_PSEUDO_3D:
418 					gog_object_add_by_name (GOG_OBJECT (chart), "Pseudo-3D-Axis", axis);
419 					break;
420 				case GOG_AXIS_COLOR:
421 					gog_object_add_by_name (GOG_OBJECT (chart), "Color-Axis", axis);
422 					break;
423 				case GOG_AXIS_BUBBLE:
424 					gog_object_add_by_name (GOG_OBJECT (chart), "Bubble-Axis", axis);
425 					break;
426 				default:
427 					g_warning ("Unknown axis type: %x\n", i);
428 				}
429 			}
430 	}
431 	/* APPEND to keep order, there won't be that many */
432 	chart->plots = g_slist_append (chart->plots, plot);
433 	gog_chart_request_cardinality_update (chart);
434 
435 	if (chart->plots->next == NULL)
436 		ok = gog_chart_axis_set_assign (chart,
437 			gog_plot_axis_set_pref (GOG_PLOT (plot)));
438 	ok |= gog_plot_axis_set_assign (GOG_PLOT (plot),
439 		chart->axis_set);
440 
441 	/* a quick post condition to keep us on our toes */
442 	g_return_if_fail (ok);
443 }
444 
445 static void
role_plot_pre_remove(GogObject * parent,GogObject * plot)446 role_plot_pre_remove (GogObject *parent, GogObject *plot)
447 {
448 	GogChart *chart = GOG_CHART (parent);
449 	gog_plot_axis_clear (GOG_PLOT (plot), GOG_AXIS_SET_ALL);
450 	chart->plots = g_slist_remove (chart->plots, plot);
451 	gog_chart_request_cardinality_update (chart);
452 
453 	if (chart->plots == NULL)
454 		gog_chart_axis_set_assign (chart, GOG_AXIS_SET_UNKNOWN);
455 
456 	if (chart->grid != NULL &&
457 	    chart->axis_set != GOG_AXIS_SET_XY &&
458 	    chart->axis_set != GOG_AXIS_SET_X &&
459 	    chart->axis_set != GOG_AXIS_SET_XY_pseudo_3d &&
460 	    chart->axis_set != GOG_AXIS_SET_XY_COLOR &&
461 	    chart->axis_set != GOG_AXIS_SET_RADAR) {
462 		GogObject *grid = chart->grid; /* clear_parent clears ::grid */
463 		gog_object_clear_parent (GOG_OBJECT (grid));
464 		g_object_unref (grid);
465 	}
466 }
467 
468 static gboolean
role_grid_can_add(GogObject const * parent)469 role_grid_can_add (GogObject const *parent)
470 {
471 	GogChart const *chart = GOG_CHART (parent);
472 	return chart->grid == NULL &&
473 		(chart->axis_set == GOG_AXIS_SET_XY ||
474 		 chart->axis_set == GOG_AXIS_SET_X ||
475 		 chart->axis_set == GOG_AXIS_SET_XY_pseudo_3d ||
476 		 chart->axis_set == GOG_AXIS_SET_XY_COLOR ||
477 		 chart->axis_set == GOG_AXIS_SET_RADAR);
478 }
479 
480 static void
role_grid_post_add(GogObject * parent,GogObject * child)481 role_grid_post_add (GogObject *parent, GogObject *child)
482 {
483 	GogChart *chart = GOG_CHART (parent);
484 	g_return_if_fail (chart->grid == NULL);
485 	chart->grid = child;
486 }
487 
488 static void
role_grid_pre_remove(GogObject * parent,GogObject * grid)489 role_grid_pre_remove (GogObject *parent, GogObject *grid)
490 {
491 	GogChart *chart = GOG_CHART (parent);
492 	g_return_if_fail (chart->grid == grid);
493 	chart->grid = NULL;
494 }
495 
496 static gboolean
xy_grid_3d_can_add(GogObject const * parent)497 xy_grid_3d_can_add (GogObject const *parent)
498 {
499 	return (GOG_CHART (parent)->axis_set == GOG_AXIS_SET_XYZ &&
500 		NULL == gog_object_get_child_by_name (parent, "XY-Backplane"));
501 }
502 
503 static gboolean
yz_grid_3d_can_add(GogObject const * parent)504 yz_grid_3d_can_add (GogObject const *parent)
505 {
506 	return (GOG_CHART (parent)->axis_set == GOG_AXIS_SET_XYZ &&
507 		NULL == gog_object_get_child_by_name (parent, "YZ-Backplane"));
508 }
509 
510 static gboolean
zx_grid_3d_can_add(GogObject const * parent)511 zx_grid_3d_can_add (GogObject const *parent)
512 {
513 	return (GOG_CHART (parent)->axis_set == GOG_AXIS_SET_XYZ &&
514 		NULL == gog_object_get_child_by_name (parent, "ZX-Backplane"));
515 }
516 
517 static void
grid_3d_post_add(GogObject * child,GogGridType t)518 grid_3d_post_add (GogObject *child, GogGridType t)
519 {
520 	g_object_set (G_OBJECT (child), "type", (int)t, NULL);
521 }
522 
xy_grid_3d_post_add(GogObject * parent,GogObject * child)523 static void xy_grid_3d_post_add    (GogObject *parent, GogObject *child)
524 { grid_3d_post_add (child, GOG_GRID_XY); }
yz_grid_3d_post_add(GogObject * parent,GogObject * child)525 static void yz_grid_3d_post_add    (GogObject *parent, GogObject *child)
526 { grid_3d_post_add (child, GOG_GRID_YZ); }
zx_grid_3d_post_add(GogObject * parent,GogObject * child)527 static void zx_grid_3d_post_add    (GogObject *parent, GogObject *child)
528 { grid_3d_post_add (child, GOG_GRID_ZX); }
529 
530 static gboolean
axis_can_add(GogObject const * parent,GogAxisType t)531 axis_can_add (GogObject const *parent, GogAxisType t)
532 {
533 	GogChart *chart = GOG_CHART (parent);
534 	if (chart->axis_set == GOG_AXIS_SET_UNKNOWN
535 	    || chart->axis_set == GOG_AXIS_SET_XYZ)
536 		return FALSE;
537 	return (chart->axis_set & (1 << t)) != 0;
538 }
539 
540 static gboolean
axis_can_remove(GogObject const * child)541 axis_can_remove (GogObject const *child)
542 {
543 	return NULL == gog_axis_contributors (GOG_AXIS (child));
544 }
545 
546 static void
axis_post_add(GogObject * axis,GogAxisType t)547 axis_post_add (GogObject *axis, GogAxisType t)
548 {
549 	GogChart *chart = GOG_CHART (axis->parent);
550 	g_object_set (G_OBJECT (axis), "type", (int)t, NULL);
551 	chart->axes = g_slist_prepend (chart->axes, axis);
552 
553 	gog_axis_base_set_position (GOG_AXIS_BASE (axis), GOG_AXIS_AUTO);
554 }
555 
556 static void
axis_pre_remove(GogObject * parent,GogObject * child)557 axis_pre_remove (GogObject *parent, GogObject *child)
558 {
559 	GogChart *chart = GOG_CHART (parent);
560 	GogAxis *axis = GOG_AXIS (child);
561 	GogColorScale *scale = gog_axis_get_color_scale (axis);
562 	if (scale)
563 		gog_color_scale_set_axis (scale, NULL);
564 	gog_axis_clear_contributors (GOG_AXIS (axis));
565 	chart->axes = g_slist_remove (chart->axes, axis);
566 }
567 
568 /*  Color scale related code */
569 static gboolean
color_scale_can_add(GogObject const * parent)570 color_scale_can_add (GogObject const *parent)
571 {
572 	/* TRUE if there are more color or pseudo-3d axes than there are color scales */
573 	GogChart *chart = GOG_CHART (parent);
574 	GSList *ptr;
575 	GogAxis *axis;
576 	GogAxisType type;
577 
578 	if ((chart->axis_set & ((1 << GOG_AXIS_COLOR) | (1 << GOG_AXIS_PSEUDO_3D ))) == 0)
579 		return FALSE;
580 	for (ptr = chart->axes; ptr && ptr->data; ptr = ptr->next) {
581 		axis = GOG_AXIS (ptr->data);
582 		type = gog_axis_get_atype (axis);
583 		if ((type == GOG_AXIS_COLOR || type == GOG_AXIS_PSEUDO_3D)
584 		    && gog_axis_get_color_scale (axis) == NULL)
585 			return TRUE;
586 	}
587 	return FALSE;
588 }
589 
590 static void
color_scale_post_add(GogObject * parent,GogObject * child)591 color_scale_post_add (GogObject *parent, GogObject *child)
592 {
593 	/* Link the color scale to an axis without a preexisting color scale */
594 	GogChart *chart = GOG_CHART (parent);
595 	GSList *ptr;
596 	GogAxis *axis;
597 	GogAxisType type;
598 	GSList const *l;
599 
600 	for (ptr = chart->axes; ptr && ptr->data; ptr = ptr->next) {
601 		axis = GOG_AXIS (ptr->data);
602 		type = gog_axis_get_atype (axis);
603 		if ((type == GOG_AXIS_COLOR || type == GOG_AXIS_PSEUDO_3D)
604 		    && gog_axis_get_color_scale (axis) == NULL) {
605 				gog_color_scale_set_axis (GOG_COLOR_SCALE (child), axis);
606 				for (l = gog_axis_contributors (axis); l; l = l->next)
607 					gog_object_request_update (GOG_OBJECT (l->data));
608 				break;
609 			}
610 	}
611 	gog_chart_request_cardinality_update (chart);
612 }
613 
614 static void
color_scale_pre_remove(GogObject * parent,GogObject * scale)615 color_scale_pre_remove (GogObject *parent, GogObject *scale)
616 {
617 	/* Unlink the color scale */
618 	GSList const *l = gog_axis_contributors (gog_color_scale_get_axis (GOG_COLOR_SCALE (scale)));
619 	gog_color_scale_set_axis (GOG_COLOR_SCALE (scale), NULL);
620 	for (; l; l = l->next)
621 		gog_object_request_update (GOG_OBJECT (l->data));
622 	gog_chart_request_cardinality_update (GOG_CHART (parent));
623 }
624 
625 
x_axis_can_add(GogObject const * parent)626 static gboolean x_axis_can_add (GogObject const *parent) { return axis_can_add (parent, GOG_AXIS_X); }
x_axis_post_add(GogObject * parent,GogObject * child)627 static void x_axis_post_add    (GogObject *parent, GogObject *child)  { axis_post_add   (child, GOG_AXIS_X); }
y_axis_can_add(GogObject const * parent)628 static gboolean y_axis_can_add (GogObject const *parent) { return axis_can_add (parent, GOG_AXIS_Y); }
y_axis_post_add(GogObject * parent,GogObject * child)629 static void y_axis_post_add    (GogObject *parent, GogObject *child)  { axis_post_add   (child, GOG_AXIS_Y); }
z_axis_can_add(GogObject const * parent)630 static gboolean z_axis_can_add (GogObject const *parent) { return axis_can_add (parent, GOG_AXIS_Z); }
z_axis_post_add(GogObject * parent,GogObject * child)631 static void z_axis_post_add    (GogObject *parent, GogObject *child)  { axis_post_add   (child, GOG_AXIS_Z); }
circular_axis_can_add(GogObject const * parent)632 static gboolean circular_axis_can_add (GogObject const *parent) { return axis_can_add (parent, GOG_AXIS_CIRCULAR); }
circular_axis_post_add(GogObject * parent,GogObject * child)633 static void circular_axis_post_add    (GogObject *parent, GogObject *child)  { axis_post_add   (child, GOG_AXIS_CIRCULAR); }
radial_axis_can_add(GogObject const * parent)634 static gboolean radial_axis_can_add (GogObject const *parent) { return axis_can_add (parent, GOG_AXIS_RADIAL); }
radial_axis_post_add(GogObject * parent,GogObject * child)635 static void radial_axis_post_add    (GogObject *parent, GogObject *child)  { axis_post_add   (child, GOG_AXIS_RADIAL); }
pseudo_3d_axis_can_add(GogObject const * parent)636 static gboolean pseudo_3d_axis_can_add (GogObject const *parent) { return axis_can_add (parent, GOG_AXIS_PSEUDO_3D); }
pseudo_3d_axis_post_add(GogObject * parent,GogObject * child)637 static void pseudo_3d_axis_post_add    (GogObject *parent, GogObject *child)  { axis_post_add   (child, GOG_AXIS_PSEUDO_3D); }
bubble_axis_can_add(GogObject const * parent)638 static gboolean bubble_axis_can_add (GogObject const *parent) { return axis_can_add (parent, GOG_AXIS_BUBBLE); }
bubble_axis_post_add(GogObject * parent,GogObject * child)639 static void bubble_axis_post_add    (GogObject *parent, GogObject *child)  { axis_post_add   (child, GOG_AXIS_BUBBLE); }
color_axis_can_add(GogObject const * parent)640 static gboolean color_axis_can_add (GogObject const *parent) { return axis_can_add (parent, GOG_AXIS_COLOR); }
color_axis_post_add(GogObject * parent,GogObject * child)641 static void color_axis_post_add    (GogObject *parent, GogObject *child)  { axis_post_add   (child, GOG_AXIS_COLOR); }
role_3d_box_can_add(GogObject const * parent)642 static gboolean role_3d_box_can_add	(GogObject const *parent) {return FALSE;}
role_3d_box_can_remove(GogObject const * parent)643 static gboolean role_3d_box_can_remove	(GogObject const *parent) {return FALSE;}
644 
645 static GogObjectRole const roles[] = {
646 	{ N_("Backplane"), "GogGrid",	1,
647 	  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
648 	  role_grid_can_add, NULL, NULL, role_grid_post_add, role_grid_pre_remove, NULL, { -1 } },
649 	{ N_("XY-Backplane"), "GogGrid",	1,
650 	  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
651 	  xy_grid_3d_can_add, NULL, NULL, xy_grid_3d_post_add, NULL, NULL,
652 	  { GOG_GRID_XY } },
653 	{ N_("YZ-Backplane"), "GogGrid",	1,
654 	  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
655 	  yz_grid_3d_can_add, NULL, NULL, yz_grid_3d_post_add, NULL, NULL,
656 	  { GOG_GRID_YZ } },
657 	{ N_("ZX-Backplane"), "GogGrid",	1,
658 	  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
659 	  zx_grid_3d_can_add, NULL, NULL, zx_grid_3d_post_add, NULL, NULL,
660 	  { GOG_GRID_ZX } },
661 	{ N_("X-Axis"), "GogAxis",	2,
662 	  GOG_POSITION_PADDING, GOG_POSITION_PADDING, GOG_OBJECT_NAME_BY_ROLE,
663 	  x_axis_can_add, axis_can_remove, NULL, x_axis_post_add, axis_pre_remove, NULL,
664 	  { GOG_AXIS_X } },
665 	{ N_("Y-Axis"), "GogAxis",	3,
666 	  GOG_POSITION_PADDING, GOG_POSITION_PADDING, GOG_OBJECT_NAME_BY_ROLE,
667 	  y_axis_can_add, axis_can_remove, NULL, y_axis_post_add, axis_pre_remove, NULL,
668 	  { GOG_AXIS_Y } },
669 	{ N_("Z-Axis"), "GogAxis",	4,
670 	  GOG_POSITION_PADDING, GOG_POSITION_PADDING, GOG_OBJECT_NAME_BY_ROLE,
671 	  z_axis_can_add, axis_can_remove, NULL, z_axis_post_add, axis_pre_remove, NULL,
672 	  { GOG_AXIS_Z } },
673 	{ N_("Circular-Axis"), "GogAxis", 2,
674 	  GOG_POSITION_PADDING, GOG_POSITION_PADDING, GOG_OBJECT_NAME_BY_ROLE,
675 	  circular_axis_can_add, axis_can_remove, NULL, circular_axis_post_add, axis_pre_remove, NULL,
676 	  { GOG_AXIS_CIRCULAR } },
677 	{ N_("Radial-Axis"), "GogAxis",	3,
678 	  GOG_POSITION_PADDING, GOG_POSITION_PADDING, GOG_OBJECT_NAME_BY_ROLE,
679 	  radial_axis_can_add, axis_can_remove, NULL, radial_axis_post_add, axis_pre_remove, NULL,
680 	  { GOG_AXIS_RADIAL } },
681 	{ N_("Pseudo-3D-Axis"), "GogAxis", 4,
682 	  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
683 	  pseudo_3d_axis_can_add, axis_can_remove, NULL, pseudo_3d_axis_post_add, axis_pre_remove, NULL,
684 	  { GOG_AXIS_PSEUDO_3D } },
685 	{ N_("Bubble-Axis"), "GogAxis", 4,
686 	  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
687 	  bubble_axis_can_add, axis_can_remove, NULL, bubble_axis_post_add, axis_pre_remove, NULL,
688 	  { GOG_AXIS_BUBBLE } },
689 	{ N_("Color-Axis"), "GogAxis", 4,
690 	  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
691 	  color_axis_can_add, axis_can_remove, NULL, color_axis_post_add, axis_pre_remove, NULL,
692 	  { GOG_AXIS_COLOR } },
693 	{ N_("Plot"), "GogPlot",	6,	/* keep the axis before the plots */
694 	  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_TYPE,
695 	  role_plot_can_add, NULL, NULL, role_plot_post_add, role_plot_pre_remove, NULL, { -1 } },
696 	{ N_("Title"), "GogLabel",	0,
697 	  GOG_POSITION_COMPASS|GOG_POSITION_ANY_MANUAL,
698 	  GOG_POSITION_N|GOG_POSITION_ALIGN_CENTER,
699 	  GOG_OBJECT_NAME_BY_ROLE,
700 	  NULL, NULL, NULL, NULL, NULL, NULL, { -1 } },
701 	{ N_("Legend"), "GogLegend",	11,
702 	  GOG_POSITION_COMPASS|GOG_POSITION_ANY_MANUAL,
703 	  GOG_POSITION_E|GOG_POSITION_ALIGN_CENTER,
704 	  GOG_OBJECT_NAME_BY_ROLE,
705 	  NULL, NULL, NULL, NULL, NULL, NULL, { -1 } },
706 #ifdef GOFFICE_WITH_LASEM
707 	{ N_("Equation"), "GogEquation",	11,
708 	  GOG_POSITION_COMPASS|GOG_POSITION_ANY_MANUAL,
709 	  GOG_POSITION_S|GOG_POSITION_ALIGN_CENTER,
710 	  GOG_OBJECT_NAME_BY_ROLE,
711 	  NULL, NULL, NULL, NULL, NULL, NULL, { -1 } },
712 #endif
713 	{ N_("3D-Box"), "Gog3DBox",	1,
714 	  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
715 	  role_3d_box_can_add, role_3d_box_can_remove, NULL, NULL, NULL, NULL, { -1 } },
716 	{ N_("Color-Scale"), "GogColorScale", 7,
717 	  GOG_POSITION_COMPASS|GOG_POSITION_ANY_MANUAL|GOG_POSITION_ANY_MANUAL_SIZE,
718 	  GOG_POSITION_E|GOG_POSITION_ALIGN_CENTER,
719 	  GOG_OBJECT_NAME_BY_ROLE,
720 	  color_scale_can_add, NULL, NULL,
721 	  color_scale_post_add, color_scale_pre_remove, NULL, { -1 } }
722 };
723 
724 static GogManualSizeMode
gog_chart_get_manual_size_mode(GogObject * gobj)725 gog_chart_get_manual_size_mode (GogObject *gobj)
726 {
727 	return GOG_MANUAL_SIZE_FULL;
728 }
729 
730 static void
gog_chart_class_init(GogObjectClass * gog_klass)731 gog_chart_class_init (GogObjectClass *gog_klass)
732 {
733 	GObjectClass *gobject_klass = (GObjectClass *)gog_klass;
734 
735 	chart_parent_klass = g_type_class_peek_parent (gog_klass);
736 	gobject_klass->finalize = gog_chart_finalize;
737 	gobject_klass->set_property = gog_chart_set_property;
738 	gobject_klass->get_property = gog_chart_get_property;
739 
740 #ifdef GOFFICE_WITH_GTK
741 	gog_klass->populate_editor = gog_chart_populate_editor;
742 #else
743 	gog_klass->populate_editor = NULL;
744 #endif
745 
746 	gog_klass->get_manual_size_mode = gog_chart_get_manual_size_mode;
747 
748 	g_object_class_install_property (gobject_klass, CHART_PROP_CARDINALITY_VALID,
749 		g_param_spec_boolean ("cardinality-valid",
750 				      _("Valid cardinality"),
751 				      _("Is the charts cardinality currently valid"),
752 				      FALSE,
753 				      GSF_PARAM_STATIC | G_PARAM_READABLE));
754 	g_object_class_install_property (gobject_klass, CHART_PROP_PLOT_AREA,
755 		g_param_spec_string ("plot-area",
756 				     _("Plot area"),
757 				     _("Position and size of plot area, in percentage of chart size"),
758 				     "0 0 1 1",
759 				     GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
760 	g_object_class_install_property (gobject_klass, CHART_PROP_PLOT_AREA_IS_MANUAL,
761 		g_param_spec_boolean ("is-plot-area-manual",
762 				      _("Manual plot area"),
763 				      _("Is plot area manual"),
764 				      FALSE,
765 				      GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
766 	g_object_class_install_property (gobject_klass, CHART_PROP_X_POS,
767 		g_param_spec_int ("xpos", _("xpos"),
768 			_("Horizontal chart position in graph grid"),
769 			0, G_MAXINT, 0, G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
770 	/* we need to force saving of ypos since the default is not constant */
771 	g_object_class_install_property (gobject_klass, CHART_PROP_Y_POS,
772 		g_param_spec_int ("ypos", _("ypos"),
773 			_("Vertical chart position in graph grid"),
774 			0, G_MAXINT, 0, G_PARAM_READWRITE | GO_PARAM_PERSISTENT | GOG_PARAM_FORCE_SAVE));
775 	g_object_class_install_property (gobject_klass, CHART_PROP_COLUMNS,
776 		g_param_spec_int ("columns", _("columns"),
777 			_("Number of columns in graph grid"),
778 			1, G_MAXINT, 1, G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
779 	g_object_class_install_property (gobject_klass, CHART_PROP_ROWS,
780 		g_param_spec_int ("rows", _("rows"),
781 			_("Number of rows in graph grid"),
782 			1, G_MAXINT, 1, G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
783 
784 	gog_klass->view_type = gog_chart_view_get_type ();
785 	gog_klass->update    = gog_chart_update;
786 	gog_klass->children_reordered = gog_chart_children_reordered;
787 	gog_object_register_roles (gog_klass, roles, G_N_ELEMENTS (roles));
788 }
789 
790 static void
gog_chart_init(GogChart * chart)791 gog_chart_init (GogChart *chart)
792 {
793 	chart->x_pos =
794 	chart->y_pos =
795 	chart->cols  =
796 	chart->rows  =
797 	chart->x_pos_actual =
798 	chart->y_pos_actual = 0;
799 
800 	/* start as true so that we can queue an update when it changes */
801 	chart->cardinality_valid = TRUE;
802 	chart->axis_set = GOG_AXIS_SET_UNKNOWN;
803 
804 	chart->is_plot_area_manual = FALSE;
805 	chart->plot_area.x =
806 	chart->plot_area.y = 0.0;
807 	chart->plot_area.w =
808 	chart->plot_area.h = 1.0;
809 }
810 
GSF_CLASS(GogChart,gog_chart,gog_chart_class_init,gog_chart_init,GOG_TYPE_OUTLINED_OBJECT)811 GSF_CLASS (GogChart, gog_chart,
812 	   gog_chart_class_init, gog_chart_init,
813 	   GOG_TYPE_OUTLINED_OBJECT)
814 
815 /**
816  * gog_chart_get_position:
817  * @chart: const #GogChart
818  * @x:
819  * @y:
820  * @cols:
821  * @rows:
822  *
823  * Returns: TRUE if the chart has been positioned.
824  **/
825 gboolean
826 gog_chart_get_position (GogChart const *chart,
827 			unsigned *x, unsigned *y, unsigned *cols, unsigned *rows)
828 {
829 	g_return_val_if_fail (GOG_CHART (chart), FALSE);
830 
831 	if (chart->cols <= 0 || chart->rows <= 0)
832 		return FALSE;
833 
834 	if (x != NULL)	  *x	= chart->x_pos;
835 	if (y != NULL)	  *y	= chart->y_pos;
836 	if (cols != NULL) *cols	= chart->cols;
837 	if (rows != NULL) *rows	= chart->rows;
838 
839 	return TRUE;
840 }
841 
842 /**
843  * gog_chart_set_position:
844  * @chart: #GogChart
845  * @x:
846  * @y:
847  * @cols:
848  * @rows:
849  *
850  **/
851 void
gog_chart_set_position(GogChart * chart,unsigned int x,unsigned int y,unsigned int cols,unsigned int rows)852 gog_chart_set_position (GogChart *chart,
853 			unsigned int x, unsigned int y, unsigned int cols, unsigned int rows)
854 {
855 	g_return_if_fail (GOG_IS_CHART (chart));
856 
857 	if (chart->x_pos == x && chart->y_pos == y &&
858 	    chart->cols == cols && chart->rows == rows)
859 		return;
860 
861 	chart->x_pos = x;
862 	chart->y_pos = y;
863 	chart->cols = cols;
864 	chart->rows = rows;
865 
866 	gog_graph_validate_chart_layout (GOG_GRAPH (GOG_OBJECT (chart)->parent));
867 	gog_object_emit_changed (GOG_OBJECT (chart), TRUE);
868 }
869 
870 /**
871  * gog_chart_get_plot_area:
872  * @chart: #GogChart
873  * @plot_area: #GogViewAllocation
874  *
875  * Stores plot area in plot_area, in fraction of chart size.
876  *
877  * Returns: %TRUE if plot area position is manual.
878  **/
879 gboolean
gog_chart_get_plot_area(GogChart * chart,GogViewAllocation * plot_area)880 gog_chart_get_plot_area (GogChart *chart, GogViewAllocation *plot_area)
881 {
882 	if (plot_area != NULL)
883 		*plot_area = chart->plot_area;
884 
885 	return chart->is_plot_area_manual;
886 }
887 
888 /**
889  * gog_chart_set_plot_area:
890  * @chart: #GogChart
891  * @plot_area: #GogViewAllocation
892  *
893  * If plot_area != NULL, sets plot area size and location, in fraction
894  * of chart size, and sets GogChart::is_plot_area_manual flag to TRUE.
895  * If plot_area == NULL, sets GogChart::is_plot_area_manual to FALSE.
896  **/
897 void
gog_chart_set_plot_area(GogChart * chart,GogViewAllocation const * plot_area)898 gog_chart_set_plot_area (GogChart *chart, GogViewAllocation const *plot_area)
899 {
900 	if (plot_area == NULL) {
901 		chart->is_plot_area_manual = FALSE;
902 	} else {
903 		chart->plot_area = *plot_area;
904 		chart->is_plot_area_manual = TRUE;
905 	}
906 	gog_object_emit_changed (GOG_OBJECT (chart), TRUE);
907 }
908 
909 /**
910  * gog_chart_get_cardinality:
911  * @chart: a #GogChart
912  * @full: placeholder for full cardinality
913  * @visible: placeholder for visible cardinality
914  *
915  * Update and cache cardinality values if required, and returns
916  * full and visible cardinality. Full cardinality is the number of
917  * chart elements that require a different style. Visible cardinality is
918  * the number of chart elements shown in chart legend.
919  *
920  * @full and @visible may be %NULL.
921  **/
922 void
gog_chart_get_cardinality(GogChart * chart,unsigned * full,unsigned * visible)923 gog_chart_get_cardinality (GogChart *chart, unsigned *full, unsigned *visible)
924 {
925 	GSList *ptr;
926 	unsigned tmp_full, tmp_visible;
927 
928 	g_return_if_fail (GOG_IS_CHART (chart));
929 
930 	if (!chart->cardinality_valid) {
931 		chart->cardinality_valid = TRUE;
932 		chart->full_cardinality = chart->visible_cardinality = 0;
933 		for (ptr = chart->plots ; ptr != NULL ; ptr = ptr->next) {
934 			gog_plot_update_cardinality (ptr->data, chart->full_cardinality);
935 			gog_plot_get_cardinality (ptr->data, &tmp_full, &tmp_visible);
936 			chart->full_cardinality += tmp_full;
937 			chart->visible_cardinality += tmp_visible;
938 		}
939 	}
940 
941 	if (full != NULL)
942 		*full = chart->full_cardinality;
943 	if (visible != NULL)
944 		*visible = chart->visible_cardinality;
945 }
946 
947 void
gog_chart_request_cardinality_update(GogChart * chart)948 gog_chart_request_cardinality_update (GogChart *chart)
949 {
950 	g_return_if_fail (GOG_IS_CHART (chart));
951 
952 	if (chart->cardinality_valid) {
953 		chart->cardinality_valid = FALSE;
954 		gog_object_request_update (GOG_OBJECT (chart));
955 	}
956 }
957 
958 /**
959  * gog_chart_foreach_elem:
960  * @chart: #GogChart
961  * @only_visible: whether to only apply to visible children
962  * @handler: (scope call): callback
963  * @data: user data
964  *
965  * Applies @handler to children
966  **/
967 void
gog_chart_foreach_elem(GogChart * chart,gboolean only_visible,GogEnumFunc handler,gpointer data)968 gog_chart_foreach_elem (GogChart *chart, gboolean only_visible,
969 			GogEnumFunc handler, gpointer data)
970 {
971 	GSList *ptr;
972 
973 	g_return_if_fail (GOG_IS_CHART (chart));
974 	g_return_if_fail (chart->cardinality_valid);
975 
976 	for (ptr = chart->plots ; ptr != NULL ; ptr = ptr->next)
977 		gog_plot_foreach_elem (ptr->data, only_visible, handler, data);
978 }
979 
980 
981 /**
982  * gog_chart_get_plots:
983  * @chart: #GogChart
984  *
985  * Returns: (element-type GogPlot) (transfer none): the list of the plots
986  * in @chart.
987  **/
988 GSList *
gog_chart_get_plots(GogChart const * chart)989 gog_chart_get_plots (GogChart const *chart)
990 {
991 	g_return_val_if_fail (GOG_IS_CHART (chart), NULL);
992 	return chart->plots;
993 }
994 
995 GogAxisSet
gog_chart_get_axis_set(GogChart const * chart)996 gog_chart_get_axis_set (GogChart const *chart)
997 {
998 	g_return_val_if_fail (GOG_IS_CHART (chart), GOG_AXIS_SET_UNKNOWN);
999 	return chart->axis_set;
1000 }
1001 
1002 gboolean
gog_chart_axis_set_is_valid(GogChart const * chart,GogAxisSet type)1003 gog_chart_axis_set_is_valid (GogChart const *chart, GogAxisSet type)
1004 {
1005 	GSList *ptr;
1006 
1007 	g_return_val_if_fail (GOG_IS_CHART (chart), FALSE);
1008 
1009 	for (ptr = chart->plots ; ptr != NULL ; ptr = ptr->next)
1010 		if (!gog_plot_axis_set_is_valid (ptr->data, type))
1011 			return FALSE;
1012 	return TRUE;
1013 }
1014 
1015 static void
gog_chart_add_axis(GogChart * chart,GogAxisType type)1016 gog_chart_add_axis (GogChart *chart, GogAxisType type)
1017 {
1018 	unsigned i = G_N_ELEMENTS (roles);
1019 	while (i-- > 0)
1020 		if (roles[i].user.i == (int)type) {
1021 			gog_object_add_by_role (GOG_OBJECT (chart), roles + i, NULL);
1022 			return;
1023 		}
1024 	g_warning ("unknown axis type %d", type);
1025 }
1026 
1027 gboolean
gog_chart_axis_set_assign(GogChart * chart,GogAxisSet axis_set)1028 gog_chart_axis_set_assign (GogChart *chart, GogAxisSet axis_set)
1029 {
1030 	GogAxis *axis;
1031 	GSList  *ptr;
1032 	GogAxisType type;
1033 
1034 	g_return_val_if_fail (GOG_IS_CHART (chart), FALSE);
1035 
1036 	if (chart->axis_set == axis_set)
1037 		return TRUE;
1038 	chart->axis_set = axis_set;
1039 
1040 	if (axis_set == GOG_AXIS_SET_UNKNOWN)
1041 		return TRUE;
1042 
1043 	/* Add at least 1 instance of any required axis */
1044 	for (type = 0 ; type < GOG_AXIS_TYPES ; type++)
1045 		if ((axis_set & (1 << type))) {
1046 			GSList *tmp = gog_chart_get_axes (chart, type);
1047 			if (tmp == NULL)
1048 				gog_chart_add_axis (chart, type);
1049 			else
1050 				g_slist_free (tmp);
1051 		}
1052 
1053 	/* link the plots */
1054 	for (ptr = chart->plots ; ptr != NULL ; ptr = ptr->next)
1055 		if (!gog_plot_axis_set_assign (ptr->data, axis_set))
1056 			return FALSE;
1057 
1058 	/* add any existing axis that do not fit this scheme to the set */
1059 	for (ptr = GOG_OBJECT (chart)->children ; ptr != NULL ; ) {
1060 		axis = ptr->data;
1061 		ptr = ptr->next; /* list may change under us */
1062 		if (GOG_IS_AXIS (axis)) {
1063 			type = -1;
1064 			g_object_get (G_OBJECT (axis), "type", &type, NULL);
1065 			if (type < 0 || type >= GOG_AXIS_TYPES) {
1066 				g_warning ("Invalid axis");
1067 				continue;
1068 			}
1069 
1070 			if (0 == (axis_set & (1 << type))) {
1071 				/* We used to delete those axes but if the first plot use a
1072 				 restricted set, all other axes are lost, see #708292 */
1073 				chart->axis_set |= 1 << type;
1074 			}
1075 		}
1076 	}
1077 
1078 	return TRUE;
1079 }
1080 
1081 /**
1082  * gog_chart_get_axes:
1083  * @chart: #GogChart
1084  * @target: #GogAxisType
1085  *
1086  * Returns: (element-type GogAxis) (transfer container): a list which the
1087  * caller must free of all axis of type @target
1088  * associated with @chart.
1089  **/
1090 GSList *
gog_chart_get_axes(GogChart const * chart,GogAxisType target)1091 gog_chart_get_axes (GogChart const *chart, GogAxisType target)
1092 {
1093 	GSList *ptr, *res = NULL;
1094 	GogAxis *axis;
1095 	int type;
1096 
1097 	g_return_val_if_fail (GOG_IS_CHART (chart), NULL);
1098 
1099 	for (ptr = GOG_OBJECT (chart)->children ; ptr != NULL ; ptr = ptr->next) {
1100 		axis = ptr->data;
1101 		if (GOG_IS_AXIS (axis)) {
1102 			type = -1;
1103 			g_object_get (G_OBJECT (axis), "type", &type, NULL);
1104 			if (type < 0 || type >= GOG_AXIS_TYPES) {
1105 				g_warning ("Invalid axis");
1106 				continue;
1107 			}
1108 			if (type == target)
1109 				res = g_slist_prepend (res, axis);
1110 		}
1111 	}
1112 
1113 	return res;
1114 }
1115 
1116 /**
1117  * gog_chart_get_grid:
1118  * @chart: #GogChart
1119  *
1120  * Returns: (transfer none): the grid associated with @chart if one exists
1121  * otherwise NULL.
1122  **/
1123 GogGrid  *
gog_chart_get_grid(GogChart const * chart)1124 gog_chart_get_grid (GogChart const *chart)
1125 {
1126 	g_return_val_if_fail (GOG_IS_CHART (chart), NULL);
1127 	return GOG_GRID (chart->grid);
1128 }
1129 
1130 gboolean
gog_chart_is_3d(GogChart const * chart)1131 gog_chart_is_3d (GogChart const *chart)
1132 {
1133 	return chart->axis_set == GOG_AXIS_SET_XYZ;
1134 }
1135 
1136 /*********************************************************************/
1137 
1138 static void
gog_chart_view_3d_process(GogView * view,GogViewAllocation * bbox)1139 gog_chart_view_3d_process (GogView *view, GogViewAllocation *bbox)
1140 {
1141 	/* A XYZ axis set in supposed. If new sets (cylindrical, spherical or
1142 	other are added, we'll need to change this code */
1143 	GogViewAllocation tmp = *bbox;
1144 	GogAxis *axisX, *axisY, *axisZ, *ref = NULL;
1145 	GSList *axes;
1146 	double xmin, xmax, ymin, ymax, zmin, zmax;
1147 	double o[3], x[3], y[3], z[3], tg, d;
1148 	Gog3DBox *box;
1149 	Gog3DBoxView *box_view;
1150 	GogChart *chart = GOG_CHART (gog_view_get_model (view));
1151 	GogObject *obj = gog_object_get_child_by_name (GOG_OBJECT (chart), "3D-Box");
1152 	GSList *ptr;
1153 	GogView *child;
1154 	GogViewPadding padding;
1155 	GogAxisMetrics xm, ym, zm;
1156 
1157 	if (!obj) {
1158 		obj = g_object_new (GOG_3D_BOX_TYPE, NULL);
1159 		gog_object_add_by_name (GOG_OBJECT (chart), "3D-Box", obj);
1160 	}
1161 	box = GOG_3D_BOX (obj);
1162 	box_view = GOG_3D_BOX_VIEW (gog_view_find_child_view (view, obj));
1163 
1164 	/* Only use the first of the axes. */
1165 	axes = gog_chart_get_axes (chart, GOG_AXIS_X);
1166 	axisX = GOG_AXIS (axes->data);
1167 	xm = gog_axis_get_metrics (axisX);
1168 	if (xm != GOG_AXIS_METRICS_DEFAULT)
1169 		ref = gog_axis_get_ref_axis (axisX);
1170 	g_slist_free (axes);
1171 	gog_axis_get_bounds (axisX, &xmin, &xmax);
1172 	axes = gog_chart_get_axes (chart, GOG_AXIS_Y);
1173 	axisY = GOG_AXIS (axes->data);
1174 	ym = gog_axis_get_metrics (axisY);
1175 	if (ym != GOG_AXIS_METRICS_DEFAULT && ref == NULL)
1176 		ref = gog_axis_get_ref_axis (axisY);
1177 	g_slist_free (axes);
1178 	gog_axis_get_bounds (axisY, &ymin, &ymax);
1179 	axes = gog_chart_get_axes (chart, GOG_AXIS_Z);
1180 	axisZ = GOG_AXIS (axes->data);
1181 	zm = gog_axis_get_metrics (axisZ);
1182 	if (zm != GOG_AXIS_METRICS_DEFAULT && ref == NULL)
1183 		ref = gog_axis_get_ref_axis (axisZ);
1184 	g_slist_free (axes);
1185 	gog_axis_get_bounds (axisZ, &zmin, &zmax);
1186 	/* define the 3d box */
1187 	if (ref == NULL) {
1188 		box_view->dz = tmp.h;
1189 		if (ymax - ymin > xmax - xmin) {
1190 			box_view->dy = tmp.w;
1191 			box_view->dx = (xmax - xmin) / (ymax - ymin) * tmp.w;
1192 		} else {
1193 			box_view->dx = tmp.w;
1194 			box_view->dy = (ymax - ymin) / (xmax - xmin) * tmp.w;
1195 		}
1196 	} else {
1197 		double ref_length, ref_tick_dist, xspan, yspan, zspan;
1198 		gog_axis_get_bounds (ref, &ref_length, &xspan);
1199 		ref_length -= xspan;
1200 		ref_tick_dist = gog_axis_get_major_ticks_distance (ref);
1201 		xspan = xmax - xmin;
1202 		if (xm == GOG_AXIS_METRICS_RELATIVE_TICKS) {
1203 			double ratio, tick_dist = gog_axis_get_major_ticks_distance (axisX);
1204 			g_object_get (axisX, "metrics-ratio", &ratio, NULL);
1205 			xspan = (xmax - xmin) / tick_dist * ref_tick_dist * ratio;
1206 		}
1207 		yspan = ymax - ymin;
1208 		if (ym == GOG_AXIS_METRICS_RELATIVE_TICKS) {
1209 			double ratio, tick_dist = gog_axis_get_major_ticks_distance (axisY);
1210 			g_object_get (axisY, "metrics-ratio", &ratio, NULL);
1211 			yspan = (ymax - ymin) / tick_dist * ref_tick_dist * ratio;
1212 		}
1213 		zspan = zmax - zmin;
1214 		if (zm == GOG_AXIS_METRICS_RELATIVE_TICKS) {
1215 			double ratio, tick_dist = gog_axis_get_major_ticks_distance (axisZ);
1216 			g_object_get (axisZ, "metrics-ratio", &ratio, NULL);
1217 			zspan = (zmax - zmin) / tick_dist * ref_tick_dist * ratio;
1218 		}
1219 		if (ref == axisZ) {
1220 			gboolean xrel = FALSE;
1221 			box_view->dz = tmp.h;
1222 			switch (xm) {
1223 			case GOG_AXIS_METRICS_RELATIVE:
1224 			case GOG_AXIS_METRICS_RELATIVE_TICKS:
1225 				box_view->dx = xspan / zspan * tmp.h;
1226 				if (box_view->dx > tmp.w) {
1227 					box_view->dz *= tmp.w / box_view->dx;
1228 					box_view->dx = tmp.w;
1229 				}
1230 					xrel = TRUE;
1231 				break;
1232 			default:
1233 				box_view->dx = tmp.w;
1234 				break;
1235 			}
1236 			switch (ym) {
1237 			case GOG_AXIS_METRICS_RELATIVE:
1238 			case GOG_AXIS_METRICS_RELATIVE_TICKS:
1239 				box_view->dy = yspan / zspan * box_view->dz;
1240 				if (box_view->dy > tmp.w) {
1241 					box_view->dz *= tmp.w / box_view->dy;
1242 					if (xrel)
1243 						box_view->dx *= tmp.w / box_view->dy;
1244 					box_view->dy = tmp.w;
1245 				}
1246 				break;
1247 			default:
1248 				box_view->dy = tmp.w;
1249 				break;
1250 			}
1251 		} else {
1252 			if (yspan > xspan) {
1253 				box_view->dy = tmp.w;
1254 				box_view->dx = xspan / yspan * tmp.w;
1255 			} else {
1256 				box_view->dx = tmp.w;
1257 				box_view->dy = yspan / xspan * tmp.w;
1258 			}
1259 			if (zm == GOG_AXIS_METRICS_DEFAULT)
1260 				box_view->dz = tmp.h;
1261 			else
1262 				box_view->dz = (ref == axisX)?
1263 								zspan / xspan * box_view->dx:
1264 								zspan / yspan * box_view->dy;
1265 		}
1266 	}
1267 
1268 	/* now compute the position of each vertex, ignoring the fov */
1269 	go_matrix3x3_transform (&box->mat, -box_view->dx, -box_view->dy, -box_view->dz, o, o + 1, o + 2);
1270 	go_matrix3x3_transform (&box->mat, box_view->dx, -box_view->dy, -box_view->dz, x, x + 1, x + 2);
1271 	go_matrix3x3_transform (&box->mat, -box_view->dx, box_view->dy, -box_view->dz, y, y + 1, y + 2);
1272 	go_matrix3x3_transform (&box->mat, -box_view->dx, -box_view->dy, box_view->dz, z, z + 1, z + 2);
1273 	/* for each diagonal, we need to take the vertex closer to the view point */
1274 	if (o[1] > 0) {
1275 		o[0] = -o[0];
1276 		o[1] = -o[1];
1277 		o[2] = -o[2];
1278 	}
1279 	if (x[1] > 0) {
1280 		x[0] = -x[0];
1281 		x[1] = -x[1];
1282 		x[2] = -x[2];
1283 	}
1284 	if (y[1] > 0) {
1285 		y[0] = -y[0];
1286 		y[1] = -y[1];
1287 		y[2] = -y[2];
1288 	}
1289 	if (z[1] > 0) {
1290 		z[0] = -z[0];
1291 		z[1] = -z[1];
1292 		z[2] = -z[2];
1293 	}
1294 	/* if the fov is positive, calculate the position of the viewpoint */
1295 	if (box->fov > 0.) {
1296 		tg = tan (box->fov / 2.);
1297 		box_view->r = -sqrt (o[0] * o[0] + o[2] * o[2]) / tg + o[1];
1298 		d = -sqrt (x[0] * x[0] + x[2] * x[2]) / tg + x[1];
1299 		if (d < box_view->r)
1300 			box_view->r = d;
1301 		d = -sqrt (y[0] * y[0] + y[2] * y[2]) / tg + y[1];
1302 		if (d < box_view->r)
1303 			box_view->r = d;
1304 		d = -sqrt (z[0] *z[0] + z[2] * z[2]) / tg + z[1];
1305 		if (d < box_view->r)
1306 			box_view->r = d;
1307 		/* also calculate the reduction factor we need to make things fit in the bbox */
1308 		xmax = fabs (o[0]) / (1. - o[1] / box_view->r);
1309 		zmax = fabs (o[2]) / (1. - o[1] / box_view->r);
1310 		d = fabs (x[0]) / (1. - x[1] / box_view->r);
1311 		if (d > xmax)
1312 			xmax = d;
1313 		d = fabs (x[2]) / (1. - x[1] / box_view->r);
1314 		if (d > zmax)
1315 			zmax = d;
1316 		d = fabs (y[0]) / (1. - y[1] / box_view->r);
1317 		if (d > xmax)
1318 			xmax = d;
1319 		d = fabs (y[2]) / (1. - y[1] / box_view->r);
1320 		if (d > zmax)
1321 			zmax = d;
1322 		d = fabs (z[0]) / (1. - z[1] / box_view->r);
1323 		if (d > xmax)
1324 			xmax = d;
1325 		d = fabs (z[2]) / (1. - z[1] / box_view->r);
1326 		if (d > zmax)
1327 			zmax = d;
1328 	} else {
1329 	    /* calculate the reduction factor we need to make things fit in the bbox */
1330 		xmax = fabs (o[0]);
1331 		zmax = fabs (o[2]);
1332 		d = fabs (x[0]);
1333 		if (d > xmax)
1334 			xmax = d;
1335 		d = fabs (x[2]);
1336 		if (d > zmax)
1337 			zmax = d;
1338 		d = fabs (y[0]);
1339 		if (d > xmax)
1340 			xmax = d;
1341 		d = fabs (y[2]);
1342 		if (d > zmax)
1343 			zmax = d;
1344 		d = fabs (z[0]);
1345 		if (d > xmax)
1346 			xmax = d;
1347 		d = fabs (z[2]);
1348 		if (d > zmax)
1349 			zmax = d;
1350 	}
1351 	/* use d and tg as x and z ratios, respectively */
1352 	d = xmax / tmp.w;
1353 	tg = zmax / tmp.h;
1354 	box_view->ratio = (d > tg)? d: tg;
1355 
1356 	gog_view_padding_request (view, bbox, &padding);
1357 
1358 	if (!chart->is_plot_area_manual) {
1359 		bbox->x += padding.wl;
1360 		bbox->w -= padding.wl + padding.wr;
1361 		bbox->y += padding.ht;
1362 		bbox->h -= padding.ht + padding.hb;
1363 	} else {
1364 		tmp.x -= padding.wl;
1365 		tmp.w += padding.wl + padding.wr;
1366 		tmp.y -= padding.ht;
1367 		tmp.h += padding.ht + padding.hb;
1368 	}
1369 
1370 	/* Recalculating ratio */
1371 	d = xmax / bbox->w;
1372 	tg = zmax / bbox->h;
1373 	box_view->ratio = (d > tg)? d: tg;
1374 
1375 	for (ptr = view->children; ptr != NULL ; ptr = ptr->next) {
1376 		child = ptr->data;
1377 		if (GOG_POSITION_IS_PADDING (child->model->position)) {
1378 			gog_view_size_allocate (child, &tmp);
1379 		}
1380 	}
1381 
1382 	/* by default, overlay all GOG_POSITION_SPECIAL children in residual */
1383 	for (ptr = view->children; ptr != NULL ; ptr = ptr->next) {
1384 		child = ptr->data;
1385 		if (GOG_POSITION_IS_SPECIAL (child->model->position))
1386 			gog_view_size_allocate (child, bbox);
1387 	}
1388 }
1389 
1390 typedef struct {
1391 	GogOutlinedView base;
1392 
1393 	GogViewAllocation	plot_area;
1394 } GogChartView;
1395 typedef GogOutlinedViewClass	GogChartViewClass;
1396 
1397 #define GOG_TYPE_CHART_VIEW	(gog_chart_view_get_type ())
1398 #define GOG_CHART_VIEW(o)	(G_TYPE_CHECK_INSTANCE_CAST ((o), GOG_TYPE_CHART_VIEW, GogChartView))
1399 #define GOG_IS_CHART_VIEW(o)	(G_TYPE_CHECK_INSTANCE_TYPE ((o), GOG_TYPE_CHART_VIEW))
1400 
1401 static GogViewClass *cview_parent_klass;
1402 
1403 static void
gog_chart_view_size_allocate(GogView * view,GogViewAllocation const * bbox)1404 gog_chart_view_size_allocate (GogView *view, GogViewAllocation const *bbox)
1405 {
1406 	GSList *ptr;
1407 	GogView *child;
1408 	GogChartView *chart_view = GOG_CHART_VIEW (view);
1409 	GogViewAllocation tmp, *plot_area = &chart_view->plot_area;
1410 	GogViewPadding padding;
1411 	GogChart *chart = GOG_CHART (gog_view_get_model (view));
1412 
1413 	(cview_parent_klass->size_allocate) (view, bbox);
1414 
1415 	if (chart->is_plot_area_manual) {
1416 		plot_area->x = bbox->x + chart->plot_area.x * bbox->w;
1417 		plot_area->y = bbox->y + chart->plot_area.y * bbox->h;
1418 		plot_area->w = chart->plot_area.w * bbox->w;
1419 		plot_area->h = chart->plot_area.h * bbox->h;
1420 	} else
1421 		*plot_area = view->residual;
1422 
1423 	/* special treatment for 3d charts */
1424 	if (gog_chart_is_3d (chart)) {
1425 		gog_chart_view_3d_process (view, plot_area);
1426 		return;
1427 	}
1428 
1429 	tmp = *plot_area;
1430 	gog_view_padding_request (view, plot_area, &padding);
1431 
1432 	if (!chart->is_plot_area_manual) {
1433 		plot_area->x += padding.wl;
1434 		plot_area->w -= padding.wl + padding.wr;
1435 		plot_area->y += padding.ht;
1436 		plot_area->h -= padding.ht + padding.hb;
1437 	} else {
1438 		tmp.x -= padding.wl;
1439 		tmp.w += padding.wl + padding.wr;
1440 		tmp.y -= padding.ht;
1441 		tmp.h += padding.ht + padding.hb;
1442 	}
1443 
1444 	for (ptr = view->children; ptr != NULL ; ptr = ptr->next) {
1445 		child = ptr->data;
1446 		if (GOG_POSITION_IS_PADDING (child->model->position)) {
1447 			gog_view_size_allocate (child, &tmp);
1448 		}
1449 	}
1450 
1451 	/* by default, overlay all GOG_POSITION_SPECIAL children in residual */
1452 	for (ptr = view->children; ptr != NULL ; ptr = ptr->next) {
1453 		child = ptr->data;
1454 		if (GOG_POSITION_IS_SPECIAL (child->model->position))
1455 			gog_view_size_allocate (child, plot_area);
1456 	}
1457 }
1458 
1459 static void
gog_chart_view_init(GogChartView * cview)1460 gog_chart_view_init (GogChartView *cview)
1461 {
1462 }
1463 
1464 static void
grid_line_render(GSList * start_ptr,GogViewAllocation const * bbox)1465 grid_line_render (GSList *start_ptr, GogViewAllocation const *bbox)
1466 {
1467 	GSList *ptr, *child_ptr;
1468 	GSList *minor_grid_lines = NULL;
1469 	GSList *major_grid_lines = NULL;
1470 	GogView *child_view, *axis_child_view;
1471 
1472 	for (ptr = start_ptr; ptr != NULL; ptr = ptr->next) {
1473 		child_view = ptr->data;
1474 		if (GOG_IS_AXIS (child_view->model)) {
1475 			for (child_ptr = child_view->children; child_ptr != NULL; child_ptr = child_ptr->next) {
1476 				axis_child_view = child_ptr->data;
1477 				if (GOG_IS_GRID_LINE (axis_child_view->model)) {
1478 					if (gog_grid_line_is_minor (GOG_GRID_LINE (axis_child_view->model)))
1479 						minor_grid_lines = g_slist_prepend (minor_grid_lines,
1480 										    axis_child_view);
1481 					else
1482 						major_grid_lines = g_slist_prepend (major_grid_lines,
1483 										    axis_child_view);
1484 				}
1485 				else if (GOG_IS_AXIS_LINE (axis_child_view->model)) {
1486 					GogView *grid_child_view;
1487 					GSList *lptr;
1488 					for (lptr = axis_child_view->children; lptr != NULL; lptr = lptr->next) {
1489 						grid_child_view = lptr->data;
1490 					if (GOG_IS_GRID_LINE (grid_child_view->model)) {
1491 						if (gog_grid_line_is_minor (GOG_GRID_LINE (grid_child_view->model)))
1492 							minor_grid_lines = g_slist_prepend (minor_grid_lines,
1493 												grid_child_view);
1494 						else
1495 							major_grid_lines = g_slist_prepend (major_grid_lines,
1496 												grid_child_view);
1497 				}
1498 
1499 					}
1500 				}
1501 			}
1502 		}
1503 	}
1504 
1505 	/* Render stripes, minor first then major */
1506 	for (ptr = minor_grid_lines; ptr != NULL; ptr = ptr->next) {
1507 		gog_grid_line_view_render_stripes (ptr->data);
1508 	}
1509 	for (ptr = major_grid_lines; ptr != NULL; ptr = ptr->next) {
1510 		gog_grid_line_view_render_stripes (ptr->data);
1511 	}
1512 
1513 	/* Render lines, minor first then major */
1514 	for (ptr = minor_grid_lines; ptr != NULL; ptr = ptr->next) {
1515 		gog_grid_line_view_render_lines (ptr->data);
1516 	}
1517 	for (ptr = major_grid_lines; ptr != NULL; ptr = ptr->next) {
1518 		gog_grid_line_view_render_lines (ptr->data);
1519 	}
1520 
1521 	g_slist_free (minor_grid_lines);
1522 	g_slist_free (major_grid_lines);
1523 }
1524 
1525 static void
plot_render(GogView * view,GogViewAllocation const * bbox,GogPlotRenderingOrder order)1526 plot_render (GogView *view, GogViewAllocation const *bbox, GogPlotRenderingOrder order)
1527 {
1528 	GSList *ptr;
1529 	GogView *child_view;
1530 
1531 	/* Render some plots before axes */
1532 	for (ptr = view->children ; ptr != NULL ; ptr = ptr->next) {
1533 		child_view = ptr->data;
1534 		if (GOG_IS_PLOT (child_view->model) &&
1535 		    GOG_PLOT (child_view->model)->rendering_order == order)
1536 			gog_view_render	(ptr->data, bbox);
1537 	}
1538 }
1539 
1540 static void
gog_chart_view_render(GogView * view,GogViewAllocation const * bbox)1541 gog_chart_view_render (GogView *view, GogViewAllocation const *bbox)
1542 {
1543 	GSList *ptr;
1544 	GogView *child_view;
1545 	gboolean grid_line_rendered = FALSE;
1546 	GogChart *chart = GOG_CHART (gog_view_get_model (view));
1547 
1548 	cview_parent_klass->render (view, bbox);
1549 
1550 	if (gog_chart_is_3d (chart)) {
1551 		for (ptr = view->children ; ptr != NULL ; ptr = ptr->next) {
1552 			child_view = ptr->data;
1553 			if (!GOG_IS_AXIS (child_view->model) && !GOG_IS_PLOT (child_view->model) && !GOG_IS_LABEL (child_view->model))
1554 				gog_view_render	(ptr->data, bbox);
1555 		}
1556 		/* now render plot and axes */
1557 		for (ptr = view->children ; ptr != NULL ; ptr = ptr->next) {
1558 			child_view = ptr->data;
1559 			if (!GOG_IS_AXIS (child_view->model))
1560 				continue;
1561 			gog_view_render (ptr->data, bbox);
1562 			grid_line_render (ptr, bbox);
1563 		}
1564 		for (ptr = view->children ; ptr != NULL ; ptr = ptr->next) {
1565 			child_view = ptr->data;
1566 			if (!GOG_IS_PLOT (child_view->model))
1567 				continue;
1568 			gog_view_render	(ptr->data, bbox);
1569 		}
1570 	} else {
1571 		/* KLUDGE: render grid lines before axis */
1572 		for (ptr = view->children ; ptr != NULL ; ptr = ptr->next) {
1573 			child_view = ptr->data;
1574 			if (!grid_line_rendered && GOG_IS_AXIS (child_view->model)) {
1575 				plot_render (view, bbox, GOG_PLOT_RENDERING_BEFORE_GRID);
1576 				grid_line_render (ptr, bbox);
1577 				plot_render (view, bbox, GOG_PLOT_RENDERING_BEFORE_AXIS);
1578 				grid_line_rendered = TRUE;
1579 			}
1580 			if (GOG_IS_PLOT (child_view->model)) {
1581 			    if (!GOG_PLOT (child_view->model)->rendering_order)
1582 				gog_view_render	(ptr->data, bbox);
1583 			} else if (!GOG_IS_LABEL (child_view->model))
1584 				gog_view_render	(ptr->data, bbox);
1585 		}
1586 	}
1587 	for (ptr = view->children ; ptr != NULL ; ptr = ptr->next) {
1588 		child_view = ptr->data;
1589 		if (!GOG_IS_LABEL (child_view->model))
1590 			continue;
1591 		gog_view_render	(ptr->data, bbox);
1592 	}
1593 }
1594 
1595 static void
gog_chart_view_class_init(GogChartViewClass * gview_klass)1596 gog_chart_view_class_init (GogChartViewClass *gview_klass)
1597 {
1598 	GogViewClass *view_klass = (GogViewClass *) gview_klass;
1599 	GogOutlinedViewClass *oview_klass = (GogOutlinedViewClass *) gview_klass;
1600 
1601 	cview_parent_klass = g_type_class_peek_parent (gview_klass);
1602 
1603 	view_klass->size_allocate   	= gog_chart_view_size_allocate;
1604 	view_klass->render 		= gog_chart_view_render;
1605 	view_klass->clip 		= FALSE;
1606 	oview_klass->call_parent_render = FALSE;
1607 }
1608 
GSF_CLASS(GogChartView,gog_chart_view,gog_chart_view_class_init,gog_chart_view_init,GOG_TYPE_OUTLINED_VIEW)1609 static GSF_CLASS (GogChartView, gog_chart_view,
1610 		  gog_chart_view_class_init, gog_chart_view_init,
1611 		  GOG_TYPE_OUTLINED_VIEW)
1612 
1613 GogViewAllocation const *
1614 gog_chart_view_get_plot_area (GogView const *view)
1615 {
1616 	g_return_val_if_fail (GOG_IS_CHART_VIEW (view), NULL);
1617 
1618 	return &(GOG_CHART_VIEW (view)->plot_area);
1619 }
1620