1 /*
2  * gog-axis.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 #include <goffice/goffice-debug.h>
25 #include "gog-axis-line-impl.h"
26 
27 #include <gsf/gsf-impl-utils.h>
28 #include <glib/gi18n-lib.h>
29 
30 #include <string.h>
31 
32 /* It's not important that this is accurate.  */
33 #define DAYS_IN_YEAR 365.25
34 
35 /* this should be per model */
36 #define PAD_HACK	4	/* pts */
37 
38 /**
39  * SECTION: gog-axis
40  * @short_description: An axis.
41  *
42  * An axis of a #GogPlot. The axis handles things like the bounds, ticks, and
43  * tick value formats.
44  * When used in plots with X/Y/Z axes, it can optionally have one
45  * #GogLabel objects in the role "Label".
46  */
47 
48 /**
49  * GogAxisPolarUnit:
50  * @GOG_AXIS_POLAR_UNIT_DEGREES: units as degrees.
51  * @GOG_AXIS_POLAR_UNIT_RADIANS: units as radians.
52  * @GOG_AXIS_POLAR_UNIT_GRADS: units as grads.
53  * @GOG_AXIS_POLAR_UNIT_MAX: maximum values, should not occur.
54  **/
55 
56 /**
57  * GogAxisTick:
58  * @position: position on the axis.
59  * @type: #GogAxisTickTypes
60  * @str: label, might be rich text.
61  **/
62 
63 /**
64  * GogAxisElemType:
65  * @GOG_AXIS_ELEM_MIN: minimum value.
66  * @GOG_AXIS_ELEM_MAX: maximum value.
67  * @GOG_AXIS_ELEM_MAJOR_TICK: distance between two major ticks.
68  * @GOG_AXIS_ELEM_MINOR_TICK: distance between two minor ticks.
69  * @GOG_AXIS_ELEM_CROSS_POINT: position of the other axis crossing.
70  * @GOG_AXIS_ELEM_MAX_ENTRY: maximum value, should not occur.
71  *
72  * The indices of the #GOData associated to the axis.
73  **/
74 
75 /**
76  * GogAxisMetrics:
77  * @GOG_AXIS_METRICS_INVALID: invalid.
78  * @GOG_AXIS_METRICS_DEFAULT: default.
79  * @GOG_AXIS_METRICS_ABSOLUTE: absolute distance between major ticks.
80  * @GOG_AXIS_METRICS_RELATIVE: relative to another axis.
81  * @GOG_AXIS_METRICS_RELATIVE_TICKS: relative distance between ticks related to
82  * another axis.
83  * @GOG_AXIS_METRICS_MAX: first unused value.
84  **/
85 
86 
87 static struct {
88 	GogAxisPolarUnit unit;
89 	const char 	*name;
90 	double		 perimeter;
91 	const char	*xl_format;
92 	double		 auto_minimum;
93 	double		 auto_maximum;
94 	double		 auto_major;
95 	double		 auto_minor;
96 } polar_units[GOG_AXIS_POLAR_UNIT_MAX] = {
97 	{ GOG_AXIS_POLAR_UNIT_DEGREES, N_("Degrees"), 360.0,	"0\"°\"",   0.0, 360.0,     30.0,      10.0},
98 	{ GOG_AXIS_POLAR_UNIT_RADIANS, N_("Radians"), 2 * M_PI,	"?pi/??", -M_PI,  M_PI, M_PI/4.0, M_PI/16.0},
99 	{ GOG_AXIS_POLAR_UNIT_GRADS,   N_("Grads"),   400.0,	"General",  0.0, 400.0,     50.0,      10.0}
100 };
101 
102 #define GOG_AXIS_CIRCULAR_ROTATION_MIN -180.0
103 #define GOG_AXIS_CIRCULAR_ROTATION_MAX  180.0
104 
105 typedef struct _GogAxisMapDesc GogAxisMapDesc;
106 
107 static struct {
108 	GogAxisMetrics metrics;
109 	const char 	*name;
110 } metrics_desc[GOG_AXIS_METRICS_MAX] = {
111 	{ GOG_AXIS_METRICS_DEFAULT, "default"},
112 	{ GOG_AXIS_METRICS_ABSOLUTE, "absolute"},
113 	{ GOG_AXIS_METRICS_RELATIVE, "relative"},
114 	{ GOG_AXIS_METRICS_RELATIVE_TICKS, "relative-ticks-distance"}
115 };
116 
117 static GogAxisMetrics
gog_axis_metrics_from_str(char const * name)118 gog_axis_metrics_from_str (char const *name)
119 {
120 	unsigned i;
121 	GogAxisMetrics ret = GOG_AXIS_METRICS_DEFAULT;
122 
123 	for (i = 0; i < GOG_AXIS_METRICS_MAX; i++) {
124 		if (strcmp (metrics_desc[i].name, name) == 0) {
125 			ret = metrics_desc[i].metrics;
126 			break;
127 		}
128 	}
129 	return ret;
130 }
131 
132 static char const *
gog_axis_metrics_as_str(GogAxisMetrics metrics)133 gog_axis_metrics_as_str (GogAxisMetrics metrics)
134 {
135 	unsigned i;
136 	char const *ret = "default";
137 
138 	for (i = 0; i < GO_LINE_MAX; i++) {
139 		if (metrics_desc[i].metrics == metrics) {
140 			ret = metrics_desc[i].name;
141 			break;
142 		}
143 	}
144 	return ret;
145 }
146 
147 struct _GogAxis {
148 	GogAxisBase	 base;
149 
150 	GogAxisType	 type;
151 	GSList		*contributors;
152 
153 	GogDatasetElement source[GOG_AXIS_ELEM_CROSS_POINT];
154 	double		  auto_bound[GOG_AXIS_ELEM_CROSS_POINT];
155 	gboolean inverted; /* apply to all map type */
156 
157 	double		min_val, max_val;
158 	double		logical_min_val, logical_max_val;
159 	GogObject	*min_contrib, *max_contrib; /* NULL means use the manual sources */
160 	gboolean	is_discrete;
161 	gboolean	center_on_ticks;
162 	GOData         *labels;
163 	GogPlot	       *plot_that_supplied_labels;
164 	GOFormat       *format, *assigned_format;
165 
166 	GogAxisMapDesc const 	*map_desc;
167 	GogAxisMapDesc const 	*actual_map_desc;
168 
169 	const GODateConventions *date_conv;
170 
171 	GogAxisPolarUnit	 polar_unit;
172 	double			 circular_rotation;
173 
174 	GogAxisTick	*ticks;
175 	unsigned	 tick_nbr;
176 	double span_start, span_end;    /* percent of used area */
177 	GogAxisColorMap const *color_map;		/* color map for color and pseudo-3d axis */
178 	gboolean auto_color_map;
179 	GogColorScale *color_scale;
180 	GogAxisMetrics metrics;
181 	GogAxis *ref_axis;
182 	GSList *refering_axes;
183 	double metrics_ratio;
184 	GoUnitId unit;
185 	double display_factor;
186 };
187 
188 /*****************************************************************************/
189 
190 #define TICK_LABEL_PAD_VERT	0
191 #define TICK_LABEL_PAD_HORIZ	1
192 
193 #define GOG_AXIS_MAX_TICK_NBR				500
194 #define GOG_AXIS_LOG_AUTO_MAX_MAJOR_TICK_NBR 		8
195 #define GOG_AXIS_DISCRETE_AUTO_MAX_MAJOR_TICK_NBR 	20
196 
197 static void gog_axis_set_ticks (GogAxis *axis,int tick_nbr, GogAxisTick *ticks);
198 
199 static PangoLayout *
gog_get_layout(void)200 gog_get_layout (void)
201 {
202 	PangoContext *context = pango_context_new ();
203 	PangoLayout *layout = pango_layout_new (context);
204 	g_object_unref (context);
205 	return layout;
206 }
207 
208 static void
gog_axis_ticks_set_text(GogAxisTick * ticks,char const * str)209 gog_axis_ticks_set_text (GogAxisTick *ticks, char const *str)
210 {
211 	go_string_unref (ticks->str);
212 	ticks->str = go_string_new (str);
213 }
214 
215 static void
gog_axis_ticks_set_markup(GogAxisTick * ticks,char const * str,PangoAttrList * l)216 gog_axis_ticks_set_markup (GogAxisTick *ticks, char const *str, PangoAttrList *l)
217 {
218 	go_string_unref (ticks->str);
219 	ticks->str = go_string_new_rich (str, -1, l, NULL);
220 }
221 
222 static GogAxisTick *
create_invalid_axis_ticks(double min,double max)223 create_invalid_axis_ticks (double min, double max)
224 {
225 	GogAxisTick *ticks;
226 
227 	ticks = g_new (GogAxisTick, 2);
228 	ticks[0].position = min;
229 	ticks[1].position = max;
230 	ticks[0].type = ticks[1].type = GOG_AXIS_TICK_MAJOR;
231 	ticks[0].str = ticks[1].str = NULL;
232 	gog_axis_ticks_set_text (&ticks[0], "##");
233 	gog_axis_ticks_set_text (&ticks[1], "##");
234 
235 	return ticks;
236 }
237 
238 const GODateConventions *
gog_axis_get_date_conv(GogAxis const * axis)239 gog_axis_get_date_conv (GogAxis const *axis)
240 {
241 	g_return_val_if_fail (GOG_IS_AXIS (axis), NULL);
242 
243 	return axis->date_conv;
244 }
245 
246 /**
247  * gog_axis_get_effective_format:
248  * @axis: #GogAxis
249  *
250  * Returns: (transfer none): the #GOFormat used for the axis labels. Differs
251  * from gog_axis_get_format in that it never returns a general format
252  * (see #go_format_is_general).
253  **/
254 GOFormat *
gog_axis_get_effective_format(GogAxis const * axis)255 gog_axis_get_effective_format (GogAxis const *axis)
256 {
257 	g_return_val_if_fail (GOG_IS_AXIS (axis), NULL);
258 
259 	if (axis->assigned_format &&
260 	    !go_format_is_general (axis->assigned_format))
261 		return axis->assigned_format;
262 	return axis->format;
263 }
264 
265 static void
axis_format_value(GogAxis * axis,double val,GOString ** str,gboolean do_scale)266 axis_format_value (GogAxis *axis, double val, GOString **str,
267 		   gboolean do_scale)
268 {
269 	GOFormat *fmt = gog_axis_get_effective_format (axis);
270 	const GODateConventions *date_conv = axis->date_conv;
271 	GOFormatNumberError err;
272 	PangoLayout *layout = gog_get_layout ();
273 	int chars;
274 
275 	g_return_if_fail (layout != NULL);
276 
277 	go_string_unref (*str);
278 
279 	if (do_scale)
280 		val /= axis->display_factor;
281 	else {
282 		/* Caller handled scaling, except for sign.  */
283 		if (axis->display_factor < 0)
284 			val = 0 - val;
285 	}
286 
287 	// If no format is explicitly set, avoid using excess precision
288 	chars = !fmt || go_format_is_general (fmt)
289 		? 10 + (val < 0)
290 		: -1;
291 
292 	err = go_format_value_gstring
293 		(layout, NULL,
294 		 go_format_measure_strlen,
295 		 go_font_metrics_unit,
296 		 fmt,
297 		 val, 'F', NULL, NULL,
298 		 chars, date_conv, TRUE);
299 	if (err)
300 		*str = go_string_new ("#####");
301 	else {
302 		*str = go_string_new_rich
303 			(pango_layout_get_text (layout), -1,
304 			 pango_attr_list_ref
305 			 (pango_layout_get_attributes (layout)),
306 			 NULL);
307 		*str = go_string_trim (*str, TRUE);
308 	}
309 
310 	g_object_unref (layout);
311 }
312 
313 static gboolean
split_date(GogAxis * axis,double val,GDate * date)314 split_date (GogAxis *axis, double val, GDate *date)
315 {
316 	if (fabs (val) >= G_MAXINT) {
317 		g_date_clear (date, 1);
318 		return TRUE;
319 	}
320 
321 	go_date_serial_to_g (date, (int)val, axis->date_conv);
322 	return !g_date_valid (date);
323 }
324 
325 /*****************************************************************************/
326 
327 struct _GogAxisMap {
328 	GogAxis		*axis;
329 	GogAxisMapDesc	const *desc;
330 	gpointer	 data;
331 	gboolean	 is_valid;	/* Default to FALSE if desc::init == NULL */
332 	unsigned     ref_count;
333 };
334 
335 struct _GogAxisMapDesc {
336 	double 		(*map) 		 (GogAxisMap *map, double value);
337 	double 		(*map_to_view)   (GogAxisMap *map, double value);
338 	double 		(*map_derivative_to_view)   (GogAxisMap *map, double value);
339 	double 		(*map_from_view) (GogAxisMap *map, double value);
340 	gboolean	(*map_finite)    (double value);
341 	double		(*map_baseline)  (GogAxisMap *map);
342 	void		(*map_bounds)	 (GogAxisMap *map, double *minimum, double *maximum);
343 	gboolean 	(*init) 	 (GogAxisMap *map, double offset, double length);
344 	void		(*destroy) 	 (GogAxisMap *map);
345 
346 	/*
347 	 * Refine the description, for example by picking a new auto_bound
348 	 * method based on format.
349 	 */
350 	const GogAxisMapDesc* (*subclass) (GogAxis *axis, const GogAxisMapDesc *desc);
351 
352 	/*
353 	 * Calculate graph bounds and tick sizes based on data minimum and
354 	 * maximum.
355 	 */
356 	void		(*auto_bound) 	 (GogAxis *axis,
357 					  double minimum, double maximum,
358 					  double *bound);
359 
360 	void		(*calc_ticks) 	 (GogAxis *axis);
361 
362 	GOFormat *      (*get_dim_format)(GogAxis *axis, unsigned dim);
363 
364 	char const	*name;
365 	char const	*description;
366 };
367 
368 /*
369  * Discrete mapping
370  */
371 
372 typedef struct
373 {
374 	double min;
375 	double max;
376 	double scale;
377 	double a;
378 	double b;
379 } MapData;
380 
381 static gboolean
map_discrete_init(GogAxisMap * map,double offset,double length)382 map_discrete_init (GogAxisMap *map, double offset, double length)
383 {
384 	MapData *data = map->data = g_new (MapData, 1);
385 
386 	if (gog_axis_get_bounds (map->axis, &data->min, &data->max)) {
387 		data->scale = 1.0 / (data->max - data->min);
388 		data->a = data->scale * length;
389 		data->b = offset - data->a * data->min;
390 		return TRUE;
391 	}
392 	data->min = 0.0;
393 	data->max = 1.0;
394 	data->scale = 1.0;
395 	data->a = length;
396 	data->b = offset;
397 	return FALSE;
398 }
399 
400 static double
map_discrete(GogAxisMap * map,double value)401 map_discrete (GogAxisMap *map, double value)
402 {
403 	const MapData *data = map->data;
404 
405 	return (value - data->min) * data->scale;
406 }
407 
408 static double
map_discrete_to_view(GogAxisMap * map,double value)409 map_discrete_to_view (GogAxisMap *map, double value)
410 {
411 	const MapData *data = map->data;
412 
413 	return map->axis->inverted ?
414 		(data->min + data->max - value) * data->a + data->b :
415 		value * data->a + data->b;
416 }
417 
418 static double
map_discrete_derivative_to_view(GogAxisMap * map,double value)419 map_discrete_derivative_to_view (GogAxisMap *map, double value)
420 {
421 	/* WARNING: does this make sense? */
422 	const MapData *data = map->data;
423 
424 	return map->axis->inverted ? -data->a: data->a;
425 
426 }
427 
428 static double
map_discrete_from_view(GogAxisMap * map,double value)429 map_discrete_from_view (GogAxisMap *map, double value)
430 {
431 	const MapData *data = map->data;
432 
433 	return map->axis->inverted ?
434 		go_rint (data->min + data->max - (value - data->b) / data->a) :
435 		go_rint ((value - data->b) / data->a);
436 }
437 
438 static void
map_discrete_auto_bound(GogAxis * axis,double minimum,double maximum,double * bound)439 map_discrete_auto_bound (GogAxis *axis,
440 			 double minimum,
441 			 double maximum,
442 			 double *bound)
443 {
444 	if ((maximum - minimum) > GOG_AXIS_DISCRETE_AUTO_MAX_MAJOR_TICK_NBR)
445 		bound[GOG_AXIS_ELEM_MAJOR_TICK] =
446 		bound[GOG_AXIS_ELEM_MINOR_TICK] =
447 			go_fake_ceil ((maximum - minimum + 1.0) /
448 			      (double) GOG_AXIS_DISCRETE_AUTO_MAX_MAJOR_TICK_NBR);
449 	else
450 		bound[GOG_AXIS_ELEM_MAJOR_TICK] =
451 		bound[GOG_AXIS_ELEM_MINOR_TICK] = 1.;
452 
453 	bound[GOG_AXIS_ELEM_MIN] = minimum;
454 	bound[GOG_AXIS_ELEM_MAX] = maximum;
455 }
456 
457 static void
map_discrete_calc_ticks(GogAxis * axis)458 map_discrete_calc_ticks (GogAxis *axis)
459 {
460 	GogAxisTick *ticks;
461 	gboolean valid;
462 	double maximum, minimum;
463 	double tick_start, label_start;
464 	int tick_nbr, label_nbr;
465 	int i, j, index;
466 	int major_tick, major_label;
467 
468 	major_tick = go_rint (gog_axis_get_entry (axis, GOG_AXIS_ELEM_MAJOR_TICK, NULL));
469 	major_label = go_rint (gog_axis_get_entry (axis, GOG_AXIS_ELEM_MINOR_TICK, NULL));
470 	if (major_tick < 1)
471 		major_tick = 1;
472 	if (major_label < 1)
473 		major_label = 1;
474 
475 	valid = gog_axis_get_bounds (axis, &minimum, &maximum);
476 	if (!valid) {
477 		gog_axis_set_ticks (axis, 2, create_invalid_axis_ticks (0.0, 1.0));
478 		return;
479 	}
480 
481 	tick_start = axis->center_on_ticks ?
482 		go_fake_ceil (minimum / (double) major_tick) * major_tick :
483 		go_fake_ceil ((minimum - 0.5) / (double) major_tick) * major_tick + 0.5;
484 	label_start = go_fake_ceil (minimum / (double) major_label) * major_label;
485 	tick_nbr = go_fake_floor ((maximum - tick_start) / major_tick + 1.0);
486 	label_nbr = go_fake_floor ((maximum - label_start) / major_label + 1.0);
487 	tick_nbr = CLAMP (tick_nbr, 0, GOG_AXIS_MAX_TICK_NBR);
488 	label_nbr = CLAMP (label_nbr, 0, GOG_AXIS_MAX_TICK_NBR);
489 	if (tick_nbr < 1  && label_nbr < 1) {
490 		gog_axis_set_ticks (axis, 2, create_invalid_axis_ticks (0.0, 1.0));
491 		return;
492 	}
493 	ticks = g_new (GogAxisTick, tick_nbr + label_nbr);
494 
495 	for (i = 0; i < tick_nbr; i++) {
496 		ticks[i].position = tick_start + (double) (i) * major_tick;
497 		ticks[i].type = GOG_AXIS_TICK_MAJOR;
498 		ticks[i].str = NULL;
499 	}
500 	for (i = 0, j = tick_nbr; i < label_nbr; i++, j++) {
501 		ticks[j].position = go_rint (label_start + (double) (i) * major_label);
502 		index = ticks[j].position - 1;
503 		ticks[j].type = GOG_AXIS_TICK_NONE;
504 		ticks[j].str = NULL;
505 		if (axis->labels != NULL) {
506 			if (index < (int) go_data_get_vector_size (axis->labels) && index >= 0) {
507 				PangoAttrList *l = go_data_get_vector_markup (axis->labels, index);
508 				if (l != NULL) {
509 					char *label = go_data_get_vector_string (axis->labels, index);
510 					gog_axis_ticks_set_markup (&ticks[j], label, l);
511 					g_free (label);
512 				} else {
513 					double val = go_data_get_vector_value (axis->labels, index);
514 					if (go_finite (val))
515 						axis_format_value (axis, val, &ticks[j].str, TRUE);
516 					else {
517 						char *label = go_data_get_vector_string (axis->labels, index);
518 						gog_axis_ticks_set_text (&ticks[j], label);
519 						g_free (label);
520 					}
521 				}
522 			}
523 		} else {
524 			char *label = g_strdup_printf ("%d", index + 1);
525 			gog_axis_ticks_set_text (&ticks[j], label);
526 			g_free (label);
527 		}
528 	}
529 
530 	gog_axis_set_ticks (axis, tick_nbr + label_nbr, ticks);
531 }
532 
533 /*****************************************************************************/
534 
535 /*
536  *  Linear mapping
537  */
538 
539 static gboolean
map_linear_init(GogAxisMap * map,double offset,double length)540 map_linear_init (GogAxisMap *map, double offset, double length)
541 {
542 	MapData *data = map->data = g_new (MapData, 1);
543 
544 	if (gog_axis_get_bounds (map->axis, &data->min, &data->max)) {
545 		data->scale = 1 / (data->max - data->min);
546 		data->a = data->scale * length;
547 		data->b = offset - data->a * data->min;
548 		return TRUE;
549 	}
550 	data->min = 0.0;
551 	data->max = 1.0;
552 	data->scale = 1.0;
553 	data->a = length;
554 	data->b = offset;
555 	return FALSE;
556 }
557 
558 static double
map_linear(GogAxisMap * map,double value)559 map_linear (GogAxisMap *map, double value)
560 {
561 	const MapData *data = map->data;
562 
563 	return (value - data->min) * data->scale;
564 }
565 
566 static double
map_linear_to_view(GogAxisMap * map,double value)567 map_linear_to_view (GogAxisMap *map, double value)
568 {
569 	const MapData *data = map->data;
570 
571 	return map->axis->inverted ?
572 		(data->min + data->max - value) * data->a + data->b :
573 		value * data->a + data->b;
574 }
575 
576 static double
map_linear_derivative_to_view(GogAxisMap * map,double value)577 map_linear_derivative_to_view (GogAxisMap *map, double value)
578 {
579 	const MapData *data = map->data;
580 
581 	return map->axis->inverted ? -data->a: data->a;
582 
583 }
584 
585 static double
map_linear_from_view(GogAxisMap * map,double value)586 map_linear_from_view (GogAxisMap *map, double value)
587 {
588 	const MapData *data = map->data;
589 
590 	return map->axis->inverted ?
591 		data->min + data->max - (value - data->b) / data->a :
592 		(value - data->b) / data->a;
593 }
594 
595 static double
map_baseline(GogAxisMap * map)596 map_baseline (GogAxisMap *map)
597 {
598 	const MapData *data = map->data;
599 
600 	if (0. < data->min)
601 		return map_linear_to_view (map, data->min);
602 	else if (0 > data->max)
603 		return map_linear_to_view (map, data->max);
604 
605 	return map_linear_to_view (map, 0.);
606 }
607 
608 static void
map_bounds(GogAxisMap * map,double * minimum,double * maximum)609 map_bounds (GogAxisMap *map, double *minimum, double *maximum)
610 {
611 	const MapData *data = map->data;
612 
613 	if (minimum != NULL) *minimum = data->min;
614 	if (maximum != NULL) *maximum = data->max;
615 }
616 
617 static void
map_polar_auto_bound(GogAxis * axis,double minimum,double maximum,double * bound)618 map_polar_auto_bound (GogAxis *axis, double minimum, double maximum, double *bound)
619 {
620 	bound[GOG_AXIS_ELEM_MIN] = polar_units[axis->polar_unit].auto_minimum;
621 	bound[GOG_AXIS_ELEM_MAX] = polar_units[axis->polar_unit].auto_maximum;
622 	bound[GOG_AXIS_ELEM_MAJOR_TICK] = polar_units[axis->polar_unit].auto_major;
623 	bound[GOG_AXIS_ELEM_MINOR_TICK] = polar_units[axis->polar_unit].auto_minor;
624 }
625 
626 static void
map_time_auto_bound(GogAxis * axis,double minimum,double maximum,double * bound)627 map_time_auto_bound (GogAxis *axis, double minimum, double maximum, double *bound)
628 {
629 	enum { U_Second, U_Minute, U_Hour, U_Day } u;
630 	static const double invunits[4] = { 24* 60 * 60, 24 * 60, 24, 1 };
631 	GOFormat *fmt = gog_axis_get_effective_format (axis);
632 	int is_time = go_format_is_time (fmt);
633 	double range = fabs (maximum - minimum);
634 	double iu, step;
635 
636 	/* handle singletons */
637 	if (go_sub_epsilon (range) <= 0.) {
638 		minimum = floor (minimum) - 1;
639 		maximum = minimum + 1;
640 		range = 1;
641 	}
642 
643 	if (go_format_has_hour (fmt))
644 		u = U_Hour;
645 	else if (go_format_has_minute (fmt))
646 		u = U_Minute;
647 	else
648 		u = U_Second;
649 
650 retry:
651 	iu = invunits[u];
652 	step = pow (10, go_fake_floor (log10 (range * iu)));
653 
654 	if (is_time == 1) {
655 		switch (u) {
656 		case U_Hour:
657 			if (step == 10) {
658 				/* Step=10 is unusually bad... */
659 				if (range <= 24)
660 					step = 4;
661 				else
662 					step = 24;
663 				break;
664 			}
665 			/* Fall through */
666 		case U_Minute:
667 		case U_Second:
668 			if (step >= 100) {
669 				u++;
670 				goto retry;
671 			} else if (step < 0.10001 && u != U_Second) {
672 				u--;
673 				goto retry;
674 			}
675 			break;
676 		case U_Day:
677 		default:
678 			break;
679 		}
680 	} else {
681 		/* This is the elapsed-time case.  */
682 	}
683 	if (range * iu / step < 3)
684 		step /= 2;
685 
686 	step /= iu;
687 
688 	bound[GOG_AXIS_ELEM_MIN] = step * go_fake_floor (minimum / step);
689 	bound[GOG_AXIS_ELEM_MAX] = step * go_fake_ceil (maximum / step);
690 	bound[GOG_AXIS_ELEM_MAJOR_TICK] = step;
691 	bound[GOG_AXIS_ELEM_MINOR_TICK] = step / 2;
692 
693 #if 0
694 	g_printerr ("min=%g (%g)  max=%g (%g) step=%g (%g)\n",
695 		    minimum, bound[GOG_AXIS_ELEM_MIN],
696 		    maximum, bound[GOG_AXIS_ELEM_MAX],
697 		    step, bound[GOG_AXIS_ELEM_MAJOR_TICK]);
698 #endif
699 }
700 
701 static void
map_date_auto_bound(GogAxis * axis,double minimum,double maximum,double * bound)702 map_date_auto_bound (GogAxis *axis, double minimum, double maximum, double *bound)
703 {
704 	double range, step, minor_step;
705 	GDate min_date, max_date;
706 	int years;
707 
708 	minimum = go_fake_floor (minimum);
709 	maximum = go_fake_ceil (maximum);
710 
711 	while (split_date (axis, minimum, &min_date)) {
712 		minimum = 1;
713 	}
714 
715 	while (minimum > maximum ||
716 	       split_date (axis, maximum, &max_date)) {
717 		maximum = minimum;
718 	}
719 
720 	if (minimum == maximum) {
721 		if (minimum > 1)
722 			while (split_date (axis, --maximum, &max_date))
723 				/* Nothing */;
724 		else
725 			while (split_date (axis, ++maximum, &max_date))
726 				/* Nothing */;
727 	}
728 
729 	range = maximum - minimum;
730 	years = g_date_get_year (&max_date) - g_date_get_year (&min_date);
731 
732 	if (range <= 60) {
733 		step = 1;
734 		minor_step = 1;
735 	} else if (years < 2) {
736 		g_date_set_day (&min_date, 1);
737 		minimum = go_date_g_to_serial (&min_date, axis->date_conv);
738 
739 		if (g_date_get_year (&max_date) < 9999 ||
740 		    g_date_get_month (&max_date) < 12) {
741 			g_date_set_day (&max_date, 1);
742 			g_date_add_months (&max_date, 1);
743 		}
744 		maximum = go_date_g_to_serial (&max_date, axis->date_conv);
745 
746 		step = 30;
747 		minor_step = (range <= 180 ? 1 : step);
748 	} else {
749 		int N = 1, y, maxy;
750 
751 		while (years > 20 * N) {
752 			N *= 10;
753 		}
754 
755 		g_date_set_day (&min_date, 1);
756 		g_date_set_month (&min_date, 1);
757 		y = g_date_get_year (&min_date) / N * N;
758 		if (g_date_valid_dmy (1, 1, y))
759 			g_date_set_year (&min_date, y);
760 
761 		maxy = g_date_get_year (&max_date);
762 		y = (maxy + N - 1) / N * N;
763 		/* Make sure we are not going backwards. */
764 		if (y == maxy &&
765 		    (g_date_get_day (&max_date) != 1 ||
766 		     g_date_get_month (&max_date) != 1))
767 			y += N;
768 
769 		if (g_date_valid_dmy (1, 1, y))
770 			g_date_set_dmy (&max_date, 1, 1, y);
771 
772 		minimum = go_date_g_to_serial (&min_date, axis->date_conv);
773 		maximum = go_date_g_to_serial (&max_date, axis->date_conv);
774 		range = maximum - minimum;
775 		step = DAYS_IN_YEAR * N;
776 		minor_step = step / (N == 1 ? 12 : 10);
777 	}
778 
779 	bound[GOG_AXIS_ELEM_MIN] = minimum;
780 	bound[GOG_AXIS_ELEM_MAX] = maximum;
781 	bound[GOG_AXIS_ELEM_MAJOR_TICK] = step;
782 	bound[GOG_AXIS_ELEM_MINOR_TICK] = minor_step;
783 
784 #if 0
785 	g_printerr ("min=%g (%g)  max=%g (%g) step=%g (%g)\n",
786 		    minimum, bound[GOG_AXIS_ELEM_MIN],
787 		    maximum, bound[GOG_AXIS_ELEM_MAX],
788 		    step, bound[GOG_AXIS_ELEM_MAJOR_TICK]);
789 #endif
790 }
791 
792 static void
map_linear_auto_bound(GogAxis * axis,double minimum,double maximum,double * bound)793 map_linear_auto_bound (GogAxis *axis, double minimum, double maximum, double *bound)
794 {
795 	double step, range, mant;
796 	int expon;
797 
798 	range = fabs (maximum - minimum);
799 
800 	/* handle singletons */
801 	if (range > 0. && range / (fabs (minimum) + fabs (maximum)) <= DBL_EPSILON)
802 		range = 0.;
803 	if (go_sub_epsilon (range) <= 0.) {
804 		if (maximum > 0)
805 			minimum = 0.;
806 		else if (minimum < 0.)
807 			maximum = 0.;
808 		else {
809 			maximum = 1;
810 			minimum = 0;
811 		}
812 
813 		range = fabs (maximum - minimum);
814 	}
815 
816 	step = pow (10, go_fake_floor (log10 (range)));
817 	if (range / step < 1.6)
818 		step /= 5.;	/* .2 .4 .6 */
819 	else if (range / step < 3)
820 		step /= 2.;	/* 0 5 10 */
821 	else if (range / step > 8)
822 		step *= 2.;	/* 2 4 6 */
823 
824 	/* we want the bounds to be loose so jump up a step if we get too close */
825 	mant = frexp (minimum / step, &expon);
826 	bound[GOG_AXIS_ELEM_MIN] = step * floor (ldexp (mant - DBL_EPSILON, expon));
827 	mant = frexp (maximum / step, &expon);
828 	bound[GOG_AXIS_ELEM_MAX] = step * ceil (ldexp (mant + DBL_EPSILON, expon));
829 	bound[GOG_AXIS_ELEM_MAJOR_TICK] = step;
830 	bound[GOG_AXIS_ELEM_MINOR_TICK] = step / 5.;
831 
832 	/* pull to zero if its nearby (do not pull both directions to 0) */
833 	if (bound[GOG_AXIS_ELEM_MIN] > 0 &&
834 	    (bound[GOG_AXIS_ELEM_MIN] - 9.99 * step) < 0)
835 		bound[GOG_AXIS_ELEM_MIN] = 0;
836 	else if (bound[GOG_AXIS_ELEM_MAX] < 0 &&
837 	    (bound[GOG_AXIS_ELEM_MAX] + 9.99 * step) > 0)
838 		bound[GOG_AXIS_ELEM_MAX] = 0;
839 
840 	/* The epsilon shift can pull us away from a zero we want to
841 	 * keep (eg percentage bars with no negative elements) */
842 	if (bound[GOG_AXIS_ELEM_MIN] < 0 && minimum >= 0.)
843 		bound[GOG_AXIS_ELEM_MIN] = 0;
844 	else if (bound[GOG_AXIS_ELEM_MAX] > 0 && maximum <= 0.)
845 		bound[GOG_AXIS_ELEM_MAX] = 0;
846 }
847 
848 static double
scale_step(double step,double * scale)849 scale_step (double step, double *scale)
850 {
851 	double f, r;
852 	int l10;
853 
854 	if (step <= 0 || !go_finite (step)) {
855 		*scale = 1;
856 		return step;
857 	}
858 
859 	r = go_fake_floor (step);
860 	f = step - r;
861 	if (f <= step * 1e-10) {
862 		*scale = 1;
863 		return r;
864 	}
865 
866 	l10 = (int)floor (log10 (f));
867 	*scale = go_pow10 (-l10 + 1);
868 	step *=* scale;  /* "*= *" isn't cute enough.  */
869 
870 	r = go_fake_floor (step);
871 	if (fabs (r - step) < 1e-10)
872 		step = r;
873 
874 	return step;
875 }
876 
877 static void
map_linear_calc_ticks(GogAxis * axis)878 map_linear_calc_ticks (GogAxis *axis)
879 {
880 	GogAxisTick *ticks;
881 	double maximum, minimum, start_i, range;
882 	double maj_step, min_step, step_scale;
883 	int t, N;
884 	int maj_i, maj_N; /* Ticks for -1,....,maj_N     */
885 	int min_i, min_N; /* Ticks for 1,....,(min_N-1) */
886 	double display_factor = fabs (axis->display_factor);
887 
888 	if (!gog_axis_get_bounds (axis, &minimum, &maximum)) {
889 		gog_axis_set_ticks (axis, 2, create_invalid_axis_ticks (0.0, 1.0));
890 		return;
891 	}
892 	maximum /= display_factor;
893 	minimum /= display_factor;
894 	range = maximum - minimum;
895 
896 	maj_step = gog_axis_get_entry (axis, GOG_AXIS_ELEM_MAJOR_TICK, NULL);
897 	if (maj_step <= 0.)
898 		maj_step = range;
899 	else
900 		maj_step /= display_factor;
901 	while (1) {
902 		double ratio = go_fake_floor (range / maj_step);
903 		double Nd = (ratio + 1);  /* Correct if no minor */
904 		if (Nd >= 10 * GOG_AXIS_MAX_TICK_NBR)
905 			maj_step *= 10;
906 		else if (Nd > GOG_AXIS_MAX_TICK_NBR)
907 			maj_step *= 2;
908 		else {
909 			maj_N = (int)ratio;
910 			break;
911 		}
912 	}
913 
914 	min_step = gog_axis_get_entry (axis, GOG_AXIS_ELEM_MINOR_TICK, NULL);
915 	if (min_step <= 0.)
916 		min_step = maj_step;
917 	else
918 		min_step /= display_factor;
919 	while (1) {
920 		/* add 0.9 there because the ratio might not be an integer
921 		 * or there might be rounding errors, more than 0.9 would
922 		 * put a minor tick quite near the next major tick, see #657670) */
923 		double ratio = go_fake_floor (maj_step / min_step + 0.9);
924 		double Nd = (maj_N + 2) * ratio;
925 		if (Nd >= 10 * GOG_AXIS_MAX_TICK_NBR)
926 			min_step *= 10;
927 		else if (Nd > GOG_AXIS_MAX_TICK_NBR)
928 			min_step *= 2;
929 		else {
930 			min_N = MAX (1, (int)ratio);
931 			break;
932 		}
933 	}
934 
935 	/*
936 	 * We now have the steps we want.  It is time to align the steps
937 	 * so they hit round numbers (where round is relative to the step
938 	 * size).  We do this by rounding up the minimum.
939 	 *
940 	 * Due to this rounding we start the major tick counting at
941 	 * index -1 although we do not put a major tick there. This way,
942 	 * we have room for minor ticks before the first major tick.
943 	 *
944 	 * The last major tick goes at index N.  There may be minor ticks
945 	 * after that.
946 	 *
947 	 * Also due to the rounding, we may have room for one less major
948 	 * tick.  Recomputing maj_N seems to be the most numerically
949 	 * stable way of figuring that out.
950 	 */
951 	start_i = go_fake_ceil (minimum / maj_step);
952 	maj_N = (int)(go_fake_floor (maximum / maj_step) - start_i);
953 
954 	N = (maj_N + 2) * min_N;
955 	ticks = g_new0 (GogAxisTick, N);
956 
957 	/*
958 	 * maj_step might be something like 1.1 which is inexact.
959 	 * Turn this into 110/100 so calculations can use integers
960 	 * as long as possible.
961 	 */
962 	maj_step = scale_step (maj_step, &step_scale);
963 
964 	t = 0;
965 	for (maj_i = -1; maj_i <= maj_N; maj_i++) {
966 		/*
967 		 * Always calculate based on start to avoid a build-up of
968 		 * rounding errors.  Note, that the left factor is an
969 		 * integer, so we will get precisely zero when we need
970 		 * to.
971 		 */
972 		double maj_pos = (start_i + maj_i) * maj_step / step_scale;
973 
974 		if (maj_i >= 0) {
975 			g_assert (t < N);
976 			ticks[t].position = maj_pos * display_factor;
977 			ticks[t].type = GOG_AXIS_TICK_MAJOR;
978 			axis_format_value (axis, maj_pos, &ticks[t].str, FALSE);
979 			t++;
980 		}
981 
982 		for (min_i = 1; min_i < min_N; min_i++) {
983 			double min_pos = maj_pos + min_i * min_step;
984 			if (min_pos < minimum)
985 				continue;
986 			if (min_pos > maximum)
987 				break;
988 
989 			g_assert (t < N);
990 			ticks[t].position = min_pos * display_factor;
991 			ticks[t].type = GOG_AXIS_TICK_MINOR;
992 			ticks[t].str = NULL;
993 			t++;
994 		}
995 	}
996 
997 	if (t > N)
998 		g_critical ("[GogAxisMap::linear_calc_ticks] wrong allocation size");
999 	gog_axis_set_ticks (axis, t, ticks);
1000 }
1001 
1002 static const int j_horizon = 23936166;  /* 31-Dec-65535 */
1003 
1004 typedef struct {
1005 	enum { DS_MONTHS, DS_DAYS } unit;
1006 	double n;
1007 } DateStep;
1008 
1009 /*
1010  * Moral equivalent of "res = start + i * step".
1011  *
1012  * Note the date system: (GDate's) Julian day + fractional day.
1013  */
1014 static gboolean
dt_add(double * res,double start,double i,const DateStep * step)1015 dt_add (double *res, double start, double i, const DateStep *step)
1016 {
1017 	if (start <= 0 || start > j_horizon)
1018 		return FALSE;
1019 
1020 	switch (step->unit) {
1021 	case DS_DAYS:
1022 		*res = start + i * step->n;
1023 		break;
1024 	case DS_MONTHS: {
1025 		GDate d;
1026 		int istart = (int)start;
1027 		int m;
1028 		double n = i * step->n;
1029 
1030 		if (n < 0 || n != floor (n))
1031 			return FALSE;
1032 
1033 		g_date_clear (&d, 1);
1034 		g_date_set_julian (&d, istart);
1035 		m = (65535 - g_date_get_year (&d)) * 12 +
1036 			(12 - g_date_get_month (&d));
1037 		if (n > m)
1038 			return FALSE;
1039 		g_date_add_months (&d, (int)n);
1040 
1041 		*res = g_date_get_julian (&d) + (start - istart);
1042 		break;
1043 	}
1044 	default:
1045 		return FALSE;
1046 	}
1047 
1048 	return *res > 0 && *res <= j_horizon;
1049 }
1050 
1051 /*
1052  * Calculate a lower bound to the number of days in the step.  Accuracy is
1053  * not terribly important.
1054  */
1055 static double
dt_inflen(const DateStep * step)1056 dt_inflen (const DateStep *step)
1057 {
1058 	switch (step->unit) {
1059 	case DS_DAYS:
1060 		return step->n;
1061 	case DS_MONTHS:
1062 		return step->n * 28;
1063 	default:
1064 		return go_pinf;
1065 	}
1066 }
1067 
1068 
1069 static double
dt_serial(double dt,const GODateConventions * date_conv)1070 dt_serial (double dt, const GODateConventions *date_conv)
1071 {
1072 	double dt0 = go_fake_floor (dt);
1073 
1074 	if (dt0 > 0 && dt < j_horizon) {
1075 		GDate d;
1076 		int s;
1077 
1078 		g_date_clear (&d, 1);
1079 		g_date_set_julian (&d, (int)dt0);
1080 
1081 		s = go_date_g_to_serial (&d, date_conv);
1082 		return s + (dt - dt0);
1083 	} else
1084 		return go_pinf;
1085 }
1086 
1087 static void
map_date_calc_ticks(GogAxis * axis)1088 map_date_calc_ticks (GogAxis *axis)
1089 {
1090 	GogAxisTick *ticks;
1091 	double major_tick, minor_tick;
1092 	double minimum, maximum, range, maj_months, min_months;
1093 	GDate min_date, max_date;
1094 	DateStep major_step, minor_step;
1095 	double start;
1096 	double maj_extra;
1097 	int N, t, maj_i;
1098 
1099 	if (!gog_axis_get_bounds (axis, &minimum, &maximum) ||
1100 	    split_date (axis, minimum, &min_date) ||
1101 	    split_date (axis, maximum, &max_date)) {
1102 		gog_axis_set_ticks (axis, 2, create_invalid_axis_ticks (0.0, 1.0));
1103 		return;
1104 	}
1105 	range = maximum - minimum;
1106 
1107 	major_tick = gog_axis_get_entry (axis, GOG_AXIS_ELEM_MAJOR_TICK, NULL);
1108 	minor_tick = gog_axis_get_entry (axis, GOG_AXIS_ELEM_MINOR_TICK, NULL);
1109 
1110 	major_tick = MAX (major_tick, 0.0);
1111 	minor_tick = CLAMP (minor_tick, 0.0, major_tick);
1112 
1113 	maj_months = go_fake_round (major_tick / (DAYS_IN_YEAR / 12));
1114 	maj_months = CLAMP (maj_months, 0, G_MAXINT / 12);
1115 	if (maj_months == 0) {
1116 		major_step.unit = DS_DAYS;
1117 		major_step.n = major_tick;
1118 	} else {
1119 		major_step.unit = DS_MONTHS;
1120 		major_step.n = maj_months;
1121 	}
1122 
1123 	min_months = go_fake_round (minor_tick / (DAYS_IN_YEAR / 12));
1124 	min_months = CLAMP (min_months, 0, maj_months);
1125 	if (min_months == 0) {
1126 		minor_step.unit = DS_DAYS;
1127 		minor_step.n = minor_tick;
1128 	} else {
1129 		minor_step.unit = DS_MONTHS;
1130 		minor_step.n = min_months;
1131 	}
1132 
1133 	N = 0;
1134 	while (1) {
1135 		double maj_N = range / dt_inflen (&major_step);
1136 		double min_N = range / dt_inflen (&minor_step);
1137 		double Nd = maj_N + min_N + 1;
1138 
1139 		if (Nd <= GOG_AXIS_MAX_TICK_NBR) {
1140 			N = (int)Nd;
1141 			break;
1142 		}
1143 
1144 		/* Too many.  Now what?  */
1145 		if (min_N >= 2) {
1146 			/*  Drop minor ticks.  */
1147 			minor_step.n = go_pinf;
1148 		} else {
1149 			switch (major_step.unit) {
1150 			case DS_MONTHS:
1151 				if (major_step.n < 12)
1152 					major_step.n = 12;
1153 				else
1154 					major_step.n *= 10;
1155 				break;
1156 			case DS_DAYS:
1157 				if (major_step.n < 1)
1158 					major_step.n = 1;
1159 				else {
1160 					major_step.unit = DS_MONTHS;
1161 					major_step.n = 1;
1162 				}
1163 				break;
1164 			default:
1165 				g_assert_not_reached ();
1166 			}
1167 
1168 			major_step.n = go_pinf;
1169 		}
1170 	}
1171 
1172 	start = g_date_get_julian (&min_date) +
1173 		(minimum - go_date_g_to_serial (&min_date, axis->date_conv));
1174 
1175 	switch (major_step.unit) {
1176 	case DS_MONTHS:
1177 		maj_extra = 1;
1178 		break;
1179 	case DS_DAYS:
1180 		maj_extra = 1 - 0.5 / N;
1181 		break;
1182 	default:
1183 		g_assert_not_reached ();
1184 	}
1185 
1186 	ticks = g_new0 (GogAxisTick, N);
1187 
1188 	t = 0;
1189 	for (maj_i = 0; t < N; maj_i++) {
1190 		double maj_d, maj_dp1;
1191 		double maj_pos, maj_posp1, min_limit;
1192 		int min_i;
1193 
1194 		if (!dt_add (&maj_d, start, maj_i, &major_step))
1195 			break;
1196 		maj_pos = dt_serial (maj_d, axis->date_conv);
1197 		if (maj_pos > maximum)
1198 			break;
1199 
1200 		ticks[t].position = maj_pos;
1201 		ticks[t].type = GOG_AXIS_TICK_MAJOR;
1202 		axis_format_value (axis, maj_pos, &ticks[t].str, TRUE);
1203 		t++;
1204 
1205 		/* Calculate next major so we know when to stop minors.  */
1206 		if (!dt_add (&maj_dp1, start, maj_i + maj_extra, &major_step))
1207 			break;
1208 		maj_posp1 = dt_serial (maj_dp1, axis->date_conv);
1209 		min_limit = MIN (maximum, maj_posp1);
1210 
1211 		for (min_i = 1; t < N; min_i++) {
1212 			double min_d;
1213 			double min_pos;
1214 
1215 			if (!dt_add (&min_d, maj_d, min_i, &minor_step))
1216 				break;
1217 
1218 			min_pos = dt_serial (min_d, axis->date_conv);
1219 			if (min_pos >= min_limit)
1220 				break;
1221 
1222 			ticks[t].position = min_pos;
1223 			ticks[t].type = GOG_AXIS_TICK_MINOR;
1224 			ticks[t].str = NULL;
1225 			t++;
1226 		}
1227 	}
1228 
1229 	gog_axis_set_ticks (axis, t, ticks);
1230 }
1231 
1232 static GOFormat *
map_time_get_dim_format(GogAxis * axis,unsigned dim)1233 map_time_get_dim_format (GogAxis *axis, unsigned dim)
1234 {
1235 	switch (dim) {
1236 	case GOG_AXIS_ELEM_MIN:
1237 	case GOG_AXIS_ELEM_MAX:
1238 	case GOG_AXIS_ELEM_MAJOR_TICK:
1239 	case GOG_AXIS_ELEM_MINOR_TICK:
1240 		return go_format_new_from_XL ("[hh]:mm:ss");
1241 
1242 	default:
1243 		return NULL;
1244 	}
1245 }
1246 
1247 static GOFormat *
map_date_get_dim_format(GogAxis * axis,unsigned dim)1248 map_date_get_dim_format (GogAxis *axis, unsigned dim)
1249 {
1250 	switch (dim) {
1251 	case GOG_AXIS_ELEM_MIN:
1252 
1253 	case GOG_AXIS_ELEM_MAX: {
1254 		GOFormat *fmt = gog_axis_get_effective_format (axis);
1255 		/*
1256 		 * This is not a particular pretty solution to the problem
1257 		 * of bug #629121.  Improvements are welcome.
1258 		 */
1259 		if (go_format_is_date (fmt) == 2 ||
1260 		    go_format_is_time (fmt) >= 1)
1261 			return go_format_new_from_XL ("d-mmm-yy hh:mm:ss");
1262 		return go_format_new_magic (GO_FORMAT_MAGIC_MEDIUM_DATE);
1263 	}
1264 
1265 	case GOG_AXIS_ELEM_MAJOR_TICK:
1266 	case GOG_AXIS_ELEM_MINOR_TICK:
1267 		/* In units of days, so default is fine.  */
1268 	default:
1269 		return NULL;
1270 	}
1271 }
1272 
1273 static const GogAxisMapDesc *
map_linear_subclass(GogAxis * axis,const GogAxisMapDesc * desc)1274 map_linear_subclass (GogAxis *axis, const GogAxisMapDesc *desc)
1275 {
1276 	GOFormat *fmt;
1277 
1278 	if (gog_axis_get_atype (axis) == GOG_AXIS_CIRCULAR) {
1279 		static GogAxisMapDesc map_desc_polar;
1280 
1281 		if (!map_desc_polar.auto_bound) {
1282 			map_desc_polar = *desc;
1283 			map_desc_polar.auto_bound = map_polar_auto_bound;
1284 			map_desc_polar.subclass = NULL;
1285 		}
1286 
1287 		return &map_desc_polar;
1288 	}
1289 
1290 	fmt = gog_axis_get_effective_format (axis);
1291 	if (fmt && go_format_is_time (fmt) > 0) {
1292 		static GogAxisMapDesc map_desc_time;
1293 
1294 		if (!map_desc_time.auto_bound) {
1295 			map_desc_time = *desc;
1296 			map_desc_time.auto_bound = map_time_auto_bound;
1297 			map_desc_time.get_dim_format = map_time_get_dim_format;
1298 			map_desc_time.subclass = NULL;
1299 		}
1300 
1301 		return &map_desc_time;
1302 	}
1303 
1304 	if (fmt && go_format_is_date (fmt) > 0) {
1305 		static GogAxisMapDesc map_desc_date;
1306 
1307 		if (!map_desc_date.auto_bound) {
1308 			map_desc_date = *desc;
1309 			map_desc_date.auto_bound = map_date_auto_bound;
1310 			map_desc_date.calc_ticks = map_date_calc_ticks;
1311 			map_desc_date.get_dim_format = map_date_get_dim_format;
1312 			map_desc_date.subclass = NULL;
1313 		}
1314 
1315 		return &map_desc_date;
1316 	}
1317 
1318 	return NULL;
1319 }
1320 
1321 /*****************************************************************************/
1322 
1323 /*
1324  * Logarithmic mapping
1325  */
1326 
1327 typedef struct
1328 {
1329 	double min;
1330 	double max;
1331 	double scale;
1332 	double a;
1333 	double b;
1334 	double a_inv;
1335 	double b_inv;
1336 } MapLogData;
1337 
1338 static gboolean
map_log_init(GogAxisMap * map,double offset,double length)1339 map_log_init (GogAxisMap *map, double offset, double length)
1340 {
1341 	MapLogData *data = map->data = g_new (MapLogData, 1);
1342 
1343 	if (gog_axis_get_bounds (map->axis, &data->min, &data->max))
1344 		if (data->min > 0.0)  {
1345 			data->min = log (data->min);
1346 			data->max = log (data->max);
1347 			data->scale = 1/ (data->max - data->min);
1348 			data->a = data->scale * length;
1349 			data->b = offset - data->a * data->min;
1350 			data->a_inv = -data->scale * length;
1351 			data->b_inv = offset + length - data->a_inv * data->min;
1352 			return TRUE;
1353 		}
1354 
1355 	data->min = 0.0;
1356 	data->max = log (10.0);
1357 	data->scale = 1 / log(10.0);
1358 	data->a = data->scale * length;
1359 	data->b = offset;
1360 	data->a_inv = -data->scale * length;
1361 	data->b_inv = offset + length;
1362 
1363 	return FALSE;
1364 }
1365 
1366 static double
map_log(GogAxisMap * map,double value)1367 map_log (GogAxisMap *map, double value)
1368 {
1369 	const MapLogData *data = map->data;
1370 
1371 	return (log (value) - data->min) * data->scale;
1372 }
1373 
1374 static double
map_log_to_view(GogAxisMap * map,double value)1375 map_log_to_view (GogAxisMap *map, double value)
1376 {
1377 	const MapLogData *data = map->data;
1378 	double result;
1379 
1380 	if (value <= 0.)
1381 		/* Make libart happy, do we still need it? */
1382 		result = map->axis->inverted ? -DBL_MAX : DBL_MAX;
1383 	else
1384 		result = map->axis->inverted ?
1385 			log (value) * data->a_inv + data->b_inv :
1386 			log (value) * data->a + data->b;
1387 
1388 	return result;
1389 }
1390 
1391 static double
map_log_derivative_to_view(GogAxisMap * map,double value)1392 map_log_derivative_to_view (GogAxisMap *map, double value)
1393 {
1394 	const MapLogData *data = map->data;
1395 
1396 	if (value <= 0.)
1397 		return go_nan;
1398 	return map->axis->inverted ? data->a_inv / value:
1399 		data->a / value;
1400 
1401 }
1402 
1403 static double
map_log_from_view(GogAxisMap * map,double value)1404 map_log_from_view (GogAxisMap *map, double value)
1405 {
1406 	const MapLogData *data = map->data;
1407 
1408 	return  map->axis->inverted ?
1409 		exp ((value - data->b_inv) / data->a_inv) :
1410 		exp ((value - data->b) / data->a);
1411 }
1412 
1413 static gboolean
map_log_finite(double value)1414 map_log_finite (double value)
1415 {
1416 	return go_finite (value) && value > 0.;
1417 }
1418 
1419 static double
map_log_baseline(GogAxisMap * map)1420 map_log_baseline (GogAxisMap *map)
1421 {
1422 	const MapLogData *data = map->data;
1423 
1424 	return map->axis->inverted ?
1425 		data->min * data->a_inv + data->b_inv :
1426 		data->min * data->a + data->b;
1427 }
1428 
1429 static void
map_log_bounds(GogAxisMap * map,double * minimum,double * maximum)1430 map_log_bounds (GogAxisMap *map, double *minimum, double *maximum)
1431 {
1432 	const MapLogData *data = map->data;
1433 
1434 	if (minimum != NULL) *minimum = exp (data->min);
1435 	if (maximum != NULL) *maximum = exp (data->max);
1436 }
1437 
1438 static void
map_log_auto_bound(GogAxis * axis,double minimum,double maximum,double * bound)1439 map_log_auto_bound (GogAxis *axis, double minimum, double maximum, double *bound)
1440 {
1441 	double step;
1442 
1443 	if (maximum <= 0.0)
1444 		maximum = 1.0;
1445 	if (minimum <= 0.0)
1446 		minimum = maximum / 100.0;
1447 	if (maximum < minimum)
1448 		maximum = minimum * 100.0;
1449 
1450 	maximum = go_fake_ceil (log10 (maximum));
1451 	minimum = go_fake_floor (log10 (minimum));
1452 
1453 	step = go_fake_ceil ((maximum - minimum + 1.0) /
1454 		     (double) GOG_AXIS_LOG_AUTO_MAX_MAJOR_TICK_NBR);
1455 
1456 	bound[GOG_AXIS_ELEM_MIN] = pow (10.0, minimum);
1457 	bound[GOG_AXIS_ELEM_MAX] = pow (10.0, maximum);
1458 	bound[GOG_AXIS_ELEM_MAJOR_TICK] = step;
1459 	bound[GOG_AXIS_ELEM_MINOR_TICK] = 9 * step - 1;
1460 }
1461 
1462 static void
map_log_calc_ticks(GogAxis * axis)1463 map_log_calc_ticks (GogAxis *axis)
1464 {
1465 	GogAxisTick *ticks;
1466 	double maximum, minimum, lminimum, lmaximum, start_i, lrange;
1467 	double maj_step, min_step;
1468 	gboolean base10;
1469 	int t, N;
1470 	int maj_i, maj_N; /* Ticks for -1,....,maj_N     */
1471 	int min_i, min_N; /* Ticks for 1,....,(min_N-1) */
1472 	int base10_unit = 0;
1473 
1474 	if (!gog_axis_get_bounds (axis, &minimum, &maximum) ||
1475 	    minimum <= 0.0) {
1476 		gog_axis_set_ticks (axis, 2, create_invalid_axis_ticks (1.0, 10.0));
1477 		return;
1478 	}
1479 
1480 	lminimum = log10 (minimum);
1481 	lmaximum = log10 (maximum);
1482 	lrange = lmaximum - lminimum;
1483 
1484 	/* This is log10(factor) between major ticks.  */
1485 	maj_step = gog_axis_get_entry (axis, GOG_AXIS_ELEM_MAJOR_TICK, NULL);
1486 	if (!(maj_step > 0))
1487 		maj_step = 1;
1488 	while (1) {
1489 		double ratio = go_fake_floor (lrange / maj_step);
1490 		double Nd = (ratio + 1);  /* Correct if no minor */
1491 		if (Nd > GOG_AXIS_MAX_TICK_NBR)
1492 			maj_step *= 2;
1493 		else {
1494 			maj_N = (int)ratio;
1495 			break;
1496 		}
1497 	}
1498 	base10 = (maj_step >= 1 && maj_step == floor (maj_step));
1499 
1500 	/*
1501 	 * This is the number of subticks we want strictly between
1502 	 * each major tick.
1503 	 */
1504 	min_step = go_fake_floor (gog_axis_get_entry (axis, GOG_AXIS_ELEM_MINOR_TICK, NULL));
1505 	if (min_step < 0) {
1506 		min_N = 1;
1507 	} else if (base10) {
1508 		int ims = (int)maj_step;
1509 		/*
1510 		 * We basically have three choices:
1511 		 *   9N-1 : minor ticks at {1,2,...,9}*10^n and at 10^n
1512 		 *          without major ticks.
1513 		 *   N-1  : minor ticks at 10^n without major tick
1514 		 *
1515 		 *   1    : no minor ticks.
1516 		 */
1517 		do {
1518 			if (min_step >= 9 * ims - 1)
1519 				min_step = 9 * ims - 1;
1520 			else if (min_step >= ims - 1)
1521 				min_step = ims - 1;
1522 			else
1523 				min_step = 0;
1524 
1525 			if (maj_N * (min_step + 1) > GOG_AXIS_MAX_TICK_NBR) {
1526 				min_step -= 1;
1527 				continue;
1528 			}
1529 		} while (0);
1530 
1531 		min_N = (int)min_step + 1;
1532 		base10_unit = (min_N == ims ? 1 : 9);
1533 		min_step = go_nan;
1534 	} else {
1535 		while ((min_step + 1) * maj_N > GOG_AXIS_MAX_TICK_NBR)
1536 			min_step = floor (min_step / 2);
1537 		min_N = 1 + (int)min_step;
1538 		min_step = maj_step / min_N;
1539 	}
1540 
1541 	start_i = go_fake_ceil (lminimum / maj_step);
1542 	maj_N = (int)(go_fake_floor (lmaximum / maj_step) - start_i);
1543 
1544 #if 0
1545 	g_printerr ("lmin=%g  lmax=%g  maj_N=%d  maj_step=%g  start_i=%g\n",
1546 		    lminimum, lmaximum, maj_N, maj_step, start_i);
1547 #endif
1548 
1549 	N = (maj_N + 2) * min_N;
1550 	ticks = g_new0 (GogAxisTick, N);
1551 
1552 	t = 0;
1553 	for (maj_i = -1; maj_i <= maj_N; maj_i++) {
1554 		/*
1555 		 * Always calculate based on start to avoid a build-up of
1556 		 * rounding errors.  Note, that the left factor is an
1557 		 * integer, so we will get precisely zero when we need
1558 		 * to.
1559 		 */
1560 		double maj_lpos = (start_i + maj_i) * maj_step;
1561 		double maj_pos = pow (10, maj_lpos);
1562 
1563 		if (maj_i >= 0) {
1564 			g_assert (t < N);
1565 			ticks[t].position = maj_pos;
1566 			ticks[t].type = GOG_AXIS_TICK_MAJOR;
1567 		        axis_format_value (axis, maj_pos, &ticks[t].str, TRUE);
1568 			t++;
1569 		}
1570 
1571 		for (min_i = 1; min_i < min_N; min_i++) {
1572 			double min_pos;
1573 
1574 			if (base10) {
1575 				min_pos = maj_pos *
1576 					go_pow10 (min_i / base10_unit) *
1577 					(1 + min_i % base10_unit);
1578 			} else {
1579 				double min_lpos = maj_lpos + min_i * min_step;
1580 				min_pos = pow (10, min_lpos);
1581 			}
1582 
1583 			if (min_pos < minimum)
1584 				continue;
1585 			if (min_pos > maximum)
1586 				break;
1587 
1588 			g_assert (t < N);
1589 			ticks[t].position = min_pos;
1590 			ticks[t].type = GOG_AXIS_TICK_MINOR;
1591 			ticks[t].str = NULL;
1592 			t++;
1593 		}
1594 	}
1595 
1596 	if (t > N)
1597 		g_critical ("[GogAxisMap::log_calc_ticks] wrong allocation size");
1598 	gog_axis_set_ticks (axis, t, ticks);
1599 }
1600 
1601 /*****************************************************************************/
1602 
1603 static const GogAxisMapDesc map_desc_discrete =
1604 {
1605 	map_discrete,			map_discrete_to_view,   map_discrete_derivative_to_view,
1606 	map_discrete_from_view,		go_finite,
1607 	map_baseline,			map_bounds,
1608 	map_discrete_init,		NULL,
1609 	NULL,
1610 	map_discrete_auto_bound,	map_discrete_calc_ticks,
1611 	NULL,
1612 	N_("Discrete"),			N_("Discrete mapping")
1613 };
1614 
1615 static const GogAxisMapDesc map_desc_linear =
1616 {
1617 	map_linear,		map_linear_to_view,     map_linear_derivative_to_view,
1618 	map_linear_from_view,   go_finite,
1619 	map_baseline,		map_bounds,
1620 	map_linear_init, 	NULL,
1621 	map_linear_subclass,
1622 	map_linear_auto_bound, 	map_linear_calc_ticks,
1623 	NULL,
1624 	N_("Linear"),		N_("Linear mapping")
1625 };
1626 
1627 static const GogAxisMapDesc map_desc_log =
1628 {
1629 	map_log,		map_log_to_view,	map_log_derivative_to_view,
1630 	map_log_from_view,	map_log_finite,
1631 	map_log_baseline,	map_log_bounds,
1632 	map_log_init,		NULL,
1633 	NULL,
1634 	map_log_auto_bound, 	map_log_calc_ticks,
1635 	NULL,
1636 	N_("Log"),		N_("Logarithm mapping")
1637 };
1638 
1639 static const GogAxisMapDesc *map_descs[] = {
1640 	&map_desc_linear,
1641 	&map_desc_log
1642 };
1643 
1644 #ifdef GOFFICE_WITH_GTK
1645 static void
gog_axis_map_set_by_num(GogAxis * axis,gint num)1646 gog_axis_map_set_by_num (GogAxis *axis, gint num)
1647 {
1648 	g_return_if_fail (GOG_IS_AXIS (axis));
1649 
1650 	if (num >= 0 && num < (gint)G_N_ELEMENTS (map_descs))
1651 		g_object_set (G_OBJECT (axis), "map-name", map_descs[num]->name, NULL);
1652 	else
1653 		g_object_set (G_OBJECT (axis), "map-name", "", NULL);
1654 }
1655 
1656 static void
gog_axis_map_populate_combo(GogAxis * axis,GtkComboBoxText * combo)1657 gog_axis_map_populate_combo (GogAxis *axis, GtkComboBoxText *combo)
1658 {
1659 	unsigned i;
1660 
1661 	g_return_if_fail (GOG_IS_AXIS (axis));
1662 
1663 	for (i = 0; i < G_N_ELEMENTS (map_descs); i++) {
1664 		const char *thisname = map_descs[i]->name;
1665 		gtk_combo_box_text_append_text (combo, _(thisname));
1666 		if (!g_ascii_strcasecmp (thisname, axis->map_desc->name))
1667 			gtk_combo_box_set_active (GTK_COMBO_BOX (combo), i);
1668 	}
1669 }
1670 #endif
1671 
1672 static void
gog_axis_figure_subclass(GogAxis * axis)1673 gog_axis_figure_subclass (GogAxis *axis)
1674 {
1675 	if (axis->is_discrete) {
1676 		axis->actual_map_desc = &map_desc_discrete;
1677 		return;
1678 	}
1679 
1680 	axis->actual_map_desc = axis->map_desc;
1681 	while (axis->actual_map_desc->subclass) {
1682 		const GogAxisMapDesc *c =
1683 			axis->actual_map_desc->subclass (axis, axis->actual_map_desc);
1684 		if (!c)
1685 			break;
1686 		axis->actual_map_desc = c;
1687 	}
1688 }
1689 
1690 static void
gog_axis_map_set(GogAxis * axis,char const * name)1691 gog_axis_map_set (GogAxis *axis, char const *name)
1692 {
1693 	unsigned i, map = 0;
1694 
1695 	g_return_if_fail (GOG_IS_AXIS (axis));
1696 
1697 	if (name != NULL)
1698 		for (i = 0; i < G_N_ELEMENTS(map_descs); i++) {
1699 			const char *thisname = map_descs[i]->name;
1700 			if (!g_ascii_strcasecmp (name, thisname)) {
1701 				map = i;
1702 				break;
1703 			}
1704 		}
1705 	axis->map_desc = map_descs[map];
1706 	gog_axis_figure_subclass (axis);
1707 }
1708 
1709 /**
1710  * gog_axis_map_is_valid:
1711  * @map: a #GogAxisMap
1712  *
1713  * Tests if @map was correctly initialized, i.e. if bounds are
1714  * valid.
1715  *
1716  * Returns: %TRUE if map is valid
1717  **/
1718 gboolean
gog_axis_map_is_valid(GogAxisMap * map)1719 gog_axis_map_is_valid (GogAxisMap *map)
1720 {
1721 	g_return_val_if_fail (map != NULL, FALSE);
1722 
1723 	return map->is_valid;
1724 }
1725 
1726 /**
1727  * gog_axis_map_new:
1728  * @axis: a #GogAxis
1729  * @offset: start of plot area.
1730  * @length: length of plot area.
1731  *
1732  * Creates a #GogAxisMap for data mapping to plot window.
1733  * offset and length are optional parameters to be used with
1734  * gog_axis_map_to_view in order to translates data coordinates
1735  * into canvas space.
1736  *
1737  * Returns: (transfer full): a newly allocated #GogAxisMap.
1738  **/
1739 GogAxisMap *
gog_axis_map_new(GogAxis * axis,double offset,double length)1740 gog_axis_map_new (GogAxis *axis, double offset, double length)
1741 {
1742 	GogAxisMap *map;
1743 
1744 	g_return_val_if_fail (GOG_IS_AXIS (axis), NULL);
1745 
1746 	map = g_new0 (GogAxisMap, 1);
1747 
1748 	g_object_ref (axis);
1749 	map->desc = axis->actual_map_desc;
1750 	map->axis = axis;
1751 	map->data = NULL;
1752 	map->is_valid = FALSE;
1753 	map->ref_count = 1;
1754 
1755 	if (axis->type != GOG_AXIS_CIRCULAR) {
1756 		offset += axis->span_start * length;
1757 		length *= axis->span_end - axis->span_start;
1758 	}
1759 
1760 	if (map->desc->init != NULL)
1761 		map->is_valid = map->desc->init (map, offset, length);
1762 
1763 	return map;
1764 }
1765 
1766 /**
1767  * gog_axis_map:
1768  * @map: a #GogAxisMap
1769  * @value: value to map to plot space.
1770  *
1771  * Converts @value to plot coordinates. A value in [0,1.0] range means a data
1772  * within axis bounds.
1773  *
1774  * Returns: mapped value.
1775  **/
1776 double
gog_axis_map(GogAxisMap * map,double value)1777 gog_axis_map (GogAxisMap *map, double value)
1778 {
1779 	g_return_val_if_fail (map != NULL, -1);
1780 
1781 	return (map->axis->inverted
1782 		? 1.0 - map->desc->map (map, value)
1783 		: map->desc->map (map, value));
1784 }
1785 
1786 /**
1787  * gog_axis_map_from_view:
1788  * @map: a #GogAxisMap
1789  * @value: value to unmap from canvas space.
1790  *
1791  * Converts value from canvas space to data space.
1792  *
1793  * return value: value in data coordinates
1794  **/
1795 
1796 double
gog_axis_map_from_view(GogAxisMap * map,double value)1797 gog_axis_map_from_view (GogAxisMap *map, double value)
1798 {
1799 	g_return_val_if_fail (map != NULL, 0);
1800 
1801 	return map->desc->map_from_view (map, value);
1802 }
1803 
1804 /**
1805  * gog_axis_map_to_view:
1806  * @map: a #GogAxisMap
1807  * @value: value to map to canvas space
1808  *
1809  * Converts value from data space to canvas space, using
1810  * offset and length parameters given to gog_axis_map_new.
1811  *
1812  * return value: a value in canvas coordinates
1813  **/
1814 
1815 double
gog_axis_map_to_view(GogAxisMap * map,double value)1816 gog_axis_map_to_view (GogAxisMap *map, double value)
1817 {
1818 	g_return_val_if_fail (map != NULL, 0);
1819 
1820 	return map->desc->map_to_view (map, value);
1821 }
1822 
1823 /**
1824  * gog_axis_map_derivative_to_view:
1825  * @map: a #GogAxisMap
1826  * @value: value to map to canvas space
1827  *
1828  * Returns: the derivative of the mapping expression at value.
1829  **/
1830 
1831 double
gog_axis_map_derivative_to_view(GogAxisMap * map,double value)1832 gog_axis_map_derivative_to_view (GogAxisMap *map, double value)
1833 {
1834 	g_return_val_if_fail (map != NULL, 0);
1835 
1836 	return map->desc->map_derivative_to_view (map, value);
1837 }
1838 
1839 /**
1840  * gog_axis_map_finite:
1841  * @map: a #GogAxisMap
1842  * @value: value to test
1843  *
1844  * Tests whether @value is valid for the given @map.
1845  *
1846  * return value: %TRUE if value means something
1847  **/
1848 
1849 gboolean
gog_axis_map_finite(GogAxisMap * map,double value)1850 gog_axis_map_finite (GogAxisMap *map, double value)
1851 {
1852 	g_return_val_if_fail (map != NULL, FALSE);
1853 
1854 	return map->desc->map_finite (value);
1855 }
1856 
1857 /**
1858  * gog_axis_map_get_baseline:
1859  * @map: a #GogAxisMap
1860  *
1861  * Returns: the baseline for @map, in view coordinates,
1862  * 	clipped to offset and offset+length, where offset and length
1863  * 	are the parameters of gog_axis_map_new.
1864  **/
1865 double
gog_axis_map_get_baseline(GogAxisMap * map)1866 gog_axis_map_get_baseline (GogAxisMap *map)
1867 {
1868 	g_return_val_if_fail (map != NULL, 0);
1869 
1870 	return map->desc->map_baseline (map);
1871 }
1872 
1873 /**
1874  * gog_axis_map_get_extents:
1875  * @map: a #GogAxisMap
1876  * @start: (out) (optional): location to store start for this axis
1877  * @stop: (out) (optional): location to store stop for this axis
1878  *
1879  * Gets start and stop for the whole chart relative to the given axis map
1880  * in data coordinates. If axis is not inverted, start = minimum and
1881  * stop = maximum.  If axis is invalid, it'll return arbitrary bounds.
1882  **/
1883 
1884 void
gog_axis_map_get_extents(GogAxisMap * map,double * start,double * stop)1885 gog_axis_map_get_extents (GogAxisMap *map, double *start, double *stop)
1886 {
1887 	double x0, x1;
1888 	g_return_if_fail (map != NULL);
1889 
1890 	if (gog_axis_is_inverted (map->axis))
1891 		map->desc->map_bounds (map, &x1, &x0);
1892 	else
1893 		map->desc->map_bounds (map, &x0, &x1);
1894 	if (map->axis->type != GOG_AXIS_CIRCULAR) {
1895 		if (gog_axis_is_discrete (map->axis)) {
1896 			double buf = (x1 - x0) / (map->axis->span_end - map->axis->span_start);
1897 			x0 -= map->axis->span_start * buf;
1898 			x1 = x0 + buf;
1899 		} else {
1900 			double t, buf;
1901 			t = gog_axis_map_to_view (map, x0);
1902 			buf = (gog_axis_map_to_view (map, x1) - t) / (map->axis->span_end - map->axis->span_start);
1903 			t -= map->axis->span_start * buf;
1904 			x0 = gog_axis_map_from_view (map, t);
1905 			x1 = gog_axis_map_from_view (map, t + buf);
1906 		}
1907 	}
1908 	if (start)
1909 		*start = x0;
1910 	if (stop)
1911 		*stop = x1;
1912 }
1913 
1914 /**
1915  * gog_axis_map_get_real_extents:
1916  * @map: a #GogAxisMap
1917  * @start: (out) (optional): location to store start for this axis
1918  * @stop: (out) (optional): location to store stop for this axis
1919  *
1920  * Gets start and stop for the given axis map in data coordinates. If
1921  * axis is not inverted, start = minimum and stop = maximum.  If axis is
1922  * invalid, it'll return arbitrary bounds.
1923  **/
1924 
1925 void
gog_axis_map_get_real_extents(GogAxisMap * map,double * start,double * stop)1926 gog_axis_map_get_real_extents (GogAxisMap *map, double *start, double *stop)
1927 {
1928 	double x0, x1;
1929 	g_return_if_fail (map != NULL);
1930 
1931 	if (gog_axis_is_inverted (map->axis))
1932 		map->desc->map_bounds (map, &x1, &x0);
1933 	else
1934 		map->desc->map_bounds (map, &x0, &x1);
1935 	if (start)
1936 		*start = x0;
1937 	if (stop)
1938 		*stop = x1;
1939 }
1940 
1941 /**
1942  * gog_axis_map_get_bounds:
1943  * @map: a #GogAxisMap
1944  * @minimum: (out) (optional): location to store minimum for this axis
1945  * @maximum: (out) (optional): location to store maximum for this axis
1946  *
1947  * Gets bounds for the whole chart relative to the given axis map in data
1948  * coordinates. If axis is invalid, it'll return arbitrary bounds.
1949  **/
1950 void
gog_axis_map_get_bounds(GogAxisMap * map,double * minimum,double * maximum)1951 gog_axis_map_get_bounds (GogAxisMap *map, double *minimum, double *maximum)
1952 {
1953 	double x0, x1;
1954 	g_return_if_fail (map != NULL);
1955 
1956 	if (gog_axis_is_inverted (map->axis))
1957 		map->desc->map_bounds (map, &x1, &x0);
1958 	else
1959 		map->desc->map_bounds (map, &x0, &x1);
1960 	if (map->axis->type != GOG_AXIS_CIRCULAR) {
1961 		if (gog_axis_is_discrete (map->axis)) {
1962 			double buf = (x1 - x0) / (map->axis->span_end - map->axis->span_start);
1963 			x0 -= map->axis->span_start * buf;
1964 			x1 = x0 + buf;
1965 		} else {
1966 			double t, buf;
1967 			t = gog_axis_map_to_view (map, x0);
1968 			buf = (gog_axis_map_to_view (map, x1) - t) / (map->axis->span_end - map->axis->span_start);
1969 			t -= map->axis->span_start * buf;
1970 			x0 = gog_axis_map_from_view (map, t);
1971 			x1 = gog_axis_map_from_view (map, t + buf);
1972 		}
1973 	}
1974 	if (minimum)
1975 		*minimum = (map->axis->inverted)? x1: x0;
1976 	if (maximum)
1977 		*maximum = (map->axis->inverted)? x0: x1;
1978 }
1979 
1980 /**
1981  * gog_axis_map_get_real_bounds:
1982  * @map: a #GogAxisMap
1983  * @minimum: (out) (optional): location to store minimum for this axis
1984  * @maximum: (out) (optional): location to store maximum for this axis
1985  *
1986  * Gets bounds for the given axis map in data coordinates. If axis is invalid,
1987  * it'll return arbitrary bounds.
1988  **/
1989 void
gog_axis_map_get_real_bounds(GogAxisMap * map,double * minimum,double * maximum)1990 gog_axis_map_get_real_bounds (GogAxisMap *map, double *minimum, double *maximum)
1991 {
1992 	double x0, x1;
1993 	g_return_if_fail (map != NULL);
1994 
1995 	if (gog_axis_is_inverted (map->axis))
1996 		map->desc->map_bounds (map, &x1, &x0);
1997 	else
1998 		map->desc->map_bounds (map, &x0, &x1);
1999 	if (minimum)
2000 		*minimum = (map->axis->inverted)? x1: x0;
2001 	if (maximum)
2002 		*maximum = (map->axis->inverted)? x0: x1;
2003 }
2004 
2005 /**
2006  * gog_axis_map_is_inverted:
2007  * @map: a #GogAxisMap
2008  *
2009  * Returns: %TRUE is the axis is inverted;
2010  **/
2011 gboolean
gog_axis_map_is_inverted(GogAxisMap * map)2012 gog_axis_map_is_inverted (GogAxisMap *map)
2013 {
2014 	g_return_val_if_fail (map != NULL, FALSE);
2015 
2016 	return map->axis->inverted;
2017 }
2018 
2019 /**
2020  * gog_axis_map_is_discrete:
2021  * @map: a #GogAxisMap
2022  *
2023  * Returns: %TRUE is the axis is discrete;
2024  **/
2025 gboolean
gog_axis_map_is_discrete(GogAxisMap * map)2026 gog_axis_map_is_discrete (GogAxisMap *map)
2027 {
2028 	g_return_val_if_fail (map != NULL, FALSE);
2029 
2030 	return map->axis->is_discrete;
2031 }
2032 
2033 /**
2034  * gog_axis_map_free:
2035  * @map: (transfer full): a #GogAxisMap
2036  *
2037  * Frees #GogAxisMap object.
2038  **/
2039 void
gog_axis_map_free(GogAxisMap * map)2040 gog_axis_map_free (GogAxisMap *map)
2041 {
2042 	g_return_if_fail (map != NULL);
2043 
2044 	if (map->ref_count-- > 1)
2045 		return;
2046 	if (map->desc->destroy != NULL)
2047 		map->desc->destroy (map);
2048 
2049 	g_object_unref (map->axis);
2050 	g_free (map->data);
2051 	g_free (map);
2052 }
2053 
2054 static GogAxisMap *
gog_axis_map_ref(GogAxisMap * map)2055 gog_axis_map_ref (GogAxisMap *map)
2056 {
2057 	map->ref_count++;
2058 	return map;
2059 }
2060 
2061 GType
gog_axis_map_get_type(void)2062 gog_axis_map_get_type (void)
2063 {
2064 	static GType t = 0;
2065 
2066 	if (t == 0)
2067 		t = g_boxed_type_register_static ("GogAxisMap",
2068 			 (GBoxedCopyFunc) gog_axis_map_ref,
2069 			 (GBoxedFreeFunc) gog_axis_map_free);
2070 	return t;
2071 }
2072 
2073 static void
gog_axis_auto_bound(GogAxis * axis)2074 gog_axis_auto_bound (GogAxis *axis)
2075 {
2076 	double maximum, minimum;
2077 	double tmp;
2078 	gboolean user_defined;
2079 
2080 	g_return_if_fail (GOG_IS_AXIS (axis));
2081 
2082 	minimum = axis->min_val;
2083 	maximum = axis->max_val;
2084 
2085 	/*
2086 	 * We handle user-set minimum and maximum by pretending to have
2087 	 * data in that range.  This is a bit dubious.
2088 	 */
2089 	tmp = gog_axis_get_entry (axis, GOG_AXIS_ELEM_MIN, &user_defined);
2090 	if (user_defined) minimum = tmp;
2091 
2092 	tmp = gog_axis_get_entry (axis, GOG_AXIS_ELEM_MAX, &user_defined);
2093 	if (user_defined) maximum = tmp;
2094 
2095 	if (axis->actual_map_desc->auto_bound)
2096 		axis->actual_map_desc->auto_bound (axis,
2097 						   minimum, maximum,
2098 						   axis->auto_bound);
2099 }
2100 
2101 static void
gog_axis_set_ticks(GogAxis * axis,int tick_nbr,GogAxisTick * ticks)2102 gog_axis_set_ticks (GogAxis *axis, int tick_nbr, GogAxisTick *ticks)
2103 {
2104 	unsigned i;
2105 
2106 	g_return_if_fail (GOG_IS_AXIS (axis));
2107 
2108 	if (axis->ticks != NULL) {
2109 		for (i = 0; i < axis->tick_nbr; i++)
2110 			go_string_unref (axis->ticks[i].str);
2111 
2112 		g_free (axis->ticks);
2113 	}
2114 
2115 	axis->tick_nbr = tick_nbr;
2116 	axis->ticks = ticks;
2117 }
2118 
2119 static void
gog_axis_calc_ticks(GogAxis * axis)2120 gog_axis_calc_ticks (GogAxis *axis)
2121 {
2122 	g_return_if_fail (GOG_IS_AXIS (axis));
2123 
2124 	if (axis->actual_map_desc->calc_ticks)
2125 		axis->actual_map_desc->calc_ticks (axis);
2126 
2127 	if (axis->type == GOG_AXIS_PSEUDO_3D || axis->type == GOG_AXIS_Z) {
2128 		GSList *l;
2129 		for (l = axis->contributors; l; l = l->next) {
2130 			gog_plot_update_3d (GOG_PLOT (l->data));
2131 		}
2132 	}
2133 }
2134 
2135 #ifdef GOFFICE_WITH_GTK
2136 static GOFormat *
gog_axis_get_dim_format(GogAxis * axis,unsigned dim)2137 gog_axis_get_dim_format (GogAxis *axis, unsigned dim)
2138 {
2139 	g_return_val_if_fail (GOG_IS_AXIS (axis), NULL);
2140 
2141 	if (!axis->actual_map_desc->get_dim_format)
2142 		return NULL;
2143 
2144 	return axis->actual_map_desc->get_dim_format (axis, dim);
2145 }
2146 #endif
2147 
2148 /************************************************************************/
2149 
2150 typedef GogAxisBaseClass GogAxisClass;
2151 
2152 static GType gog_axis_view_get_type (void);
2153 
2154 static GObjectClass *parent_klass;
2155 
2156 enum {
2157 	AXIS_PROP_0,
2158 	AXIS_PROP_TYPE,
2159 	AXIS_PROP_INVERT,
2160 	AXIS_PROP_MAP,
2161 	AXIS_PROP_ASSIGNED_FORMAT_STR_XL,
2162 	AXIS_PROP_CIRCULAR_ROTATION,
2163 	AXIS_PROP_POLAR_UNIT,
2164 	AXIS_PROP_SPAN_START,
2165 	AXIS_PROP_SPAN_END,
2166 	AXIS_PROP_COLOR_MAP,
2167 	AXIS_PROP_METRICS,
2168 	AXIS_PROP_REF_AXIS,
2169 	AXIS_PROP_METRICS_RATIO,
2170 	AXIS_PROP_METRICS_UNIT,
2171 	AXIS_PROP_DISPLAY_FACTOR
2172 };
2173 
2174 /*****************************************************************************/
2175 
2176 static gboolean
role_axis_line_can_add(GogObject const * parent)2177 role_axis_line_can_add (GogObject const *parent)
2178 {
2179 	GogChart *chart = GOG_AXIS_BASE (parent)->chart;
2180 	GogAxisSet axis_set = gog_chart_get_axis_set (chart);
2181 
2182 	if (axis_set == GOG_AXIS_SET_XY ||
2183 	    (axis_set == GOG_AXIS_SET_RADAR &&
2184 	     gog_axis_get_atype (GOG_AXIS (parent)) == GOG_AXIS_RADIAL))
2185 		return TRUE;
2186 
2187 	return FALSE;
2188 }
2189 
2190 static void
role_axis_line_post_add(GogObject * parent,GogObject * child)2191 role_axis_line_post_add (GogObject *parent, GogObject *child)
2192 {
2193 	gog_axis_base_set_position (GOG_AXIS_BASE (child), GOG_AXIS_AUTO);
2194 }
2195 
2196 /**
2197  * gog_axis_set_format:
2198  * @axis: #GogAxis
2199  * @fmt: (transfer full) (nullable): #GOFormat
2200  *
2201  * Returns: %TRUE if things changed
2202  **/
2203 gboolean
gog_axis_set_format(GogAxis * axis,GOFormat * fmt)2204 gog_axis_set_format (GogAxis *axis, GOFormat *fmt)
2205 {
2206 	g_return_val_if_fail (GOG_IS_AXIS (axis), FALSE);
2207 
2208 	if (go_format_eq (fmt, axis->assigned_format)) {
2209 		go_format_unref (fmt);
2210 		return FALSE;
2211 	}
2212 
2213 	go_format_unref (axis->assigned_format);
2214 	axis->assigned_format = fmt;
2215 
2216 	gog_axis_figure_subclass (axis);
2217 
2218 	gog_object_request_update (GOG_OBJECT (axis));
2219 	return TRUE;
2220 }
2221 
2222 /**
2223  * gog_axis_get_format:
2224  * @axis: #GogAxis
2225  *
2226  * Returns: (transfer none): the format assigned to @axis.
2227  **/
2228 GOFormat *
gog_axis_get_format(GogAxis const * axis)2229 gog_axis_get_format (GogAxis const *axis)
2230 {
2231 	g_return_val_if_fail (GOG_IS_AXIS (axis), NULL);
2232 	return axis->assigned_format;
2233 }
2234 
2235 static gboolean
gog_axis_set_atype(GogAxis * axis,GogAxisType new_type)2236 gog_axis_set_atype (GogAxis *axis, GogAxisType new_type)
2237 {
2238 	if (axis->type == new_type)
2239 		return FALSE;
2240 
2241 	axis->type = new_type;
2242 	gog_axis_figure_subclass (axis);
2243 	if (new_type == GOG_AXIS_PSEUDO_3D)
2244 		g_object_set (G_OBJECT (axis),
2245 			      "major-tick-labeled", FALSE,
2246 			      "major-tick-in", FALSE,
2247 			      "major-tick-out", FALSE,
2248 			      "minor-tick-in", FALSE,
2249 			      "minor-tick-out", FALSE,
2250 			      NULL);
2251 	return TRUE;
2252 }
2253 
2254 GogAxisType
gog_axis_get_atype(GogAxis const * axis)2255 gog_axis_get_atype (GogAxis const *axis)
2256 {
2257 	g_return_val_if_fail (GOG_IS_AXIS (axis), GOG_AXIS_UNKNOWN);
2258 	return axis->type;
2259 }
2260 
2261 static void
gog_axis_prep_sax(GOPersist * gp,GsfXMLIn * xin,xmlChar const ** attrs)2262 gog_axis_prep_sax (GOPersist *gp, GsfXMLIn *xin, xmlChar const **attrs)
2263 {
2264 	/* Nothing to do */
2265 }
2266 
2267 static void
gog_axis_sax_save(GOPersist const * gp,GsfXMLOut * output)2268 gog_axis_sax_save (GOPersist const *gp, GsfXMLOut *output)
2269 {
2270 	GogAxis const *axis;
2271 	GoResourceType type;
2272 
2273 	g_return_if_fail (GOG_IS_AXIS (gp));
2274 	axis = (GogAxis const*) gp;
2275 	if (axis->auto_color_map)
2276 		return;
2277 	type = gog_axis_color_map_get_resource_type (axis->color_map);
2278 	if (type == GO_RESOURCE_CHILD || type == GO_RESOURCE_NATIVE)
2279 		return;
2280 	go_doc_save_resource (gog_graph_get_document (gog_object_get_graph (GOG_OBJECT (gp))),
2281 	                      GO_PERSIST (axis->color_map));
2282 }
2283 
2284 struct axis_ref_struct {
2285 	GogAxis *axis;
2286 	char *ref;
2287 };
2288 
2289 static gboolean
axis_ref_load_cb(struct axis_ref_struct * s)2290 axis_ref_load_cb (struct axis_ref_struct *s)
2291 {
2292 	GogObject *parent = gog_object_get_parent ((GogObject *) s->axis);
2293 	GogAxis *axis;
2294 	GSList *ptr, *l;
2295 	l = gog_object_get_children (parent, NULL);
2296 	s->axis->ref_axis = NULL;
2297 	if (s->axis->refering_axes != NULL) {
2298 		s->axis->metrics = GOG_AXIS_METRICS_DEFAULT;
2299 		s->axis->metrics_ratio = 1.;
2300 		s->axis->unit = GO_UNIT_CENTIMETER;
2301 		goto clean;
2302 	}
2303 	for (ptr = l; (ptr != NULL) && (s->axis->ref_axis == NULL); ptr = ptr->next) {
2304 		if (!GOG_IS_AXIS (ptr->data))
2305 			continue;
2306 		axis = ptr->data;
2307 		if (axis->metrics > GOG_AXIS_METRICS_ABSOLUTE || axis == s->axis)
2308 			continue;
2309 		if (!strcmp (gog_object_get_name (ptr->data), s->ref)) {
2310 			s->axis->ref_axis = axis;
2311 			axis->refering_axes = g_slist_prepend (axis->refering_axes, s->axis);
2312 		}
2313 	}
2314 	g_slist_free (l);
2315 clean:
2316 	gog_object_request_update ((GogObject *) s->axis);
2317 	g_free (s->ref);
2318 	g_free (s);
2319 	return FALSE;
2320 }
2321 
2322 static void
gog_axis_set_property(GObject * obj,guint param_id,GValue const * value,GParamSpec * pspec)2323 gog_axis_set_property (GObject *obj, guint param_id,
2324 		       GValue const *value, GParamSpec *pspec)
2325 {
2326 	GogAxis *axis = GOG_AXIS (obj);
2327 	gboolean resized = FALSE;
2328 	gboolean calc_ticks = FALSE;
2329 	gboolean request_update = FALSE;
2330 
2331 	switch (param_id) {
2332 	case AXIS_PROP_TYPE:
2333 		resized = gog_axis_set_atype (axis, g_value_get_int (value));
2334 		break;
2335 
2336 	case AXIS_PROP_INVERT: {
2337 		gboolean new_inv = g_value_get_boolean (value);
2338 		if (axis->inverted != new_inv) {
2339 			axis->inverted = new_inv;
2340 			resized = calc_ticks = TRUE;
2341 		}
2342 		break;
2343 	}
2344 	case AXIS_PROP_MAP: {
2345 		const char *s = g_value_get_string (value);
2346 		if (go_str_compare (s, axis->map_desc->name)) {
2347 			gog_axis_map_set (axis, s);
2348 			request_update = TRUE;
2349 		}
2350 		break;
2351 	}
2352 	case AXIS_PROP_ASSIGNED_FORMAT_STR_XL : {
2353 		char const *str = g_value_get_string (value);
2354 		GOFormat *newfmt = str ? go_format_new_from_XL (str) : NULL;
2355 		resized = calc_ticks = gog_axis_set_format (axis, newfmt);
2356 		break;
2357 	}
2358 	case AXIS_PROP_CIRCULAR_ROTATION:
2359 		axis->circular_rotation = CLAMP (g_value_get_double (value),
2360 						 GOG_AXIS_CIRCULAR_ROTATION_MIN,
2361 						 GOG_AXIS_CIRCULAR_ROTATION_MAX);
2362 		break;
2363 	case AXIS_PROP_POLAR_UNIT: {
2364 		char const *str = g_value_get_string (value);
2365 		unsigned int i;
2366 		for (i = 0; i < G_N_ELEMENTS (polar_units); i++) {
2367 			if (g_ascii_strcasecmp (str, polar_units[i].name) == 0) {
2368 				axis->polar_unit = i;
2369 				resized = calc_ticks = TRUE;
2370 				break;
2371 			}
2372 		}
2373 		break;
2374 	}
2375 	case AXIS_PROP_SPAN_START:
2376 		if (axis->type != GOG_AXIS_CIRCULAR) {
2377 			double new_value = g_value_get_double (value);
2378 			g_return_if_fail (new_value < axis->span_end);
2379 			axis->span_start = new_value;
2380 		}
2381 		break;
2382 	case AXIS_PROP_SPAN_END:
2383 		if (axis->type != GOG_AXIS_CIRCULAR) {
2384 			double new_value = g_value_get_double (value);
2385 			g_return_if_fail (new_value > axis->span_start);
2386 			axis->span_end = new_value;
2387 		}
2388 		break;
2389 	case AXIS_PROP_COLOR_MAP: {
2390 		char const *str = g_value_get_string (value);
2391 		GogAxisColorMap const *map = NULL;
2392 		if (strcmp (str, "default")) {
2393 			GogGraph *graph = gog_object_get_graph (GOG_OBJECT (axis));
2394 			map = gog_theme_get_color_map (gog_graph_get_theme (graph), FALSE);
2395 			if (strcmp (gog_axis_color_map_get_id (map), str))
2396 				map = gog_axis_color_map_get_from_id (str);
2397 		}
2398 		if (map) {
2399 			axis->color_map = map;
2400 			axis->auto_color_map = FALSE;
2401 		}
2402 		break;
2403 	}
2404 	case AXIS_PROP_METRICS:
2405 		axis->metrics = gog_axis_metrics_from_str (g_value_get_string (value));
2406 		break;
2407 	case AXIS_PROP_REF_AXIS: {
2408 		char const *str = g_value_get_string (value);
2409 		if ((axis->ref_axis == NULL && !strcmp (str, "none")) ||
2410 		    (axis->ref_axis && !strcmp (gog_object_get_name (GOG_OBJECT (axis->ref_axis)), str)))
2411 			return;
2412 		if (strcmp (str, "none")) {
2413 			struct axis_ref_struct *s = g_new (struct axis_ref_struct, 1);
2414 			s->ref = g_strdup (str);
2415 			s->axis = axis;
2416 			g_idle_add ((GSourceFunc) axis_ref_load_cb, s);
2417 			return;
2418 		}
2419 		axis->ref_axis = NULL;
2420 		break;
2421 	}
2422 	case AXIS_PROP_METRICS_RATIO:
2423 		axis->metrics_ratio = g_value_get_double (value);
2424 		break;
2425 	case AXIS_PROP_METRICS_UNIT:
2426 		axis->unit = (strcmp (g_value_get_string (value), "in"))? GO_UNIT_CENTIMETER: GO_UNIT_INCH;
2427 		break;
2428 	case AXIS_PROP_DISPLAY_FACTOR:
2429 		axis->display_factor = g_value_get_double (value);
2430 		break;
2431 
2432 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
2433 		 return; /* NOTE : RETURN */
2434 	}
2435 
2436 	if (request_update)
2437 		gog_object_request_update (GOG_OBJECT (axis));
2438 	else {
2439 		if (calc_ticks)
2440 			gog_axis_calc_ticks (axis);
2441 		gog_object_emit_changed (GOG_OBJECT (obj), resized);
2442 	}
2443 }
2444 
2445 static void
gog_axis_get_property(GObject * obj,guint param_id,GValue * value,GParamSpec * pspec)2446 gog_axis_get_property (GObject *obj, guint param_id,
2447 		       GValue *value, GParamSpec *pspec)
2448 {
2449 	GogAxis const *axis = GOG_AXIS (obj);
2450 
2451 	switch (param_id) {
2452 	case AXIS_PROP_TYPE:
2453 		g_value_set_int (value, axis->type);
2454 		break;
2455 	case AXIS_PROP_INVERT:
2456 		g_value_set_boolean (value, axis->inverted);
2457 		break;
2458 	case AXIS_PROP_MAP:
2459 		g_value_set_string (value, axis->map_desc->name);
2460 		break;
2461 	case AXIS_PROP_ASSIGNED_FORMAT_STR_XL :
2462 		if (axis->assigned_format != NULL)
2463 			g_value_set_string (value,
2464 				go_format_as_XL	(axis->assigned_format));
2465 		else
2466 			g_value_set_static_string (value, NULL);
2467 		break;
2468 	case AXIS_PROP_CIRCULAR_ROTATION:
2469 		g_value_set_double (value, axis->circular_rotation);
2470 		break;
2471 	case AXIS_PROP_POLAR_UNIT:
2472 		g_value_set_string (value, polar_units[axis->polar_unit].name);
2473 		break;
2474 	case AXIS_PROP_SPAN_START:
2475 		g_value_set_double (value, axis->span_start);
2476 		break;
2477 	case AXIS_PROP_SPAN_END:
2478 		g_value_set_double (value, axis->span_end);
2479 		break;
2480 	case AXIS_PROP_COLOR_MAP:
2481 		g_value_set_string (value, (axis->auto_color_map)? "default": gog_axis_color_map_get_id (axis->color_map));
2482 		break;
2483 	case AXIS_PROP_METRICS:
2484 		g_value_set_string (value, gog_axis_metrics_as_str (axis->metrics));
2485 		break;
2486 	case AXIS_PROP_REF_AXIS:
2487 		if (axis->ref_axis == NULL || axis->metrics < GOG_AXIS_METRICS_RELATIVE)
2488 			g_value_set_string (value, "none");
2489 		else
2490 			g_value_set_string (value, gog_object_get_name ((GogObject *) axis->ref_axis));
2491 		break;
2492 	case AXIS_PROP_METRICS_RATIO:
2493 		g_value_set_double (value, axis->metrics_ratio);
2494 		break;
2495 	case AXIS_PROP_METRICS_UNIT:
2496 		g_value_set_string (value, (axis->unit == GO_UNIT_INCH)? "in": "cm");
2497 		break;
2498 	case AXIS_PROP_DISPLAY_FACTOR:
2499 		g_value_set_double (value, axis->display_factor);
2500 		break;
2501 
2502 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
2503 		 break;
2504 	}
2505 }
2506 
2507 static void
gog_axis_finalize(GObject * obj)2508 gog_axis_finalize (GObject *obj)
2509 {
2510 	GogAxis *axis = GOG_AXIS (obj);
2511 
2512 	gog_axis_clear_contributors (axis);
2513 
2514 	g_slist_free (axis->contributors);	axis->contributors = NULL;
2515 	if (axis->labels != NULL) {
2516 		g_object_unref (axis->labels);
2517 		axis->labels   = NULL;
2518 		/* this is for information only, no ref */
2519 		axis->plot_that_supplied_labels = NULL;
2520 	}
2521 	go_format_unref (axis->assigned_format);
2522 	go_format_unref (axis->format);
2523 
2524 	gog_axis_set_ticks (axis, 0, NULL);
2525 
2526 	gog_dataset_finalize (GOG_DATASET (axis));
2527 	(parent_klass->finalize) (obj);
2528 }
2529 
2530 /**
2531  * gog_axis_get_entry:
2532  * @axis: #GogAxis
2533  * @i:
2534  * @user_defined: an optionally %NULL pointr to gboolean
2535  *
2536  * Returns: the value of axis element @i and sets @user_defined or
2537  * 	NaN on error
2538  **/
2539 double
gog_axis_get_entry(GogAxis const * axis,GogAxisElemType i,gboolean * user_defined)2540 gog_axis_get_entry (GogAxis const *axis, GogAxisElemType i, gboolean *user_defined)
2541 {
2542 	GOData *dat;
2543 
2544 	if (user_defined)
2545 		*user_defined = FALSE;
2546 
2547 	g_return_val_if_fail (GOG_IS_AXIS (axis), go_nan);
2548 	g_return_val_if_fail (i >= GOG_AXIS_ELEM_MIN && i < GOG_AXIS_ELEM_MAX_ENTRY, go_nan);
2549 
2550 	if (i != GOG_AXIS_ELEM_CROSS_POINT)
2551 		dat = axis->source[i].data;
2552 	else
2553 		dat = GOG_AXIS_BASE (axis)->cross_location.data;
2554 
2555 	if (GO_IS_DATA (dat)) {
2556 		double tmp = go_data_get_scalar_value (dat);
2557 		if (go_finite (tmp)) {
2558 			if (user_defined)
2559 				*user_defined = TRUE;
2560 			return tmp;
2561 		}
2562 	}
2563 
2564 	if (i != GOG_AXIS_ELEM_CROSS_POINT)
2565 		return axis->auto_bound[i];
2566 	else
2567 		return 0.;
2568 }
2569 
2570 static void
gog_axis_update(GogObject * obj)2571 gog_axis_update (GogObject *obj)
2572 {
2573 	GSList *ptr;
2574 	GogAxis *axis = GOG_AXIS (obj);
2575 	double old_min = axis->auto_bound[GOG_AXIS_ELEM_MIN];
2576 	double old_max = axis->auto_bound[GOG_AXIS_ELEM_MAX];
2577 	GogPlotBoundInfo bounds;
2578 
2579 	gog_debug (0, g_warning ("axis::update"););
2580 
2581 	if (axis->labels != NULL) {
2582 		g_object_unref (axis->labels);
2583 		axis->labels   = NULL;
2584 		axis->plot_that_supplied_labels = NULL;
2585 	}
2586 	axis->is_discrete = TRUE;
2587 	axis->min_val = +DBL_MAX;
2588 	axis->max_val = -DBL_MAX;
2589 	axis->min_contrib = axis->max_contrib = NULL;
2590 	go_format_unref (axis->format);
2591 	axis->format = NULL;
2592 	axis->date_conv = NULL;
2593 
2594 	/* Everything else is initialized in gog_plot_get_axis_bounds */
2595 	bounds.fmt = NULL;
2596 
2597 	for (ptr = axis->contributors ; ptr != NULL ; ptr = ptr->next) {
2598 		GogPlot *plot = GOG_PLOT (ptr->data);
2599 		GogObject *ploto = GOG_OBJECT (plot);
2600 		GOData *labels;
2601 
2602 		labels = gog_plot_get_axis_bounds (plot, axis->type, &bounds);
2603 		if (bounds.date_conv)
2604 			axis->date_conv = bounds.date_conv;
2605 
2606 		/* value dimensions have more information than index dimensions.
2607 		 * At least thats what I am guessing today*/
2608 		if (!bounds.is_discrete)
2609 			axis->is_discrete = FALSE;
2610 		else if (axis->labels == NULL && labels != NULL) {
2611 			g_object_ref (labels);
2612 			axis->labels = labels;
2613 			axis->plot_that_supplied_labels = plot;
2614 		}
2615 		axis->center_on_ticks = bounds.center_on_ticks;
2616 
2617 		if (axis->min_val > bounds.val.minima) {
2618 			axis->min_val = bounds.val.minima;
2619 			axis->logical_min_val = bounds.logical.minima;
2620 			axis->min_contrib = ploto;
2621 		} else if (axis->min_contrib == ploto) {
2622 			axis->min_contrib = NULL;
2623 			axis->min_val = bounds.val.minima;
2624 		}
2625 
2626 		if (axis->max_val < bounds.val.maxima) {
2627 			axis->max_val = bounds.val.maxima;
2628 			axis->logical_max_val = bounds.logical.maxima;
2629 			axis->max_contrib = ploto;
2630 		} else if (axis->max_contrib == ploto) {
2631 			axis->max_contrib = NULL;
2632 			axis->max_val = bounds.val.maxima;
2633 		}
2634 	}
2635 	axis->format = bounds.fmt; /* just absorb the ref if it exists */
2636 
2637 	gog_axis_figure_subclass (axis);
2638 
2639 	gog_axis_auto_bound (axis);
2640 
2641 	if (go_finite (axis->logical_min_val) &&
2642 	    axis->auto_bound[GOG_AXIS_ELEM_MIN] < axis->logical_min_val)
2643 		axis->auto_bound[GOG_AXIS_ELEM_MIN] = axis->logical_min_val;
2644 	if (go_finite (axis->logical_max_val) &&
2645 	    axis->auto_bound[GOG_AXIS_ELEM_MAX] > axis->logical_max_val)
2646 		axis->auto_bound[GOG_AXIS_ELEM_MAX] = axis->logical_max_val;
2647 
2648 	gog_axis_calc_ticks (axis);
2649 
2650 	if (old_min != axis->auto_bound[GOG_AXIS_ELEM_MIN] ||
2651 	    old_max != axis->auto_bound[GOG_AXIS_ELEM_MAX])
2652 		gog_object_emit_changed (GOG_OBJECT (obj), TRUE);
2653 }
2654 
2655 #ifdef GOFFICE_WITH_GTK
2656 
2657 typedef struct {
2658 	GogAxis		*axis;
2659 	GtkWidget 	*format_selector;
2660 	GtkComboBox *color_map_combo;
2661 	GOCmdContext *cc;
2662 	GtkBuilder *gui;
2663 	GogDataEditor *de[GOG_AXIS_ELEM_CROSS_POINT];
2664 } GogAxisPrefState;
2665 
2666 static void
gog_axis_pref_state_free(GogAxisPrefState * state)2667 gog_axis_pref_state_free (GogAxisPrefState *state)
2668 {
2669 	if (state->axis != NULL)
2670 		g_object_unref (state->axis);
2671 	g_free (state);
2672 }
2673 
2674 static void
cb_axis_toggle_changed(GtkToggleButton * toggle_button,GObject * axis)2675 cb_axis_toggle_changed (GtkToggleButton *toggle_button, GObject *axis)
2676 {
2677 	g_object_set (axis,
2678 		gtk_buildable_get_name (GTK_BUILDABLE (toggle_button)),
2679 		gtk_toggle_button_get_active (toggle_button),
2680 		NULL);
2681 }
2682 
2683 typedef struct {
2684 	GtkWidget *editor;
2685 	GtkToggleButton *toggle;
2686 	GogDataset *set;
2687 	unsigned dim;
2688 	gulong update_editor_handler;
2689 	gulong toggle_handler;
2690 } ElemToggleData;
2691 
2692 static void
elem_toggle_data_free(ElemToggleData * data)2693 elem_toggle_data_free (ElemToggleData *data)
2694 {
2695 	g_signal_handler_disconnect (G_OBJECT (data->set), data->update_editor_handler);
2696 	g_free (data);
2697 }
2698 
2699 static void
set_to_auto_value(ElemToggleData * closure)2700 set_to_auto_value (ElemToggleData *closure)
2701 {
2702 	GogAxis *axis = GOG_AXIS (closure->set);
2703 	double bound = axis->auto_bound[closure->dim];
2704 	GODateConventions const *date_conv = axis->date_conv;
2705 
2706 	gog_data_editor_set_value_double (GOG_DATA_EDITOR (closure->editor),
2707 					  bound, date_conv);
2708 }
2709 
2710 static void
cb_enable_dim(GtkToggleButton * toggle_button,ElemToggleData * closure)2711 cb_enable_dim (GtkToggleButton *toggle_button, ElemToggleData *closure)
2712 {
2713 	gboolean is_auto = gtk_toggle_button_get_active (toggle_button);
2714 
2715 	gtk_widget_set_sensitive (closure->editor, !is_auto);
2716 
2717 	if (is_auto) {
2718 		gog_dataset_set_dim (closure->set, closure->dim, NULL, NULL);
2719 		set_to_auto_value (closure);
2720 	} else {
2721 		GogAxis *axis = GOG_AXIS (closure->set);
2722 		GOData *data = go_data_scalar_val_new (axis->auto_bound[closure->dim]);
2723 		gog_dataset_set_dim (closure->set, closure->dim, data, NULL);
2724 	}
2725 
2726 }
2727 
2728 static void
cb_update_dim_editor(GogObject * gobj,ElemToggleData * closure)2729 cb_update_dim_editor (GogObject *gobj, ElemToggleData *closure)
2730 {
2731 	gboolean is_auto;
2732 
2733 	g_signal_handler_block (closure->toggle, closure->toggle_handler);
2734 
2735 	is_auto = (gog_dataset_get_dim (closure->set, closure->dim) == NULL);
2736 
2737 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (closure->toggle), is_auto);
2738 	gtk_widget_set_sensitive (GTK_WIDGET (closure->editor), !is_auto);
2739 
2740 	if (is_auto)
2741 		set_to_auto_value (closure);
2742 
2743 	g_signal_handler_unblock (closure->toggle, closure->toggle_handler);
2744 }
2745 
2746 static GogDataEditor *
make_dim_editor(GogDataset * set,GtkGrid * grid,unsigned dim,GogDataAllocator * dalloc,char const * dim_name)2747 make_dim_editor (GogDataset *set, GtkGrid *grid, unsigned dim,
2748 		 GogDataAllocator *dalloc, char const *dim_name)
2749 {
2750 	ElemToggleData *info;
2751 	GogDataEditor *deditor = gog_data_allocator_editor (dalloc, set, dim, GOG_DATA_SCALAR);
2752 	GtkWidget *editor = GTK_WIDGET (deditor);
2753 	char *txt;
2754 	GtkWidget *toggle;
2755 	GogAxis *axis = GOG_AXIS (set);
2756 	GOFormat const *fmt;
2757 
2758 	txt = g_strconcat (dim_name, ":", NULL);
2759 	toggle = gtk_check_button_new_with_mnemonic (txt);
2760 	g_free (txt);
2761 
2762 	fmt = gog_axis_get_dim_format (axis, dim);
2763 	gog_data_editor_set_format (deditor, fmt);
2764 	go_format_unref (fmt);
2765 
2766 	info = g_new0 (ElemToggleData, 1);
2767 	info->editor = editor;
2768 	info->set = set;
2769 	info->dim = dim;
2770 	info->toggle = GTK_TOGGLE_BUTTON (toggle);
2771 
2772 	info->toggle_handler = g_signal_connect (G_OBJECT (toggle),
2773 						 "toggled",
2774 						 G_CALLBACK (cb_enable_dim), info);
2775 
2776 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
2777 				      gog_dataset_get_dim (set, dim) == NULL);
2778 
2779 	info->update_editor_handler = g_signal_connect (G_OBJECT (set),
2780 							"update-editor",
2781 							G_CALLBACK (cb_update_dim_editor), info);
2782 
2783 	g_object_weak_ref (G_OBJECT (toggle), (GWeakNotify) elem_toggle_data_free, info);
2784 
2785 	gtk_grid_attach (grid, toggle,
2786 		0, dim + 1, 1, 1);
2787 	g_object_set (G_OBJECT (editor), "hexpand", TRUE, NULL);
2788 	gtk_grid_attach (grid, editor,
2789 		1, dim + 1, 1, 1);
2790 	return GOG_DATA_EDITOR (editor);
2791 }
2792 
2793 static void
cb_axis_fmt_changed(G_GNUC_UNUSED GtkWidget * widget,char * fmt,GogAxis * axis)2794 cb_axis_fmt_changed (G_GNUC_UNUSED GtkWidget *widget,
2795 		     char *fmt,
2796 		     GogAxis *axis)
2797 {
2798 	g_object_set (axis, "assigned-format-string-XL", fmt, NULL);
2799 }
2800 
2801 static void
cb_map_combo_changed(GtkComboBox * combo,GogAxis * axis)2802 cb_map_combo_changed (GtkComboBox *combo,
2803 		      GogAxis *axis)
2804 {
2805 	gog_axis_map_set_by_num (axis, gtk_combo_box_get_active (combo));
2806 }
2807 
2808 static void
gog_axis_populate_polar_unit_combo(GogAxis * axis,GtkComboBoxText * combo)2809 gog_axis_populate_polar_unit_combo (GogAxis *axis, GtkComboBoxText *combo)
2810 {
2811 	unsigned i, id = 0;
2812 
2813 	g_return_if_fail (GOG_IS_AXIS (axis));
2814 
2815 	for (i = 0; i < G_N_ELEMENTS (polar_units); i++) {
2816 		gtk_combo_box_text_append_text (combo, _(polar_units[i].name));
2817 		if (polar_units[i].unit == axis->polar_unit)
2818 			id = i;
2819 	}
2820 	gtk_combo_box_set_active (GTK_COMBO_BOX (combo), id);
2821 }
2822 
2823 static void
cb_polar_unit_changed(GtkComboBox * combo,GogAxisPrefState * state)2824 cb_polar_unit_changed (GtkComboBox *combo,
2825 		       GogAxisPrefState *state)
2826 {
2827 	GogAxis *axis = state->axis;
2828 	GOFormat *format;
2829 	int i;
2830 
2831 	axis->polar_unit = gtk_combo_box_get_active (combo);
2832 	format = go_format_new_from_XL (polar_units[axis->polar_unit].xl_format);
2833 
2834 	if (gog_axis_set_format (axis, format) &&
2835 	    state->format_selector != NULL)
2836 		go_format_sel_set_style_format (GO_FORMAT_SEL (state->format_selector), format);
2837 	gog_axis_auto_bound (axis);
2838 	for (i = GOG_AXIS_ELEM_MIN; i < GOG_AXIS_ELEM_CROSS_POINT; i++) {
2839 		GOData *data = gog_dataset_get_dim (GOG_DATASET (axis), i);
2840 		gog_data_editor_set_value_double (state->de[i],
2841 			                              (data)? go_data_get_scalar_value (data): axis->auto_bound[i],
2842 			                              axis->date_conv);
2843 	}
2844 }
2845 
2846 static void
cb_rotation_changed(GtkSpinButton * spin,GogAxis * axis)2847 cb_rotation_changed (GtkSpinButton *spin, GogAxis *axis)
2848 {
2849 	axis->circular_rotation = CLAMP (gtk_spin_button_get_value (spin),
2850 					 GOG_AXIS_CIRCULAR_ROTATION_MIN,
2851 					 GOG_AXIS_CIRCULAR_ROTATION_MAX);
2852 	gog_object_emit_changed (GOG_OBJECT (axis), TRUE);
2853 }
2854 
2855 static void
cb_start_changed(GtkSpinButton * spin,GogAxis * axis)2856 cb_start_changed (GtkSpinButton *spin, GogAxis *axis)
2857 {
2858 	double val = gtk_spin_button_get_value (spin);
2859 	GtkAdjustment *adj = GTK_ADJUSTMENT (g_object_get_data (G_OBJECT (spin), "other-adj"));
2860 	gtk_adjustment_set_lower (adj, fmin (val + 1, axis->span_end * 100.));
2861 	g_object_set (axis, "span-start", val /100., NULL);
2862 }
2863 
2864 static void
cb_end_changed(GtkSpinButton * spin,GogAxis * axis)2865 cb_end_changed (GtkSpinButton *spin, GogAxis *axis)
2866 {
2867 	double val = gtk_spin_button_get_value (spin);
2868 	GtkAdjustment *adj = GTK_ADJUSTMENT (g_object_get_data (G_OBJECT (spin), "other-adj"));
2869 	gtk_adjustment_set_upper (adj, fmax (val - 1, axis->span_start * 100.));
2870 	g_object_set (axis, "span-end", val /100., NULL);
2871 }
2872 
2873 static void
color_map_new_cb(GogAxisPrefState * state)2874 color_map_new_cb (GogAxisPrefState *state)
2875 {
2876 	GogAxisColorMap *map = gog_axis_color_map_edit (NULL, state->cc);
2877 	if (map) {
2878 		GtkListStore *model = GTK_LIST_STORE (gtk_combo_box_get_model (state->color_map_combo));
2879 		GtkTreeIter iter;
2880 		gtk_list_store_append (model, &iter);
2881 		gtk_list_store_set (model, &iter,
2882 		                    0, gog_axis_color_map_get_name (map),
2883 		                    1, gog_axis_color_map_get_snapshot (map, state->axis->type == GOG_AXIS_PSEUDO_3D,
2884 		                                                        TRUE, 200, 16),
2885 		                    2, map,
2886 		                    -1);
2887 		gtk_combo_box_set_active_iter (state->color_map_combo, &iter);
2888 		gog_object_emit_changed (GOG_OBJECT (state->axis), FALSE);
2889 	}
2890 }
2891 
2892 static void
color_map_dup_cb(GogAxisPrefState * state)2893 color_map_dup_cb (GogAxisPrefState *state)
2894 {
2895 	GogAxisColorMap *map = gog_axis_color_map_dup (state->axis->color_map);
2896 	if (gog_axis_color_map_edit (map, state->cc) != NULL) {
2897 		GtkListStore *model = GTK_LIST_STORE (gtk_combo_box_get_model (state->color_map_combo));
2898 		GtkTreeIter iter;
2899 		gtk_list_store_append (model, &iter);
2900 		gtk_list_store_set (model, &iter,
2901 		                    0, gog_axis_color_map_get_name (map),
2902 		                    1, gog_axis_color_map_get_snapshot (map, state->axis->type == GOG_AXIS_PSEUDO_3D,
2903 		                                                        TRUE, 200, 16),
2904 		                    2, map,
2905 		                    -1);
2906 		gtk_combo_box_set_active_iter (state->color_map_combo, &iter);
2907 		gog_object_emit_changed (GOG_OBJECT (state->axis), FALSE);
2908 	} else
2909 		g_object_unref (map);
2910 }
2911 
2912 static void
color_map_save_cb(GogAxisPrefState * state)2913 color_map_save_cb (GogAxisPrefState *state)
2914 {
2915 	go_persist_sax_save (GO_PERSIST (state->axis->color_map), NULL);
2916 	gtk_widget_hide (go_gtk_builder_get_widget (state->gui, "save-btn"));
2917 }
2918 
2919 static void
color_map_changed_cb(GtkComboBox * combo,GogAxisPrefState * state)2920 color_map_changed_cb (GtkComboBox *combo, GogAxisPrefState *state)
2921 {
2922 	GtkTreeModel *model = gtk_combo_box_get_model (combo);
2923 	GtkTreeIter iter;
2924 	GogAxisColorMap *map;
2925 	GogTheme *theme = gog_graph_get_theme (gog_object_get_graph (GOG_OBJECT (state->axis)));
2926 	gtk_combo_box_get_active_iter (combo, &iter);
2927 	gtk_tree_model_get (model, &iter, 2, &map, -1);
2928 	state->axis->color_map = map;
2929 	state->axis->auto_color_map = map == gog_theme_get_color_map (theme, state->axis->type == GOG_AXIS_PSEUDO_3D);
2930 	gog_object_emit_changed (GOG_OBJECT (state->axis), FALSE);
2931 	gtk_widget_set_visible (go_gtk_builder_get_widget (state->gui, "save-btn"),
2932 	                        gog_axis_color_map_get_resource_type (map) == GO_RESOURCE_EXTERNAL);
2933 }
2934 
2935 struct ColorMapState {
2936 	GtkComboBox *combo;
2937 	GtkListStore *model;
2938 	GogAxisColorMap const *target;
2939 	gboolean discrete;
2940 };
2941 
2942 static void
add_color_map_cb(GogAxisColorMap const * map,struct ColorMapState * state)2943 add_color_map_cb (GogAxisColorMap const *map, struct ColorMapState *state)
2944 {
2945 	GtkTreeIter iter;
2946 	GdkPixbuf *pixbuf = gog_axis_color_map_get_snapshot (map, state->discrete,
2947 		                                          TRUE, 200, 16);
2948 	gtk_list_store_append (state->model, &iter);
2949 	gtk_list_store_set (state->model, &iter, 0, gog_axis_color_map_get_name (map),
2950 	                    1, pixbuf, 2, map, -1);
2951 	g_object_unref (pixbuf);
2952 	if (map == state->target)
2953 		gtk_combo_box_set_active_iter (state->combo, &iter);
2954 }
2955 
2956 struct MetricsState {
2957 	GogAxis *axis;
2958 	GtkWidget *axes, *label, *ratio, *unit;
2959 };
2960 
2961 static void
metrics_ratio_changed_cb(GtkSpinButton * btn,struct MetricsState * state)2962 metrics_ratio_changed_cb (GtkSpinButton *btn, struct MetricsState *state)
2963 {
2964 	state->axis->metrics_ratio = gtk_spin_button_get_value (btn);
2965 	gog_object_request_update ((GogObject *) state->axis);
2966 }
2967 
2968 static void
metrics_unit_changed_cb(GtkComboBox * box,struct MetricsState * state)2969 metrics_unit_changed_cb (GtkComboBox *box, struct MetricsState *state)
2970 {
2971 	GtkTreeIter iter;
2972 	GtkTreeModel *model = gtk_combo_box_get_model (box);
2973 	gtk_combo_box_get_active_iter (box, &iter);
2974 	gtk_tree_model_get (model, &iter, 1, &state->axis->unit, -1);
2975 	gog_object_request_update ((GogObject *) state->axis);
2976 }
2977 
2978 static void
metrics_axis_changed_cb(GtkComboBox * box,struct MetricsState * state)2979 metrics_axis_changed_cb (GtkComboBox *box, struct MetricsState *state)
2980 {
2981 	GogAxis *ref_axis;
2982 	GtkTreeIter iter;
2983 	GtkTreeModel *model;
2984 	if (state->axis->ref_axis != NULL)
2985 		state->axis->ref_axis->refering_axes = g_slist_remove (state->axis->ref_axis->refering_axes, state->axis);
2986 	model = gtk_combo_box_get_model (box);
2987 	gtk_combo_box_get_active_iter (box, &iter);
2988 	gtk_tree_model_get (model, &iter, 1, &ref_axis, -1);
2989 	ref_axis->refering_axes = g_slist_prepend (ref_axis->refering_axes, state->axis);
2990 	state->axis->ref_axis = ref_axis;
2991 	gog_object_request_update ((GogObject *) state->axis);
2992 }
2993 
2994 static void
metrics_changed_cb(GtkComboBox * box,struct MetricsState * state)2995 metrics_changed_cb (GtkComboBox *box, struct MetricsState *state)
2996 {
2997 	GtkTreeIter iter;
2998 	GtkTreeModel *model;
2999 	model = gtk_combo_box_get_model (box);
3000 	gtk_combo_box_get_active_iter (box, &iter);
3001 	gtk_tree_model_get (model, &iter, 1, &state->axis->metrics, -1);
3002 	switch (state->axis->metrics) {
3003 	case GOG_AXIS_METRICS_DEFAULT:
3004 		gtk_widget_hide (state->axes);
3005 		gtk_widget_hide (state->label);
3006 		gtk_widget_hide (state->ratio);
3007 		gtk_widget_hide (state->unit);
3008 		state->axis->ref_axis = NULL;
3009 		gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->ratio), 1.);
3010 		gtk_combo_box_set_active (GTK_COMBO_BOX (state->unit), 0);
3011 		break;
3012 	case GOG_AXIS_METRICS_ABSOLUTE:
3013 		gtk_widget_hide (state->axes);
3014 		state->axis->ref_axis = NULL;
3015 		gtk_label_set_text (GTK_LABEL (state->label), _("Distance:"));
3016 		gtk_widget_show (state->label);
3017 		gtk_widget_show (state->ratio);
3018 		gtk_widget_show (state->unit);
3019 		break;
3020 	case GOG_AXIS_METRICS_RELATIVE:
3021 		gtk_widget_show (state->axes);
3022 		gtk_label_set_text (GTK_LABEL (state->label), _("Ratio:"));
3023 		gtk_widget_show (state->label);
3024 		gtk_widget_show (state->ratio);
3025 		gtk_widget_hide (state->unit);
3026 		if (gtk_combo_box_get_active ((GtkComboBox *) state->axes) < 0)
3027 			gtk_combo_box_set_active ((GtkComboBox *) state->axes, 0);
3028 		else
3029 			metrics_axis_changed_cb (GTK_COMBO_BOX (state->axes), state);
3030 		gtk_combo_box_set_active (GTK_COMBO_BOX (state->unit), 0);
3031 		break;
3032 	case GOG_AXIS_METRICS_RELATIVE_TICKS:
3033 		gtk_widget_show (state->axes);
3034 		gtk_label_set_text (GTK_LABEL (state->label), _("Ratio:"));
3035 		gtk_widget_show (state->label);
3036 		gtk_widget_show (state->ratio);
3037 		gtk_widget_hide (state->unit);
3038 		if (gtk_combo_box_get_active ((GtkComboBox *) state->axes) < 0)
3039 				gtk_combo_box_set_active ((GtkComboBox *) state->axes, 0);
3040 		else
3041 			metrics_axis_changed_cb ((GtkComboBox *) state->axes, state);
3042 		gtk_combo_box_set_active (GTK_COMBO_BOX (state->unit), 0);
3043 		break;
3044 	default:
3045 		/* will never occur */
3046 		break;
3047 	}
3048 	gog_object_request_update ((GogObject *) state->axis);
3049 }
3050 
3051 static void
gog_axis_populate_editor(GogObject * gobj,GOEditor * editor,GogDataAllocator * dalloc,GOCmdContext * cc)3052 gog_axis_populate_editor (GogObject *gobj,
3053 			  GOEditor *editor,
3054 			  GogDataAllocator *dalloc,
3055 			  GOCmdContext *cc)
3056 {
3057 	static guint axis_pref_page = 0;
3058 	static char const *toggle_props[] = {
3059 		"invert-axis"
3060 	};
3061 	GtkWidget *w;
3062 	GtkGrid *grid;
3063 	unsigned i = 0;
3064 	GogAxis *axis = GOG_AXIS (gobj);
3065 	GogAxisPrefState *state;
3066 	GogDataset *set = GOG_DATASET (gobj);
3067 	GtkBuilder *gui;
3068 	gui = go_gtk_builder_load_internal ("res:go:graph/gog-axis-prefs.ui", GETTEXT_PACKAGE, cc);
3069 	if (gui == NULL)
3070 		return;
3071 
3072 	state = g_new0 (GogAxisPrefState, 1);
3073 	state->axis = axis;
3074 	state->cc = cc;
3075 	state->gui = gui;
3076 	g_object_ref (axis);
3077 
3078 	/* Bounds Page */
3079 	grid = GTK_GRID (gtk_builder_get_object (gui, "bound-grid"));
3080 	if (axis->is_discrete) {
3081 		static char const * const dim_names[] = {
3082 			N_("M_inimum"),
3083 			N_("M_aximum"),
3084 			N_("Categories between _ticks"),
3085 			N_("Categories between _labels")
3086 		};
3087 		for (i = GOG_AXIS_ELEM_MIN; i < GOG_AXIS_ELEM_CROSS_POINT ; i++)
3088 			state->de[i] = make_dim_editor (set, grid, i, dalloc,
3089 			                                _(dim_names[i]));
3090 	} else {
3091 		static char const * const dim_names[] = {
3092 			N_("M_inimum"),
3093 			N_("M_aximum"),
3094 			N_("Ma_jor ticks"),
3095 			N_("Mi_nor ticks")
3096 		};
3097 
3098 		for (i = GOG_AXIS_ELEM_MIN; i < GOG_AXIS_ELEM_CROSS_POINT ; i++)
3099 			state->de[i] = make_dim_editor (set, grid, i, dalloc,
3100 			                                _(dim_names[i]));
3101 	}
3102 	gtk_widget_show_all (GTK_WIDGET (grid));
3103 
3104 	/* Details page */
3105 	if (!axis->is_discrete && gog_axis_get_atype (axis) != GOG_AXIS_CIRCULAR) {
3106 		GtkComboBoxText *box = GTK_COMBO_BOX_TEXT (gtk_builder_get_object (gui, "map-type-combo"));
3107 		gog_axis_map_populate_combo (axis, box);
3108 		g_signal_connect_object (G_OBJECT (box),
3109 					 "changed",
3110 					 G_CALLBACK (cb_map_combo_changed),
3111 					 axis, 0);
3112 	} else {
3113 		GtkWidget *w = go_gtk_builder_get_widget (gui, "map-label");
3114 		gtk_widget_hide (w);
3115 		w = go_gtk_builder_get_widget (gui, "map-type-combo");
3116 		gtk_widget_hide (w);
3117 	}
3118 
3119 	if (gog_axis_get_atype (axis) == GOG_AXIS_CIRCULAR) {
3120 		GtkWidget *w;
3121 		GtkComboBoxText *box = GTK_COMBO_BOX_TEXT (gtk_builder_get_object (gui, "polar-unit-combo"));
3122 		if (!axis->is_discrete) {
3123 			gog_axis_populate_polar_unit_combo (axis, box);
3124 			g_signal_connect (G_OBJECT (box),
3125 					  "changed",
3126 					  G_CALLBACK (cb_polar_unit_changed),
3127 					  state);
3128 		} else {
3129 			gtk_widget_hide (GTK_WIDGET (box));
3130 			gtk_widget_hide (go_gtk_builder_get_widget (gui, "unit-lbl"));
3131 		}
3132 
3133 		w = go_gtk_builder_get_widget (gui, "circular-rotation-spinbutton");
3134 		gtk_spin_button_set_value (GTK_SPIN_BUTTON (w), axis->circular_rotation);
3135 		g_signal_connect_object (G_OBJECT (w), "value-changed",
3136 					 G_CALLBACK (cb_rotation_changed),
3137 					 axis, 0);
3138 	} else
3139 		gtk_widget_hide (go_gtk_builder_get_widget (gui, "circular-grid"));
3140 
3141 	for (i = 0; i < G_N_ELEMENTS (toggle_props) ; i++) {
3142 		gboolean cur_val;
3143 		GtkWidget *w = go_gtk_builder_get_widget (gui, toggle_props[i]);
3144 
3145 		g_object_get (G_OBJECT (gobj), toggle_props[i], &cur_val, NULL);
3146 		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), cur_val);
3147 		g_signal_connect_object (G_OBJECT (w),
3148 					 "toggled",
3149 					 G_CALLBACK (cb_axis_toggle_changed), axis, 0);
3150 	}
3151 
3152 	go_editor_add_page (editor,
3153 			     go_gtk_builder_get_widget (gui, "axis-pref-grid"),
3154 			     _("Scale"));
3155 
3156 	/* effective area */
3157 	/* we actually only support with some axes (X, Y, and radial) in 2D charts */
3158 	if ((axis->type == GOG_AXIS_X || axis->type == GOG_AXIS_Y || axis->type == GOG_AXIS_RADIAL) &&
3159 	    (GOG_IS_CHART (gog_object_get_parent (gobj))? !gog_chart_is_3d (GOG_CHART (gog_object_get_parent (gobj))): TRUE)) {
3160 		GtkAdjustment *adj;
3161 		double start = axis->span_start * 100., end = axis->span_end * 100.;
3162 		w = go_gtk_builder_get_widget (gui, "start-btn");
3163 		adj = GTK_ADJUSTMENT (gtk_builder_get_object (gui, "end-adj"));
3164 		gtk_spin_button_set_value (GTK_SPIN_BUTTON (w), start);
3165 		gtk_adjustment_set_lower (adj, fmin (start + 1., end));
3166 		g_signal_connect (w, "value_changed", G_CALLBACK (cb_start_changed), axis);
3167 		g_object_set_data (G_OBJECT (w), "other-adj", adj);
3168 		w = go_gtk_builder_get_widget (gui, "end-btn");
3169 		adj = GTK_ADJUSTMENT (gtk_builder_get_object (gui, "start-adj"));
3170 		gtk_spin_button_set_value (GTK_SPIN_BUTTON (w), end);
3171 		gtk_adjustment_set_upper (adj, fmax (end - 1., start));
3172 		g_signal_connect (w, "value_changed", G_CALLBACK (cb_end_changed), axis);
3173 		g_object_set_data (G_OBJECT (w), "other-adj", adj);
3174 		go_editor_add_page (editor,
3175 		                    go_gtk_builder_get_widget (gui, "area-grid"),
3176 		                    _("Span"));
3177 	}
3178 
3179 	/* color map */
3180 	if (axis->type == GOG_AXIS_COLOR || axis->type == GOG_AXIS_PSEUDO_3D) {
3181 		GtkListStore *model = GTK_LIST_STORE (gtk_builder_get_object (gui, "color-map-list"));
3182 		GtkTreeIter iter;
3183 		GdkPixbuf *pixbuf;
3184 		GogAxisColorMap const *map;
3185 		GogTheme *theme = gog_graph_get_theme (gog_object_get_graph (GOG_OBJECT (axis)));
3186 		GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
3187 		GtkWidget *combo = go_gtk_builder_get_widget (gui, "color-map-combo");
3188 		struct ColorMapState color_state;
3189 		state->color_map_combo = GTK_COMBO_BOX (combo);
3190 		gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, FALSE);
3191 		gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer,
3192 				                        "text", 0, NULL);
3193 		renderer = gtk_cell_renderer_pixbuf_new ();
3194 		gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, FALSE);
3195 		gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer,
3196 				                        "pixbuf", 1, NULL);
3197 
3198 		if (axis->type == GOG_AXIS_PSEUDO_3D) {
3199 			gtk_list_store_append (model, &iter);
3200 			map = gog_theme_get_color_map (theme, TRUE);
3201 			pixbuf = gog_axis_color_map_get_snapshot (map, TRUE,
3202 				                                      TRUE, 200, 16);
3203 			gtk_list_store_set (model, &iter, 0, gog_axis_color_map_get_name (map),
3204 				                1, pixbuf, 2, map, -1);
3205 			g_object_unref (pixbuf);
3206 			if (map == axis->color_map)
3207 				gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &iter);
3208 		}
3209 		gtk_list_store_append (model, &iter);
3210 		map = gog_theme_get_color_map (theme, FALSE);
3211 		pixbuf = gog_axis_color_map_get_snapshot (map, FALSE,
3212 		                                          TRUE, 200, 16);
3213 		gtk_list_store_set (model, &iter, 0, gog_axis_color_map_get_name (map),
3214 		                    1, pixbuf, 2, map, -1);
3215 		g_object_unref (pixbuf);
3216 		if (map == axis->color_map)
3217 			gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &iter);
3218 		/* add all locally defined maps */
3219 		color_state.combo = GTK_COMBO_BOX (combo);
3220 		color_state.model = model;
3221 		color_state.target = axis->color_map;
3222 		color_state.discrete = axis->type == GOG_AXIS_PSEUDO_3D;
3223 		gog_axis_color_map_foreach ((GogAxisColorMapHandler) add_color_map_cb, &color_state);
3224 		g_signal_connect (combo, "changed", G_CALLBACK (color_map_changed_cb), state);
3225 		w = go_gtk_builder_get_widget (gui, "new-btn");
3226 		g_signal_connect_swapped (w, "clicked", G_CALLBACK (color_map_new_cb), state);
3227 		w = go_gtk_builder_get_widget (gui, "duplicate-btn");
3228 		g_signal_connect_swapped (w, "clicked", G_CALLBACK (color_map_dup_cb), state);
3229 		w = go_gtk_builder_get_widget (gui, "save-btn");
3230 		g_signal_connect_swapped (w, "clicked", G_CALLBACK (color_map_save_cb), state);
3231 		go_editor_add_page (editor,
3232 		                    go_gtk_builder_get_widget (gui, "color-map-grid"),
3233 		                    _("Colors"));
3234 	}
3235 	/* Metrics */
3236 	if ((axis->type == GOG_AXIS_X || axis->type == GOG_AXIS_Y ||
3237 	    axis->type == GOG_AXIS_Z || axis->type == GOG_AXIS_RADIAL)
3238 	    && (gog_chart_is_3d (GOG_CHART (gog_object_get_parent ((GogObject *) axis)))
3239 	        && axis->refering_axes == NULL)) {
3240 		/* only 3d for now */
3241 		GogChart *parent = GOG_CHART (gog_object_get_parent ((GogObject *) axis));
3242 		GogAxis *axis_;
3243 		GtkGrid *grid;
3244 		GtkWidget *combo;
3245 		GSList *l, *ptr;
3246 		GtkListStore *store;
3247 		GtkCellRenderer *cell;
3248 		GtkTreeIter iter;
3249 		GtkAdjustment *adj;
3250 		GoUnit const *unit;
3251 		struct MetricsState *state = g_new (struct MetricsState, 1);
3252 		state->axis = axis;
3253 		w = gtk_grid_new ();
3254 		g_object_set (w, "border-width", 12, "column-spacing", 12, "row-spacing", 6, NULL);
3255 		gtk_widget_show (w);
3256 		grid = GTK_GRID (w);
3257 		store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_UINT);
3258 		combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
3259 		cell = gtk_cell_renderer_text_new ();
3260 		gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE);
3261 		gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell,
3262 						                "text", 0, NULL);
3263 		gtk_list_store_append (store, &iter);
3264 		gtk_list_store_set (store, &iter, 0, _("Default"), 1, GOG_AXIS_METRICS_DEFAULT, -1);
3265 		if (axis->metrics == GOG_AXIS_METRICS_DEFAULT)
3266 			gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &iter);
3267 		if (!gog_chart_is_3d ((GogChart *) parent)) {
3268 			gtk_list_store_append (store, &iter);
3269 			gtk_list_store_set (store, &iter, 0, _("Absolute"), 1, GOG_AXIS_METRICS_ABSOLUTE, -1);
3270 			if (axis->metrics == GOG_AXIS_METRICS_ABSOLUTE)
3271 				gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &iter);
3272 		}
3273 		if (axis->refering_axes == NULL) {
3274 			gtk_list_store_append (store, &iter);
3275 			gtk_list_store_set (store, &iter, 0, _("Relative length"), 1, GOG_AXIS_METRICS_RELATIVE, -1);
3276 			if (axis->metrics == GOG_AXIS_METRICS_RELATIVE)
3277 				gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &iter);
3278 			gtk_list_store_append (store, &iter);
3279 			gtk_list_store_set (store, &iter, 0, _("Relative ticks distance"), 1, GOG_AXIS_METRICS_RELATIVE_TICKS, -1);
3280 			if (axis->metrics == GOG_AXIS_METRICS_RELATIVE_TICKS)
3281 				gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &iter);
3282 		}
3283 		gtk_grid_attach (grid, combo, 0, 0, 3, 1);
3284 		gtk_widget_show ((GtkWidget *) combo);
3285 		state->axes = gtk_combo_box_new ();
3286 		store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
3287 		gtk_combo_box_set_model (GTK_COMBO_BOX (state->axes), GTK_TREE_MODEL (store));
3288 		cell = gtk_cell_renderer_text_new ();
3289 		gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (state->axes), cell, TRUE);
3290 		gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (state->axes), cell,
3291 						                "text", 0, NULL);
3292 		l = gog_object_get_children ((GogObject *) parent, NULL);
3293 		for (ptr = l; ptr != NULL; ptr = ptr->next) {
3294 			if (!GOG_IS_AXIS (ptr->data))
3295 				continue;
3296 			axis_ = ptr->data;
3297 			if (axis_->metrics > GOG_AXIS_METRICS_ABSOLUTE || axis_ == axis)
3298 				continue;
3299 			gtk_list_store_append (store, &iter);
3300 			gtk_list_store_set (store, &iter, 0, gog_object_get_name (ptr->data), 1, axis_, -1);
3301 			if (axis_ == axis->ref_axis)
3302 				gtk_combo_box_set_active_iter (GTK_COMBO_BOX (state->axes), &iter);
3303 		}
3304 		/* now add a spin button for the metrix_ratio */
3305 		gtk_grid_attach (grid, state->axes, 0, 1, 3, 1);
3306 		g_signal_connect (state->axes, "changed", G_CALLBACK (metrics_axis_changed_cb), state);
3307 		if (axis->metrics > GOG_AXIS_METRICS_ABSOLUTE)
3308 			gtk_widget_show (state->axes);
3309 		state->label = gtk_label_new (NULL); /* the text will be set later */
3310 		gtk_grid_attach (grid, state->label, 0, 2, 1, 1);
3311 		g_signal_connect (combo, "changed", G_CALLBACK (metrics_changed_cb), state);
3312 		/* now add a spin button for the metrix_ratio */
3313 		adj = gtk_adjustment_new (1., 0.01, 100., .1, 1., 1.);
3314 		state->ratio = gtk_spin_button_new (adj, .1, 2);
3315 		gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->ratio), axis->metrics_ratio);
3316 		g_signal_connect (state->ratio, "value-changed", G_CALLBACK (metrics_ratio_changed_cb), state);
3317 		gtk_grid_attach (grid, state->ratio, 1, 2, 1, 1);
3318 		state->unit = gtk_combo_box_new ();
3319 		store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
3320 		gtk_combo_box_set_model (GTK_COMBO_BOX (state->unit), GTK_TREE_MODEL (store));
3321 		cell = gtk_cell_renderer_text_new ();
3322 		gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (state->unit), cell, TRUE);
3323 		gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (state->unit), cell,
3324 						                "text", 0, NULL);
3325 		unit = go_unit_get (GO_UNIT_CENTIMETER);
3326 		gtk_list_store_append (store, &iter);
3327 		gtk_list_store_set (store, &iter, 0, go_unit_get_symbol (unit), 1, unit, -1);
3328 		if (axis->unit == GO_UNIT_CENTIMETER)
3329 			gtk_combo_box_set_active_iter (GTK_COMBO_BOX (state->unit), &iter);
3330 		unit = go_unit_get (GO_UNIT_INCH);
3331 		gtk_list_store_append (store, &iter);
3332 		gtk_list_store_set (store, &iter, 0, go_unit_get_symbol (unit), 1, unit, -1);
3333 		if (axis->unit == GO_UNIT_INCH)
3334 			gtk_combo_box_set_active_iter (GTK_COMBO_BOX (state->unit), &iter);
3335 		gtk_grid_attach (grid, state->unit, 2, 2, 1, 1);
3336 		g_signal_connect (state->unit, "changed", G_CALLBACK (metrics_unit_changed_cb), state);
3337 		if (axis->metrics >= GOG_AXIS_METRICS_ABSOLUTE) {
3338 			gtk_widget_show (state->label);
3339 			gtk_widget_show (state->ratio);
3340 			if (axis->metrics == GOG_AXIS_METRICS_ABSOLUTE)
3341 				gtk_widget_show (state->unit);
3342 		}
3343 		g_object_set_data_full ((GObject *) w, "state", state, g_free);
3344 		go_editor_add_page (editor, w, _("Metrics"));
3345 	}
3346 
3347 	if (gog_object_is_visible (axis) && gog_axis_get_atype (axis) < GOG_AXIS_VIRTUAL) {
3348 	    /* Style page */
3349 	    (GOG_OBJECT_CLASS(parent_klass)->populate_editor) (gobj, editor, dalloc, cc);
3350 
3351 	    /* Format page */
3352 	    {
3353 		    GOFormat *fmt = gog_axis_get_effective_format (axis);
3354 		    w = go_format_sel_new_full (TRUE);
3355 		    state->format_selector = w;
3356 
3357 		    if (fmt)
3358 			    go_format_sel_set_style_format (GO_FORMAT_SEL (w),
3359 							    fmt);
3360 			gtk_widget_show (w);
3361 
3362 		    go_editor_add_page (editor, w, _("Format"));
3363 
3364 		    g_signal_connect (G_OBJECT (w),
3365 			    "format_changed", G_CALLBACK (cb_axis_fmt_changed), axis);
3366 	    }
3367 	}
3368 
3369 	g_signal_connect_swapped (gtk_builder_get_object (gui, "axis-pref-grid"),
3370 	                          "destroy", G_CALLBACK (g_object_unref), gui);
3371 	g_object_set_data_full (gtk_builder_get_object (gui, "axis-pref-grid"),
3372 				"state", state, (GDestroyNotify) gog_axis_pref_state_free);
3373 
3374 	go_editor_set_store_page (editor, &axis_pref_page);
3375 }
3376 #endif
3377 
3378 static void
gog_axis_init_style(GogStyledObject * gso,GOStyle * style)3379 gog_axis_init_style (GogStyledObject *gso, GOStyle *style)
3380 {
3381 	GogAxis *axis = GOG_AXIS (gso);
3382 	GogAxisType type = gog_axis_get_atype (axis);
3383 	GogTheme *theme = gog_object_get_theme (GOG_OBJECT (gso));
3384 	if (type != GOG_AXIS_PSEUDO_3D && type != GOG_AXIS_COLOR)
3385 		style->interesting_fields = GO_STYLE_LINE | GO_STYLE_FONT |
3386 			GO_STYLE_TEXT_LAYOUT;
3387 	else {
3388 		style->interesting_fields = 0;
3389 		if (axis->auto_color_map)
3390 			axis->color_map = gog_theme_get_color_map (theme, type == GOG_AXIS_PSEUDO_3D);
3391 	}
3392 	gog_theme_fillin_style (theme, style, GOG_OBJECT (gso), 0, GO_STYLE_LINE |
3393 	                        GO_STYLE_FONT | GO_STYLE_TEXT_LAYOUT);
3394 }
3395 
3396 static void
gog_axis_class_init(GObjectClass * gobject_klass)3397 gog_axis_class_init (GObjectClass *gobject_klass)
3398 {
3399 	static GogObjectRole const roles[] = {
3400 		{ N_("AxisLine"), "GogAxisLine", 2,
3401 		  GOG_POSITION_PADDING, GOG_POSITION_PADDING, GOG_OBJECT_NAME_BY_ROLE,
3402 		  role_axis_line_can_add, NULL, NULL, role_axis_line_post_add, NULL, NULL, { -1 } },
3403 	};
3404 
3405 	GogObjectClass *gog_klass = (GogObjectClass *) gobject_klass;
3406 	GogStyledObjectClass *style_klass = (GogStyledObjectClass *) gog_klass;
3407 
3408 	parent_klass = g_type_class_peek_parent (gobject_klass);
3409 	gobject_klass->set_property = gog_axis_set_property;
3410 	gobject_klass->get_property = gog_axis_get_property;
3411 	gobject_klass->finalize	    = gog_axis_finalize;
3412 
3413 	/* no need to persist, the role handles that */
3414 	g_object_class_install_property (gobject_klass, AXIS_PROP_TYPE,
3415 		g_param_spec_int ("type", _("Type"),
3416 			_("Numerical type of this axis"),
3417 			GOG_AXIS_UNKNOWN, GOG_AXIS_TYPES, GOG_AXIS_UNKNOWN,
3418 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
3419 	g_object_class_install_property (gobject_klass, AXIS_PROP_INVERT,
3420 		g_param_spec_boolean ("invert-axis", _("Invert axis"),
3421 			_("Scale from high to low rather than low to high"),
3422 			FALSE,
3423 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
3424 	g_object_class_install_property (gobject_klass, AXIS_PROP_MAP,
3425 		g_param_spec_string ("map-name", _("MapName"),
3426 			_("The name of the map for scaling"),
3427 			"Linear",
3428 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
3429 	g_object_class_install_property (gobject_klass, AXIS_PROP_ASSIGNED_FORMAT_STR_XL,
3430 		g_param_spec_string ("assigned-format-string-XL",
3431 			_("Assigned XL format"),
3432 			_("The user assigned format to use for non-discrete axis labels (XL format)"),
3433 			"General",
3434 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
3435 	g_object_class_install_property (gobject_klass, AXIS_PROP_CIRCULAR_ROTATION,
3436 		g_param_spec_double ("circular-rotation",
3437 			_("Rotation of circular axis"),
3438 			_("Rotation of circular axis"),
3439 			GOG_AXIS_CIRCULAR_ROTATION_MIN,
3440 			GOG_AXIS_CIRCULAR_ROTATION_MAX,
3441 			0.0,
3442 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
3443 	g_object_class_install_property (gobject_klass, AXIS_PROP_POLAR_UNIT,
3444 		g_param_spec_string ("polar-unit",
3445 			_("Polar axis set unit"),
3446 			_("Polar axis set unit"),
3447 			polar_units[GOG_AXIS_POLAR_UNIT_DEGREES].name,
3448 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
3449 	g_object_class_install_property (gobject_klass, AXIS_PROP_SPAN_START,
3450 		g_param_spec_double ("span-start",
3451 			_("Axis start position"),
3452 			_("Position of the plot area at which the axis effective area starts, expressed as a percentage of the available position. Defaults to 0.0"),
3453 			0., 1., 0.,
3454 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
3455 	g_object_class_install_property (gobject_klass, AXIS_PROP_SPAN_END,
3456 		g_param_spec_double ("span-end",
3457 			_("Axis end position"),
3458 			_("Position of the plot area at which the axis effective area ends, expressed as a percentage of the available position. Defaults to 1.0"),
3459 			0., 1., 1.,
3460 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
3461 	g_object_class_install_property (gobject_klass, AXIS_PROP_COLOR_MAP,
3462 		g_param_spec_string ("color-map-name", _("ColorMapName"),
3463 			_("The name of the color map"),
3464 			"default",
3465 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
3466 	g_object_class_install_property (gobject_klass, AXIS_PROP_METRICS,
3467 		g_param_spec_string ("metrics", _("Metrics"),
3468 			_("The way the axis ticks distance is evaluated"),
3469 			"default",
3470 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
3471 	g_object_class_install_property (gobject_klass, AXIS_PROP_REF_AXIS,
3472 		g_param_spec_string ("axis-ref", _("AxisRef"),
3473 			_("The name of the axis used as reference for ticks distance"),
3474 			"none",
3475 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
3476 	g_object_class_install_property (gobject_klass, AXIS_PROP_METRICS_RATIO,
3477 		g_param_spec_double ("metrics-ratio",
3478 			_("Metrics ratio"),
3479 			_("If an axis is used as reference, gives the ratio of the ticks distance, and if the metrix is absolute, the ticks distance. Defaults to 1.0"),
3480 		    0.01, 100., 1.,
3481 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
3482 	g_object_class_install_property (gobject_klass, AXIS_PROP_REF_AXIS,
3483 		g_param_spec_string ("metrics-unit", _("Metrics Unit"),
3484 			_("The unit symbol for the absolute distance unit between ticks. Might be \"cm\" or \"in\""),
3485 			"cm",
3486 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
3487 	g_object_class_install_property (gobject_klass, AXIS_PROP_DISPLAY_FACTOR,
3488 		g_param_spec_double ("display-factor",
3489 			_("Display factor"),
3490 			_("Real values are the displayed ones multipled by the display factor."),
3491 		    G_MINDOUBLE, G_MAXDOUBLE, 1.,
3492 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
3493 
3494 	gog_object_register_roles (gog_klass, roles, G_N_ELEMENTS (roles));
3495 
3496 	gog_klass->update		= gog_axis_update;
3497 #ifdef GOFFICE_WITH_GTK
3498 	gog_klass->populate_editor	= gog_axis_populate_editor;
3499 #endif
3500 	gog_klass->view_type	= gog_axis_view_get_type ();
3501 	style_klass->init_style = gog_axis_init_style;
3502 }
3503 
3504 static void
gog_axis_init(GogAxis * axis)3505 gog_axis_init (GogAxis *axis)
3506 {
3507 	axis->type	 = GOG_AXIS_UNKNOWN;
3508 	axis->contributors = NULL;
3509 	axis->inverted = FALSE;
3510 
3511 	/* yes we want min = MAX */
3512 	axis->min_val =  DBL_MAX;
3513 	axis->max_val = -DBL_MAX;
3514 	axis->min_contrib = axis->max_contrib = NULL;
3515 	axis->is_discrete = FALSE;
3516 	axis->center_on_ticks = TRUE;
3517 	axis->labels = NULL;
3518 	axis->plot_that_supplied_labels = NULL;
3519 	axis->format = axis->assigned_format = NULL;
3520 
3521 	gog_axis_map_set (axis, NULL);
3522 
3523 	axis->polar_unit = GOG_AXIS_POLAR_UNIT_DEGREES;
3524 	axis->circular_rotation = 0;
3525 
3526 	axis->ticks = NULL;
3527 	axis->tick_nbr = 0;
3528 	axis->span_start = 0.;
3529 	axis->span_end = 1.;
3530 	axis->auto_color_map = TRUE;
3531 	axis->metrics_ratio = 1.;
3532 	axis->unit = GO_UNIT_CENTIMETER;
3533 	axis->display_factor = 1.;
3534 }
3535 
3536 static void
gog_axis_dataset_dims(GogDataset const * set,int * first,int * last)3537 gog_axis_dataset_dims (GogDataset const *set, int *first, int *last)
3538 {
3539 	*first = GOG_AXIS_ELEM_MIN;
3540 	*last  = GOG_AXIS_ELEM_CROSS_POINT;
3541 }
3542 
3543 static GogDatasetElement *
gog_axis_dataset_get_elem(GogDataset const * set,int dim_i)3544 gog_axis_dataset_get_elem (GogDataset const *set, int dim_i)
3545 {
3546 	GogAxis *axis = GOG_AXIS (set);
3547 	if (GOG_AXIS_ELEM_MIN <= dim_i && dim_i < GOG_AXIS_ELEM_CROSS_POINT)
3548 		return &axis->source[dim_i];
3549 	if (dim_i == GOG_AXIS_ELEM_CROSS_POINT) {
3550 		return &(axis->base.cross_location);
3551 	}
3552 	return NULL;
3553 }
3554 
3555 static void
gog_axis_dim_changed(GogDataset * set,int dim_i)3556 gog_axis_dim_changed (GogDataset *set, int dim_i)
3557 {
3558 	gog_axis_update (GOG_OBJECT (set));
3559 	gog_object_emit_changed (GOG_OBJECT (set), TRUE);
3560 }
3561 
3562 static void
gog_axis_dataset_init(GogDatasetClass * iface)3563 gog_axis_dataset_init (GogDatasetClass *iface)
3564 {
3565 	iface->dims	   = gog_axis_dataset_dims;
3566 	iface->get_elem	   = gog_axis_dataset_get_elem;
3567 	iface->dim_changed = gog_axis_dim_changed;
3568 }
3569 
3570 static void
gog_axis_persist_init(GOPersistClass * iface)3571 gog_axis_persist_init (GOPersistClass *iface)
3572 {
3573 	iface->sax_save = gog_axis_sax_save;
3574 	iface->prep_sax = gog_axis_prep_sax;
3575 }
3576 
3577 GSF_CLASS_FULL (GogAxis, gog_axis,
3578 		NULL, NULL, gog_axis_class_init, NULL,
3579 		gog_axis_init, GOG_TYPE_AXIS_BASE, 0,
3580 		GSF_INTERFACE (gog_axis_dataset_init, GOG_TYPE_DATASET) \
3581 		GSF_INTERFACE (gog_axis_persist_init, GO_TYPE_PERSIST))
3582 
3583 
3584 /**
3585  * gog_axis_is_center_on_ticks:
3586  * @axis: #GogAxis
3587  *
3588  * Returns: TRUE if labels are centered on ticks when @axis is discrete
3589  **/
3590 gboolean
gog_axis_is_center_on_ticks(GogAxis const * axis)3591 gog_axis_is_center_on_ticks (GogAxis const *axis)
3592 {
3593 	g_return_val_if_fail (GOG_IS_AXIS (axis), FALSE);
3594 	return axis->center_on_ticks;
3595 }
3596 
3597 /**
3598  * gog_axis_is_discrete:
3599  * @axis: #GogAxis
3600  *
3601  * Returns: TRUE if @axis enumerates a set of discrete items, rather than a
3602  * 	continuous value
3603  **/
3604 gboolean
gog_axis_is_discrete(GogAxis const * axis)3605 gog_axis_is_discrete (GogAxis const *axis)
3606 {
3607 	g_return_val_if_fail (GOG_IS_AXIS (axis), FALSE);
3608 	return axis->is_discrete;
3609 }
3610 
3611 /**
3612  * gog_axis_is_inverted:
3613  * @axis: #GogAxis
3614  *
3615  * Returns: TRUE if @axis is inverted.
3616  **/
3617 gboolean
gog_axis_is_inverted(GogAxis const * axis)3618 gog_axis_is_inverted (GogAxis const *axis)
3619 {
3620 	g_return_val_if_fail (GOG_IS_AXIS (axis), FALSE);
3621 	return axis->inverted;
3622 }
3623 
3624 /**
3625  * gog_axis_get_bounds:
3626  * @axis: #GogAxis
3627  * @minima: non-NULL storage for result
3628  * @maxima: non-NULL storage for result
3629  *
3630  * Returns: %TRUE if the bounds stored in @minima and @maxima are sane
3631  **/
3632 gboolean
gog_axis_get_bounds(GogAxis const * axis,double * minima,double * maxima)3633 gog_axis_get_bounds (GogAxis const *axis, double *minima, double *maxima)
3634 {
3635 	g_return_val_if_fail (GOG_IS_AXIS (axis), FALSE);
3636 	g_return_val_if_fail (minima != NULL, FALSE);
3637 	g_return_val_if_fail (maxima != NULL, FALSE);
3638 
3639 	*minima = gog_axis_get_entry (axis, GOG_AXIS_ELEM_MIN, NULL);
3640 	*maxima = gog_axis_get_entry (axis, GOG_AXIS_ELEM_MAX, NULL);
3641 
3642 	return go_finite (*minima) && go_finite (*maxima) && *minima < *maxima;
3643 }
3644 
3645 /**
3646  * gog_axis_set_bounds:
3647  * @axis: #GogAxis
3648  * @minimum: axis low bound
3649  * @maximum: axis high bound
3650  *
3651  * Sets axis bounds. If minimum or maximum are not finite values, corresponding
3652  * bound remains unchanged.
3653  **/
3654 void
gog_axis_set_bounds(GogAxis * axis,double minimum,double maximum)3655 gog_axis_set_bounds (GogAxis *axis, double minimum, double maximum)
3656 {
3657 	g_return_if_fail (GOG_IS_AXIS (axis));
3658 
3659 	/*
3660 	 * ???????
3661 	 * This sets a new dim instead of using the one embedded in the
3662 	 * axis.  Is that really right?
3663 	 * --MW 20090515
3664 	 */
3665 
3666 	if (go_finite (minimum)) {
3667 		GOData *data = GO_DATA (go_data_scalar_val_new (minimum));
3668 		gog_dataset_set_dim (GOG_DATASET (axis), GOG_AXIS_ELEM_MIN,
3669 				     data, NULL);
3670 	}
3671 	if (go_finite (maximum)) {
3672 		GOData *data = GO_DATA (go_data_scalar_val_new (maximum));
3673 		gog_dataset_set_dim (GOG_DATASET (axis), GOG_AXIS_ELEM_MAX,
3674 				     data, NULL);
3675 	}
3676 }
3677 
3678 void
gog_axis_get_effective_span(GogAxis const * axis,double * start,double * end)3679 gog_axis_get_effective_span (GogAxis const *axis, double *start, double *end)
3680 {
3681 	g_return_if_fail (GOG_IS_AXIS (axis));
3682 
3683 	*start = axis->span_start;
3684 	*end = axis->span_end;
3685 }
3686 
3687 /**
3688  * gog_axis_set_extents:
3689  * @axis: #GogAxis
3690  * @start: axis start bound
3691  * @stop: axis stop bound
3692  *
3693  * Set axis exents. It's a convenience function that sets axis bounds taking
3694  * into account invert flag.
3695  **/
3696 void
gog_axis_set_extents(GogAxis * axis,double start,double stop)3697 gog_axis_set_extents (GogAxis *axis, double start, double stop)
3698 {
3699 	g_return_if_fail (GOG_IS_AXIS (axis));
3700 
3701 	if (axis->inverted)
3702 		gog_axis_set_bounds (axis, stop, start);
3703 	else
3704 		gog_axis_set_bounds (axis, start, stop);
3705 }
3706 
3707 
3708 /**
3709  * gog_axis_get_ticks:
3710  * @axis: #GogAxis
3711  * @ticks: an array of #GogAxisTick
3712  *
3713  * An accessor to @axis->ticks.
3714  *
3715  * return value: number of ticks
3716  **/
3717 unsigned
gog_axis_get_ticks(GogAxis * axis,GogAxisTick ** ticks)3718 gog_axis_get_ticks (GogAxis *axis, GogAxisTick **ticks)
3719 {
3720 	g_return_val_if_fail (GOG_IS_AXIS (axis), 0);
3721 	g_return_val_if_fail (ticks != NULL, 0);
3722 
3723 	*ticks = axis->ticks;
3724 	return axis->tick_nbr;
3725 }
3726 
3727 /**
3728  * gog_axis_get_labels:
3729  * @axis: a #GogAxis
3730  * @plot_that_labeled_axis: a #GogPlot
3731  *
3732  * Returns: (transfer none): the possibly NULL #GOData used as a label for this axis
3733  * along with the plot that it was associated with
3734  **/
3735 GOData *
gog_axis_get_labels(GogAxis const * axis,GogPlot ** plot_that_labeled_axis)3736 gog_axis_get_labels (GogAxis const *axis, GogPlot **plot_that_labeled_axis)
3737 {
3738 	g_return_val_if_fail (GOG_IS_AXIS (axis), NULL);
3739 
3740 	if (axis->is_discrete) {
3741 		if (plot_that_labeled_axis != NULL)
3742 			*plot_that_labeled_axis = axis->plot_that_supplied_labels;
3743 		return GO_DATA (axis->labels);
3744 	}
3745 	if (plot_that_labeled_axis != NULL)
3746 		*plot_that_labeled_axis = NULL;
3747 	return NULL;
3748 }
3749 
3750 /**
3751  * gog_axis_add_contributor:
3752  * @axis: #GogAxis
3753  * @contrib: #GogObject (can we relax this to use an interface ?)
3754  *
3755  * Register @contrib as taking part in the negotiation of @axis's bounds.
3756  **/
3757 void
gog_axis_add_contributor(GogAxis * axis,GogObject * contrib)3758 gog_axis_add_contributor (GogAxis *axis, GogObject *contrib)
3759 {
3760 	g_return_if_fail (GOG_IS_AXIS (axis));
3761 	g_return_if_fail (g_slist_find (axis->contributors, contrib) == NULL);
3762 
3763 	axis->contributors = g_slist_prepend (axis->contributors, contrib);
3764 
3765 	gog_object_request_update (GOG_OBJECT (axis));
3766 }
3767 
3768 /**
3769  * gog_axis_del_contributor:
3770  * @axis: #GogAxis
3771  * @contrib: #GogObject (can we relax this to use an interface ?)
3772  *
3773  * @contrib no longer takes part in the negotiation of @axis's bounds.
3774  **/
3775 void
gog_axis_del_contributor(GogAxis * axis,GogObject * contrib)3776 gog_axis_del_contributor (GogAxis *axis, GogObject *contrib)
3777 {
3778 	gboolean update = FALSE;
3779 
3780 	g_return_if_fail (GOG_IS_AXIS (axis));
3781 	g_return_if_fail (g_slist_find (axis->contributors, contrib) != NULL);
3782 
3783 	if (axis->min_contrib == contrib) {
3784 		axis->min_contrib = NULL;
3785 		update = TRUE;
3786 	}
3787 	if (axis->max_contrib == contrib) {
3788 		axis->max_contrib = NULL;
3789 		update = TRUE;
3790 	}
3791 	axis->contributors = g_slist_remove (axis->contributors, contrib);
3792 
3793 	if (update)
3794 		gog_object_request_update (GOG_OBJECT (axis));
3795 }
3796 
3797 void
gog_axis_clear_contributors(GogAxis * axis)3798 gog_axis_clear_contributors (GogAxis *axis)
3799 {
3800 	GSList *ptr, *list;
3801 	GogAxisSet filter;
3802 
3803 	g_return_if_fail (GOG_IS_AXIS (axis));
3804 
3805 	filter = 1 << axis->type;
3806 	list = g_slist_copy (axis->contributors);
3807 	for (ptr = list; ptr != NULL ; ptr = ptr->next)
3808 		gog_plot_axis_clear (GOG_PLOT (ptr->data), filter);
3809 	g_slist_free (list);
3810 }
3811 
3812 /**
3813  * gog_axis_contributors:
3814  * @axis: #GogAxis
3815  *
3816  * Returns: (element-type GogObject) (transfer none): the list of the axis
3817  * contributors
3818  **/
3819 GSList const *
gog_axis_contributors(GogAxis * axis)3820 gog_axis_contributors (GogAxis *axis)
3821 {
3822 	g_return_val_if_fail (GOG_IS_AXIS (axis), NULL);
3823 
3824 	return axis->contributors;
3825 }
3826 
3827 /**
3828  * gog_axis_bound_changed:
3829  * @axis: #GogAxis
3830  * @contrib: #GogObject
3831 **/
3832 void
gog_axis_bound_changed(GogAxis * axis,GogObject * contrib)3833 gog_axis_bound_changed (GogAxis *axis, GogObject *contrib)
3834 {
3835 	g_return_if_fail (GOG_IS_AXIS (axis));
3836 
3837 	gog_object_request_update (GOG_OBJECT (axis));
3838 }
3839 
3840 /*
3841  * gog_axis_data_get_bounds:
3842  * @axis: (allow-none): the axis for which the data applies
3843  * @data: the data to get bounds for
3844  * @minimum: smallest valid data value.
3845  * @maximum: largest valid data value.
3846  */
3847 void
gog_axis_data_get_bounds(GogAxis * axis,GOData * data,double * minimum,double * maximum)3848 gog_axis_data_get_bounds (GogAxis *axis, GOData *data,
3849 			  double *minimum, double *maximum)
3850 {
3851 	gboolean (*map_finite) (double value) =
3852 		axis ? axis->actual_map_desc->map_finite : go_finite;
3853 
3854 	if (map_finite != go_finite) {
3855 		size_t i, n = go_data_get_n_values (data);
3856 		const double *values = go_data_get_values (data);
3857 		*minimum = go_pinf;
3858 		*maximum = go_ninf;
3859 		for (i = 0; i < n; i++) {
3860 			double x = values[i];
3861 			if (!map_finite (x))
3862 				continue;
3863 			*minimum = MIN (*minimum, x);
3864 			*maximum = MAX (*maximum, x);
3865 		}
3866 	} else
3867 		go_data_get_bounds (data, minimum, maximum);
3868 }
3869 
3870 /*
3871  * gog_axis_is_zero_important:
3872  * @axis: the axis to check
3873  *
3874  * Returns %TRUE if the axis is of a type in which zero is important and
3875  * preferentially should be included in the range.
3876  *
3877  * This generally means a linear axis, i.e., not a log axis and not a
3878  * date formatted axis.
3879  */
3880 gboolean
gog_axis_is_zero_important(GogAxis * axis)3881 gog_axis_is_zero_important (GogAxis *axis)
3882 {
3883 	GogAxisMapDesc const *desc = axis->actual_map_desc;
3884 
3885 	return !axis->is_discrete &&
3886 		desc->map_finite (0.0) &&
3887 		desc->auto_bound != map_date_auto_bound;
3888 }
3889 
3890 /**
3891  * gog_axis_get_grid_line:
3892  * @axis: #GogAxis
3893  * @major: whether to retrieve major or minor grid line.
3894  *
3895  * Returns: (transfer none): a pointer to GridLine object associated to given axis, NULL
3896  * if it doesn't exists.
3897  **/
3898 GogGridLine *
gog_axis_get_grid_line(GogAxis * axis,gboolean major)3899 gog_axis_get_grid_line (GogAxis *axis, gboolean major)
3900 {
3901 	GogGridLine *grid_line;
3902 	GSList *children;
3903 
3904 	children = gog_object_get_children (GOG_OBJECT (axis),
3905 		gog_object_find_role_by_name (GOG_OBJECT (axis),
3906 			major ? "MajorGrid" : "MinorGrid"));
3907 	if (children != NULL) {
3908 		grid_line = GOG_GRID_LINE (children->data);
3909 		g_slist_free (children);
3910 		return grid_line;
3911 	}
3912 	return NULL;
3913 }
3914 
3915 /**
3916  * gog_axis_set_polar_unit:
3917  * @axis: a #GogAxis
3918  * @unit: #GogAxisPolarUnit
3919  *
3920  * Sets unit of a circular axis. See #GogAxisPolarUnit for valid
3921  * values.
3922  **/
3923 
3924 void
gog_axis_set_polar_unit(GogAxis * axis,GogAxisPolarUnit unit)3925 gog_axis_set_polar_unit (GogAxis *axis, GogAxisPolarUnit unit)
3926 {
3927 	g_return_if_fail (GOG_IS_AXIS (axis));
3928 
3929 	axis->polar_unit = CLAMP (unit, 0, GOG_AXIS_POLAR_UNIT_MAX - 1);
3930 }
3931 
3932 /**
3933  * gog_axis_get_polar_unit:
3934  * @axis: a #GogAxis
3935  *
3936  * Returns: unit of @axis if it's a circular axis of a polar
3937  * 	axis set, -1 otherwise.
3938  **/
3939 
3940 GogAxisPolarUnit
gog_axis_get_polar_unit(GogAxis * axis)3941 gog_axis_get_polar_unit (GogAxis *axis)
3942 {
3943 	g_return_val_if_fail (GOG_IS_AXIS (axis), 0);
3944 
3945 	return axis->polar_unit;
3946 }
3947 
3948 /**
3949  * gog_axis_get_circular_perimeter:
3950  * @axis: a #GogAxis
3951  *
3952  * Returns: perimeter of a circular #GogAxis of a polar axis set.
3953  * 	radians: 2*pi
3954  * 	degrees: 360.0
3955  * 	grads  : 400.0
3956  **/
3957 double
gog_axis_get_polar_perimeter(GogAxis * axis)3958 gog_axis_get_polar_perimeter (GogAxis *axis)
3959 {
3960 	g_return_val_if_fail (GOG_IS_AXIS (axis), 0.0);
3961 
3962 	return polar_units[axis->polar_unit].perimeter;
3963 }
3964 
3965 /**
3966  * gog_axis_get_circular_rotation:
3967  * @axis: a #GogAxis
3968  *
3969  * Returns: rotation of a circular #GogAxis.
3970  **/
3971 double
gog_axis_get_circular_rotation(GogAxis * axis)3972 gog_axis_get_circular_rotation (GogAxis *axis)
3973 {
3974 	g_return_val_if_fail (GOG_IS_AXIS (axis), 0.0);
3975 
3976 	return axis->circular_rotation;
3977 }
3978 
3979 /**
3980  * gog_axis_get_color_map:
3981  * @axis: a #GogAxis
3982  *
3983  * Retrieves the #GogAxisColorMap associated to the axis or %NULL.
3984  * Returns: (transfer none): the color map used by the axis if any.
3985  **/
3986 GogAxisColorMap const *
gog_axis_get_color_map(GogAxis * axis)3987 gog_axis_get_color_map (GogAxis *axis)
3988 {
3989 	g_return_val_if_fail (GOG_IS_AXIS (axis), NULL);
3990 
3991 	return axis->color_map;
3992 }
3993 
3994 
3995 /**
3996  * gog_axis_get_color_scale:
3997  * @axis: a #GogAxis
3998  *
3999  * Retrieves the #GogColorScale associated to the axis or %NULL.
4000  * Returns: (transfer none): the color scale used to display the axis colors
4001  * for color and pseudo-3d axes.
4002  **/
4003 GogColorScale *
gog_axis_get_color_scale(GogAxis * axis)4004 gog_axis_get_color_scale (GogAxis *axis)
4005 {
4006 	g_return_val_if_fail (GOG_IS_AXIS (axis), NULL);
4007 
4008 	return axis->color_scale;
4009 }
4010 
4011 void
_gog_axis_set_color_scale(GogAxis * axis,GogColorScale * scale)4012 _gog_axis_set_color_scale (GogAxis *axis, GogColorScale *scale)
4013 {
4014 	g_return_if_fail (GOG_IS_AXIS (axis) &&
4015 	                  (axis->type == GOG_AXIS_COLOR || axis->type == GOG_AXIS_PSEUDO_3D) &&
4016 	                  (axis->color_scale == NULL || scale == NULL));
4017 	axis->color_scale = scale;
4018 }
4019 
4020 GogAxisMetrics
gog_axis_get_metrics(GogAxis const * axis)4021 gog_axis_get_metrics (GogAxis const *axis)
4022 {
4023 	g_return_val_if_fail (GOG_IS_AXIS (axis), GOG_AXIS_METRICS_INVALID);
4024 	return axis->metrics;
4025 }
4026 
4027 /**
4028  * gog_axis_get_ref_axis:
4029  * @axis: a #GogAxis
4030  *
4031  * Since: 0.10.17
4032  * Returns: (transfer none): the axis used to evaluate the length of @axis or
4033  * NULL.
4034  **/
4035 GogAxis *
gog_axis_get_ref_axis(GogAxis const * axis)4036 gog_axis_get_ref_axis (GogAxis const *axis)
4037 {
4038 	g_return_val_if_fail (GOG_IS_AXIS (axis) && axis->metrics > GOG_AXIS_METRICS_ABSOLUTE, NULL);
4039 	return axis->ref_axis;
4040 }
4041 
4042 double
gog_axis_get_major_ticks_distance(GogAxis const * axis)4043 gog_axis_get_major_ticks_distance (GogAxis const *axis)
4044 {
4045 	g_return_val_if_fail (GOG_IS_AXIS (axis), go_nan);
4046 	return gog_axis_get_entry (axis, GOG_AXIS_ELEM_MAJOR_TICK, NULL);
4047 }
4048 
4049 /****************************************************************************/
4050 
4051 typedef struct {
4052 	GogAxisBaseView base;
4053 	double padding_low, padding_high;
4054 } GogAxisView;
4055 typedef GogAxisBaseViewClass	GogAxisViewClass;
4056 
4057 #define GOG_TYPE_AXIS_VIEW	(gog_axis_view_get_type ())
4058 #define GOG_AXIS_VIEW(o)	(G_TYPE_CHECK_INSTANCE_CAST ((o), GOG_TYPE_AXIS_VIEW, GogAxisView))
4059 #define GOG_IS_AXIS_VIEW(o)	(G_TYPE_CHECK_INSTANCE_TYPE ((o), GOG_TYPE_AXIS_VIEW))
4060 
4061 static GogViewClass *aview_parent_klass;
4062 
4063 static void
gog_axis_view_padding_request_3d(GogView * view,GogView * child,GogViewAllocation const * plot_area,GogViewPadding * label_padding)4064 gog_axis_view_padding_request_3d (GogView *view, GogView *child,
4065                                   GogViewAllocation const *plot_area,
4066 				  GogViewPadding *label_padding)
4067 {
4068 	GogViewAllocation child_bbox;
4069 	GogViewAllocation label_pos;
4070 	GogViewAllocation tmp = *plot_area;
4071 	GogViewRequisition req, available;
4072 	GOStyle *style = go_styled_object_get_style (GO_STYLED_OBJECT (child->model));
4073 	double angle;
4074 
4075 	gog_axis_base_view_label_position_request (view, plot_area, &label_pos);
4076 	if (style->text_layout.auto_angle) {
4077 		angle = atan2 (label_pos.w, label_pos.h) * 180. / M_PI;
4078 		if (angle < 0.)
4079 			angle += 180.;
4080 		style->text_layout.angle = (angle > 45. && angle < 135.)? 90. : 0.;
4081 	}
4082 
4083 	available.w = plot_area->w;
4084 	available.h = plot_area->h;
4085 
4086 	gog_view_size_request (child, &available, &req);
4087 
4088 	if (req.w == 0 || req.h == 0)
4089 		return;
4090 
4091 	child_bbox.x = label_pos.x + label_pos.w;
4092 	if (label_pos.w < 0)
4093 		child_bbox.x -= req.w;
4094 	child_bbox.y = label_pos.y + label_pos.h;
4095 	if (label_pos.h < 0)
4096 		child_bbox.y -= req.h;
4097 
4098 	child_bbox.w = req.w;
4099 	child_bbox.h = req.h;
4100 
4101 	tmp.x -= label_padding->wl;
4102 	tmp.w += label_padding->wl + label_padding->wr;
4103 	tmp.y -= label_padding->hb;
4104 	tmp.h += label_padding->hb + label_padding->ht;
4105 
4106 	label_padding->wl += MAX (0, tmp.x - child_bbox.x);
4107 	label_padding->ht += MAX (0, tmp.y - child_bbox.y);
4108 	label_padding->wr += MAX (0, child_bbox.x + child_bbox.w
4109 				  - tmp.x - tmp.w);
4110 	label_padding->hb += MAX (0, child_bbox.y + child_bbox.h
4111 				  - tmp.y - tmp.h);
4112 }
4113 
4114 static void
gog_axis_view_padding_request(GogView * view,GogViewAllocation const * bbox,GogViewPadding * padding)4115 gog_axis_view_padding_request (GogView *view,
4116 			       GogViewAllocation const *bbox,
4117 			       GogViewPadding *padding)
4118 {
4119 	GogView *child;
4120 	GogAxis *axis = GOG_AXIS (view->model);
4121 	GogAxisView *axis_view = GOG_AXIS_VIEW (view);
4122 	GogAxisType type = gog_axis_get_atype (axis);
4123 	GogObjectPosition pos;
4124 	GogAxisPosition axis_pos, base_axis_pos;
4125 	GogViewAllocation tmp = *bbox;
4126 	GogViewRequisition req, available;
4127 	GogViewPadding label_padding, child_padding;
4128 	GogObject *parent = gog_object_get_parent (view->model);
4129 	gboolean is_3d = GOG_IS_CHART (parent) && gog_chart_is_3d (GOG_CHART (parent));
4130 	GSList *ptr, *saved_ptr = NULL;
4131 	double const pad_h = gog_renderer_pt2r_y (view->renderer, PAD_HACK);
4132 	double const pad_w = gog_renderer_pt2r_x (view->renderer, PAD_HACK);
4133 
4134 	label_padding.wr = label_padding.wl = label_padding.ht = label_padding.hb = 0;
4135 
4136 	base_axis_pos = axis_pos = gog_axis_base_get_clamped_position (GOG_AXIS_BASE (axis));
4137 
4138 	ptr = view->children;
4139 	while (ptr) {
4140 		child = ptr->data;
4141 		pos = child->model->position;
4142 		if (GOG_IS_LABEL (child->model) && !(pos & GOG_POSITION_MANUAL)) {
4143 			if (is_3d) {
4144 				gog_axis_view_padding_request_3d (view, child,
4145 					bbox, &label_padding);
4146 			} else {
4147 				available.w = bbox->w;
4148 				available.h = bbox->h;
4149 				gog_view_size_request (child, &available, &req);
4150 				if (type == GOG_AXIS_X)
4151 					switch (axis_pos) {
4152 						case GOG_AXIS_AT_HIGH:
4153 							label_padding.ht += req.h + pad_h;
4154 							break;
4155 						case GOG_AXIS_AT_LOW:
4156 						default:
4157 							label_padding.hb += req.h + pad_h;
4158 							break;
4159 					}
4160 				else
4161 					switch (axis_pos) {
4162 						case GOG_AXIS_AT_HIGH:
4163 							label_padding.wr += req.w + pad_w;
4164 							break;
4165 						case GOG_AXIS_AT_LOW:
4166 						default:
4167 							label_padding.wl += req.w + pad_w;
4168 							break;
4169 					}
4170 			}
4171 		}
4172 		if (GOG_IS_AXIS_LINE (child->model) && saved_ptr == NULL && child->children != NULL) {
4173 			axis_pos = gog_axis_base_get_clamped_position (GOG_AXIS_BASE (child->model));
4174 			saved_ptr = ptr;
4175 			ptr = child->children;
4176 		} else if (saved_ptr != NULL && ptr->next == NULL) {
4177 			axis_pos = base_axis_pos;
4178 			ptr = saved_ptr->next;
4179 			saved_ptr = NULL;
4180 		} else
4181 			ptr = ptr->next;
4182 	}
4183 
4184 	if (is_3d) {
4185 		/* For 3d chart we have to calculate how much more padding
4186 		 * is needed for the axis itself */
4187 		tmp.x -= label_padding.wl;
4188 		tmp.w += label_padding.wl + label_padding.wr;
4189 		tmp.y -= label_padding.hb;
4190 		tmp.h += label_padding.hb + label_padding.ht;
4191 	} else {
4192 		tmp.x += label_padding.wl;
4193 		tmp.w -= label_padding.wl + label_padding.wr;
4194 		tmp.y += label_padding.hb;
4195 		tmp.h -= label_padding.hb + label_padding.ht;
4196 	}
4197 
4198 	(aview_parent_klass->padding_request) (view, &tmp, padding);
4199 
4200 	for (ptr = view->children; ptr != NULL ; ptr = ptr->next) {
4201 		child = ptr->data;
4202 		if (GOG_POSITION_IS_PADDING (child->model->position)) {
4203 			gog_view_padding_request (child, &tmp, &child_padding);
4204 			padding->wr = MAX (padding->wr, child_padding.wr);
4205 			padding->wl = MAX (padding->wl, child_padding.wl);
4206 			padding->hb = MAX (padding->hb, child_padding.hb);
4207 			padding->ht = MAX (padding->ht, child_padding.ht);
4208 		}
4209 	}
4210 
4211 	padding->wr += label_padding.wr;
4212 	padding->wl += label_padding.wl;
4213 	padding->ht += label_padding.ht;
4214 	padding->hb += label_padding.hb;
4215 	if (!is_3d) {
4216 		if (type == GOG_AXIS_X) {
4217 			axis_view->padding_high = padding->ht;
4218 			axis_view->padding_low = padding->hb;
4219 		} else {
4220 			axis_view->padding_high = padding->wr;
4221 			axis_view->padding_low = padding->wl;
4222 		}
4223 	}
4224 }
4225 
4226 static void
gog_axis_view_size_allocate_3d(GogView * view,GogView * child,GogViewAllocation const * plot_area)4227 gog_axis_view_size_allocate_3d (GogView *view, GogView *child,
4228                                 GogViewAllocation const *plot_area)
4229 {
4230 	GogViewAllocation child_bbox;
4231 	GogViewAllocation label_pos;
4232 	GogViewRequisition req, available;
4233 
4234 	gog_view_size_request (child, &available, &req);
4235 	gog_axis_base_view_label_position_request (view, plot_area, &label_pos);
4236 
4237 	child_bbox.x = label_pos.x + label_pos.w;
4238 	if (label_pos.w < 0)
4239 		child_bbox.x -= req.w;
4240 	child_bbox.y = label_pos.y + label_pos.h;
4241 	if (label_pos.h < 0)
4242 		child_bbox.y -= req.h;
4243 
4244 	child_bbox.w = req.w;
4245 	child_bbox.h = req.h;
4246 
4247 	gog_view_size_allocate (child, &child_bbox);
4248 }
4249 
4250 static void
gog_axis_view_size_allocate(GogView * view,GogViewAllocation const * bbox)4251 gog_axis_view_size_allocate (GogView *view, GogViewAllocation const *bbox)
4252 {
4253 	GSList *ptr, *saved_ptr = NULL;
4254 	GogView *child;
4255 	GogAxis *axis = GOG_AXIS (view->model);
4256 	GogAxisView *axis_view = GOG_AXIS_VIEW (view);
4257 	GogAxisType type = gog_axis_get_atype (axis);
4258 	GogViewAllocation tmp = *bbox;
4259 	GogViewAllocation const *plot_area = gog_chart_view_get_plot_area (view->parent);
4260 	GogViewAllocation child_bbox;
4261 	GogViewRequisition req, available;
4262 	GogObjectPosition pos;
4263 	GogAxisPosition axis_pos, base_axis_pos;
4264 	GogChart *chart = GOG_CHART (gog_object_get_parent (view->model));
4265 	double const pad_h = gog_renderer_pt2r_y (view->renderer, PAD_HACK);
4266 	double const pad_w = gog_renderer_pt2r_x (view->renderer, PAD_HACK);
4267 	double start, end;
4268 
4269 	if (!gog_chart_is_3d (chart)) {
4270 		double d;
4271 		if (type == GOG_AXIS_X) {
4272 			d = plot_area->y - tmp.y - axis_view->padding_high;
4273 			tmp.y += d;
4274 			tmp.h = plot_area->h + axis_view->padding_low + axis_view->padding_high;
4275 		} else {
4276 			d = plot_area->x - tmp.x - axis_view->padding_low;
4277 			tmp.x += d;
4278 			tmp.w = plot_area->w + axis_view->padding_low + axis_view->padding_high;
4279 		}
4280 	}
4281 	available.w = tmp.w;
4282 	available.h = tmp.h;
4283 
4284 	base_axis_pos = axis_pos = gog_axis_base_get_clamped_position (GOG_AXIS_BASE (axis));
4285 
4286 	ptr = view->children;
4287 	while (ptr != NULL) {
4288 		child = ptr->data;
4289 		pos = child->model->position;
4290 		if (GOG_IS_LABEL (child->model) && (pos & GOG_POSITION_MANUAL)) {
4291 			gog_view_size_request (child, &available, &req);
4292 			child_bbox = gog_object_get_manual_allocation (gog_view_get_model (child),
4293 								       bbox, &req);
4294 			gog_view_size_allocate (child, &child_bbox);
4295 		} else {
4296 			if (GOG_POSITION_IS_SPECIAL (pos)) {
4297 				if (GOG_IS_LABEL (child->model)) {
4298 					if (gog_chart_is_3d (chart)) {
4299 						gog_axis_view_size_allocate_3d (view,
4300 							child, plot_area);
4301 						ptr = ptr->next;
4302 						continue;
4303 					}
4304 					gog_view_size_request (child, &available, &req);
4305 					gog_axis_get_effective_span (axis, &start, &end);
4306 					if (type == GOG_AXIS_X) {
4307 						child_bbox.x = plot_area->x +
4308 							(plot_area->w * (start + end) - req.w) / 2.0;
4309 						child_bbox.w = req.w;
4310 						child_bbox.h = req.h;
4311 						switch (axis_pos) {
4312 							case GOG_AXIS_AT_HIGH:
4313 								child_bbox.y = tmp.y;
4314 								tmp.y += req.h + pad_h;
4315 								break;
4316 							case GOG_AXIS_AT_LOW:
4317 							default:
4318 								child_bbox.y = tmp.y + tmp.h - req.h;
4319 								break;
4320 						}
4321 						tmp.h -= req.h + pad_h;
4322 					} else {
4323 						child_bbox.y = plot_area->y + plot_area->h -
4324 							(plot_area->h * (start + end) + req.h) / 2.0;
4325 						child_bbox.h = req.h;
4326 						child_bbox.w = req.w;
4327 						switch (axis_pos) {
4328 							case GOG_AXIS_AT_HIGH:
4329 								child_bbox.x = tmp.x + tmp.w - req.w;
4330 								break;
4331 							case GOG_AXIS_AT_LOW:
4332 							default:
4333 								child_bbox.x = tmp.x;
4334 								tmp.x += req.w + pad_w;
4335 								break;
4336 						}
4337 						tmp.w -= req.w + pad_w;
4338 					}
4339 					gog_view_size_allocate (child, &child_bbox);
4340 				} else {
4341 					gog_view_size_allocate (child, plot_area);
4342 				}
4343 			} else if (GOG_IS_AXIS_LINE (child->model))
4344 				child->allocation_valid = TRUE; /* KLUDGE, but needed for axis lines
4345 					otherwise adding a title does not invalidate the chart allocation,
4346 					see #760675, comments 5+ */
4347 
4348 		}
4349 		if (GOG_IS_AXIS_LINE (child->model) && saved_ptr == NULL && child->children != NULL) {
4350 			axis_pos = gog_axis_base_get_clamped_position (GOG_AXIS_BASE (child->model));
4351 			saved_ptr = ptr;
4352 			ptr = child->children;
4353 		} else if (saved_ptr != NULL && ptr->next == NULL) {
4354 			axis_pos = base_axis_pos;
4355 			ptr = saved_ptr->next;
4356 			saved_ptr = NULL;
4357 		} else
4358 			ptr = ptr->next;
4359 	}
4360 }
4361 
4362 static void
gog_axis_view_render(GogView * view,GogViewAllocation const * bbox)4363 gog_axis_view_render (GogView *view, GogViewAllocation const *bbox)
4364 {
4365 	GSList *ptr, *saved_ptr = NULL;
4366 	GogView *child;
4367 
4368 	(aview_parent_klass->render) (view, bbox);
4369 
4370 	/* Render every child except grid lines. Those are rendered
4371 	 * before in gog_chart_view since we don't want to render them
4372 	 * over axis. */
4373 	ptr = view->children;
4374 	while (ptr != NULL) {
4375 		child = ptr->data;
4376 		if (!GOG_IS_GRID_LINE (child->model))
4377 			gog_view_render	(ptr->data, bbox);
4378 		if (GOG_IS_AXIS_LINE (child->model) && saved_ptr == NULL && child->children != NULL) {
4379 			saved_ptr = ptr;
4380 			ptr = child->children;
4381 		} else if (saved_ptr != NULL && ptr->next == NULL) {
4382 			ptr = saved_ptr->next;
4383 			saved_ptr = NULL;
4384 		} else
4385 			ptr = ptr->next;
4386 	}
4387 }
4388 
4389 static void
gog_axis_view_class_init(GogAxisViewClass * gview_klass)4390 gog_axis_view_class_init (GogAxisViewClass *gview_klass)
4391 {
4392 	GogViewClass *view_klass    = (GogViewClass *) gview_klass;
4393 
4394 	aview_parent_klass = g_type_class_peek_parent (gview_klass);
4395 	view_klass->size_allocate = gog_axis_view_size_allocate;
4396 	view_klass->padding_request = gog_axis_view_padding_request;
4397 	view_klass->render	    = gog_axis_view_render;
4398 }
4399 
4400 static GSF_CLASS (GogAxisView, gog_axis_view,
4401 		  gog_axis_view_class_init, NULL,
4402 		  GOG_TYPE_AXIS_BASE_VIEW)
4403