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