1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 2004, 2009, 2010, 2011, 2014, 2015 Free Software Foundation, Inc.
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
19 #include "output/cairo-chart.h"
20 #include "math/decimal.h"
21 #include "math/chart-geometry.h"
24 #include <cairo/cairo.h>
25 #include <pango/pango.h>
26 #include <pango/pangocairo.h>
34 #include "libpspp/assertion.h"
35 #include "math/chart-geometry.h"
36 #include "output/cairo.h"
37 #include "output/chart-item.h"
39 #include "gl/xalloc.h"
40 #include "gl/xvasprintf.h"
43 #define _(msgid) gettext (msgid)
46 xrchart_geometry_init (cairo_t
*cr
, struct xrchart_geometry
*geom
,
47 double width
, double length
)
49 /* Set default chart geometry. */
50 geom
->axis
[SCALE_ORDINATE
].data_max
= 0.900 * length
;
51 geom
->axis
[SCALE_ORDINATE
].data_min
= 0.120 * length
;
53 geom
->axis
[SCALE_ABSCISSA
].data_min
= 0.150 * width
;
54 geom
->axis
[SCALE_ABSCISSA
].data_max
= 0.800 * width
;
55 geom
->abscissa_bottom
= 0.070 * length
;
56 geom
->ordinate_left
= 0.050 * width
;
57 geom
->title_bottom
= 0.920 * length
;
58 geom
->legend_left
= 0.810 * width
;
59 geom
->legend_right
= width
;
60 geom
->font_size
= 15.0;
61 geom
->in_path
= false;
65 geom
->fill_colour
= data_colour
[0];
67 cairo_set_line_width (cr
, 1.0);
69 cairo_rectangle (cr
, geom
->axis
[SCALE_ABSCISSA
].data_min
, geom
->axis
[SCALE_ORDINATE
].data_min
,
70 geom
->axis
[SCALE_ABSCISSA
].data_max
- geom
->axis
[SCALE_ABSCISSA
].data_min
,
71 geom
->axis
[SCALE_ORDINATE
].data_max
- geom
->axis
[SCALE_ORDINATE
].data_min
);
76 xrchart_geometry_free (cairo_t
*cr UNUSED
, struct xrchart_geometry
*geom
)
80 for (i
= 0 ; i
< geom
->n_datasets
; ++i
)
81 free (geom
->dataset
[i
]);
85 #if ! PANGO_VERSION_CHECK (1, 22, 0)
86 int pango_layout_get_baseline (PangoLayout
*layout
);
88 /* Shamelessly copied from the pango source */
90 pango_layout_get_baseline (PangoLayout
*layout
)
94 /* XXX this is so inefficient */
95 PangoLayoutIter
*iter
= pango_layout_get_iter (layout
);
96 baseline
= pango_layout_iter_get_baseline (iter
);
97 pango_layout_iter_free (iter
);
104 These colours come from:
105 http://tango.freedesktop.org/static/cvs/tango-art-tools/palettes/Tango-Palette.gpl */
106 const struct xrchart_colour data_colour
[XRCHART_N_COLOURS
] =
108 {252, 233, 79}, /* Butter 1 */
109 {138, 226, 52}, /* Chameleon 1 */
110 {252, 175, 62}, /* Orange 1 */
111 {114, 159, 207}, /* Sky Blue 1 */
112 {173, 127, 168}, /* Plum 1 */
113 {233, 185, 110}, /* Chocolate 1 */
114 {239, 41, 41}, /* Scarlet Red 1 */
115 {238, 238, 236}, /* Aluminium 1 */
117 {237, 212, 0}, /* Butter 2 */
118 {115, 210, 22}, /* Chameleon 2 */
119 {245, 121, 0}, /* Orange 2 */
120 {52, 101, 164}, /* Sky Blue 2 */
121 {117, 80, 123}, /* Plum 2 */
122 {193, 125, 17}, /* Chocolate 2 */
123 {204, 0, 0}, /* Scarlet Red 2 */
125 {136, 138, 133}, /* Aluminium 4 */
127 {196, 160, 0}, /* Butter 3 */
128 {78, 154, 6}, /* Chameleon 3 */
129 {206, 92, 0}, /* Orange 3 */
130 {32, 74, 135}, /* Sky Blue 3 */
131 {92, 53, 102}, /* Plum 3 */
132 {143, 89, 2}, /* Chocolate 3 */
133 {164, 0, 0}, /* Scarlet Red 3 */
134 {85, 87, 83}, /* Aluminium 5 */
136 {211, 215, 207}, /* Aluminium 2 */
137 {186, 189, 182}, /* Aluminium 3 */
138 {46, 52, 54}, /* Aluminium 6 */
142 xrchart_draw_marker (cairo_t
*cr
, double x
, double y
,
143 enum xrmarker_type marker
, double size
)
146 cairo_translate (cr
, x
, y
);
147 cairo_scale (cr
, size
/ 2.0, size
/ 2.0);
148 cairo_set_line_width (cr
, cairo_get_line_width (cr
) / (size
/ 2.0));
151 case XRMARKER_CIRCLE
:
152 cairo_arc (cr
, 0, 0, 1.0, 0, 2 * M_PI
);
156 case XRMARKER_ASTERISK
:
157 cairo_move_to (cr
, 0, -1.0); /* | */
158 cairo_line_to (cr
, 0, 1.0);
159 cairo_move_to (cr
, -M_SQRT1_2
, -M_SQRT1_2
); /* / */
160 cairo_line_to (cr
, M_SQRT1_2
, M_SQRT1_2
);
161 cairo_move_to (cr
, -M_SQRT1_2
, M_SQRT1_2
); /* \ */
162 cairo_line_to (cr
, M_SQRT1_2
, -M_SQRT1_2
);
166 case XRMARKER_SQUARE
:
167 cairo_rectangle (cr
, -1.0, -1.0, 2.0, 2.0);
175 xrchart_label_rotate (cairo_t
*cr
, int horz_justify
, int vert_justify
,
176 double font_size
, const char *string
, double angle
)
178 PangoFontDescription
*desc
;
182 desc
= pango_font_description_from_string ("Sans");
188 pango_font_description_set_absolute_size (desc
, font_size
* PANGO_SCALE
);
191 cairo_rotate (cr
, angle
);
192 cairo_get_current_point (cr
, &x
, &y
);
193 cairo_translate (cr
, x
, y
);
194 cairo_move_to (cr
, 0, 0);
195 cairo_scale (cr
, 1.0, -1.0);
197 layout
= pango_cairo_create_layout (cr
);
198 pango_layout_set_font_description (layout
, desc
);
199 pango_layout_set_text (layout
, string
, -1);
200 if (horz_justify
!= 'l')
205 pango_layout_get_size (layout
, &width_pango
, NULL
);
206 width
= (double) width_pango
/ PANGO_SCALE
;
207 if (horz_justify
== 'r')
208 cairo_rel_move_to (cr
, -width
, 0);
210 cairo_rel_move_to (cr
, -width
/ 2.0, 0);
212 if (vert_justify
== 'x')
214 int baseline_pango
= pango_layout_get_baseline (layout
);
215 double baseline
= (double) baseline_pango
/ PANGO_SCALE
;
216 cairo_rel_move_to (cr
, 0, -baseline
);
218 else if (vert_justify
!= 't')
223 pango_layout_get_size (layout
, NULL
, &height_pango
);
224 height
= (double) height_pango
/ PANGO_SCALE
;
225 if (vert_justify
== 'b')
226 cairo_rel_move_to (cr
, 0, -height
);
227 else if (vert_justify
== 'c')
228 cairo_rel_move_to (cr
, 0, -height
/ 2.0);
230 pango_cairo_show_layout (cr
, layout
);
231 g_object_unref (layout
);
237 pango_font_description_free (desc
);
241 xrchart_label (cairo_t
*cr
, int horz_justify
, int vert_justify
,
242 double font_size
, const char *string
)
244 xrchart_label_rotate (cr
, horz_justify
, vert_justify
, font_size
, string
, 0);
248 /* Draw a tick mark at position
249 If label is non null, then print it at the tick mark
252 draw_tick_internal (cairo_t
*cr
, const struct xrchart_geometry
*geom
,
253 enum tick_orientation orientation
,
259 draw_tick (cairo_t
*cr
, const struct xrchart_geometry
*geom
,
260 enum tick_orientation orientation
,
263 const char *label
, ...)
267 va_start (ap
, label
);
268 s
= xvasprintf (label
, ap
);
270 if (fabs (position
) < DBL_EPSILON
)
273 draw_tick_internal (cr
, geom
, orientation
, rotated
, position
, s
);
280 draw_tick_internal (cairo_t
*cr
, const struct xrchart_geometry
*geom
,
281 enum tick_orientation orientation
,
286 const int tickSize
= 10;
289 cairo_move_to (cr
, geom
->axis
[SCALE_ABSCISSA
].data_min
, geom
->axis
[SCALE_ORDINATE
].data_min
);
291 if (orientation
== SCALE_ABSCISSA
)
293 cairo_rel_move_to (cr
, position
, 0);
294 cairo_rel_line_to (cr
, 0, -tickSize
);
296 else if (orientation
== SCALE_ORDINATE
)
298 cairo_rel_move_to (cr
, 0, position
);
299 cairo_rel_line_to (cr
, -tickSize
, 0);
303 cairo_get_current_point (cr
, &x
, &y
);
309 cairo_move_to (cr
, x
, y
);
311 if (orientation
== SCALE_ABSCISSA
)
314 xrchart_label_rotate (cr
, 'l', 'c', geom
->font_size
, s
, -G_PI_4
);
316 xrchart_label (cr
, 'c', 't', geom
->font_size
, s
);
318 else if (orientation
== SCALE_ORDINATE
)
320 if (fabs (position
) < DBL_EPSILON
)
321 cairo_rel_move_to (cr
, 0, 10);
322 xrchart_label (cr
, 'r', 'c', geom
->font_size
, s
);
328 /* Write the title on a chart*/
330 xrchart_write_title (cairo_t
*cr
, const struct xrchart_geometry
*geom
,
331 const char *title
, ...)
337 cairo_move_to (cr
, geom
->axis
[SCALE_ABSCISSA
].data_min
, geom
->title_bottom
);
340 s
= xvasprintf (title
, ap
);
341 xrchart_label (cr
, 'l', 'x', geom
->font_size
* 1.5, s
);
351 xrchart_write_scale (cairo_t
*cr
, struct xrchart_geometry
*geom
,
352 double smin
, double smax
, enum tick_orientation orient
)
357 struct decimal dinterval
;
358 struct decimal dlower
;
359 struct decimal dupper
;
361 chart_get_scale (smax
, smin
, &dlower
, &dinterval
, &ticks
);
364 decimal_int_multiply (&dupper
, ticks
);
365 decimal_add (&dupper
, &dlower
);
367 double tick_interval
= decimal_to_double (&dinterval
);
369 geom
->axis
[orient
].max
= decimal_to_double (&dupper
);
370 geom
->axis
[orient
].min
= decimal_to_double (&dlower
);
372 geom
->axis
[orient
].scale
= (fabs (geom
->axis
[orient
].data_max
- geom
->axis
[orient
].data_min
)
373 / fabs (geom
->axis
[orient
].max
- geom
->axis
[orient
].min
));
375 struct decimal pos
= dlower
;
377 for (s
= 0 ; s
< ticks
; ++s
)
379 char *str
= decimal_to_string (&pos
);
380 draw_tick (cr
, geom
, orient
, false,
381 s
* tick_interval
* geom
->axis
[orient
].scale
,
385 decimal_add (&pos
, &dinterval
);
389 /* Set the scale for the ordinate */
391 xrchart_write_yscale (cairo_t
*cr
, struct xrchart_geometry
*geom
,
392 double smin
, double smax
)
394 xrchart_write_scale (cr
, geom
, smin
, smax
, SCALE_ORDINATE
);
397 /* Set the scale for the abscissa */
399 xrchart_write_xscale (cairo_t
*cr
, struct xrchart_geometry
*geom
,
400 double smin
, double smax
)
402 xrchart_write_scale (cr
, geom
, smin
, smax
, SCALE_ABSCISSA
);
406 /* Write the abscissa label */
408 xrchart_write_xlabel (cairo_t
*cr
, const struct xrchart_geometry
*geom
,
411 cairo_move_to (cr
, geom
->axis
[SCALE_ABSCISSA
].data_min
, geom
->abscissa_bottom
);
412 xrchart_label (cr
, 'l', 't', geom
->font_size
, label
);
415 /* Write the ordinate label */
417 xrchart_write_ylabel (cairo_t
*cr
, const struct xrchart_geometry
*geom
,
421 cairo_translate (cr
, geom
->ordinate_left
, geom
->axis
[SCALE_ORDINATE
].data_min
);
422 cairo_rotate (cr
, M_PI
/ 2.0);
424 xrchart_label (cr
, 'l', 'x', geom
->font_size
, label
);
430 xrchart_write_legend (cairo_t
*cr
, const struct xrchart_geometry
*geom
)
433 const int vstep
= geom
->font_size
* 2;
436 const int swatch
= 20;
437 const int legend_top
= geom
->axis
[SCALE_ORDINATE
].data_max
;
438 const int legend_bottom
= legend_top
-
439 (vstep
* geom
->n_datasets
+ 2 * ypad
);
443 cairo_rectangle (cr
, geom
->legend_left
, legend_top
,
444 geom
->legend_right
- xpad
- geom
->legend_left
,
445 legend_bottom
- legend_top
);
448 for (i
= 0 ; i
< geom
->n_datasets
; ++i
)
450 const int ypos
= legend_top
- vstep
* (i
+ 1);
451 const int xpos
= geom
->legend_left
+ xpad
;
452 const struct xrchart_colour
*colour
;
454 cairo_move_to (cr
, xpos
, ypos
);
457 colour
= &data_colour
[ i
% XRCHART_N_COLOURS
];
458 cairo_set_source_rgb (cr
,
460 colour
->green
/ 255.0,
461 colour
->blue
/ 255.0);
462 cairo_rectangle (cr
, xpos
, ypos
, swatch
, swatch
);
463 cairo_fill_preserve (cr
);
467 cairo_move_to (cr
, xpos
+ swatch
* 1.5, ypos
);
468 xrchart_label (cr
, 'l', 'x', geom
->font_size
, geom
->dataset
[i
]);
474 /* Start a new vector called NAME */
476 xrchart_vector_start (cairo_t
*cr
, struct xrchart_geometry
*geom
, const char *name
)
478 const struct xrchart_colour
*colour
;
482 colour
= &data_colour
[geom
->n_datasets
% XRCHART_N_COLOURS
];
483 cairo_set_source_rgb (cr
,
485 colour
->green
/ 255.0,
486 colour
->blue
/ 255.0);
489 geom
->dataset
= xrealloc (geom
->dataset
,
490 geom
->n_datasets
* sizeof (*geom
->dataset
));
492 geom
->dataset
[geom
->n_datasets
- 1] = strdup (name
);
495 /* Plot a data point */
497 xrchart_datum (cairo_t
*cr
, const struct xrchart_geometry
*geom
,
498 int dataset UNUSED
, double x
, double y
)
500 double x_pos
= (x
- geom
->axis
[SCALE_ABSCISSA
].min
) * geom
->axis
[SCALE_ABSCISSA
].scale
+ geom
->axis
[SCALE_ABSCISSA
].data_min
;
501 double y_pos
= (y
- geom
->axis
[SCALE_ORDINATE
].min
) * geom
->axis
[SCALE_ORDINATE
].scale
+ geom
->axis
[SCALE_ORDINATE
].data_min
;
503 xrchart_draw_marker (cr
, x_pos
, y_pos
, XRMARKER_CIRCLE
, 10);
507 xrchart_vector_end (cairo_t
*cr
, struct xrchart_geometry
*geom
)
511 geom
->in_path
= false;
514 /* Plot a data point */
516 xrchart_vector (cairo_t
*cr
, struct xrchart_geometry
*geom
, double x
, double y
)
519 (x
- geom
->axis
[SCALE_ABSCISSA
].min
) * geom
->axis
[SCALE_ABSCISSA
].scale
+ geom
->axis
[SCALE_ABSCISSA
].data_min
;
522 (y
- geom
->axis
[SCALE_ORDINATE
].min
) * geom
->axis
[SCALE_ORDINATE
].scale
+ geom
->axis
[SCALE_ORDINATE
].data_min
;
525 cairo_line_to (cr
, x_pos
, y_pos
);
528 cairo_move_to (cr
, x_pos
, y_pos
);
529 geom
->in_path
= true;
535 /* Draw a line with slope SLOPE and intercept INTERCEPT.
536 between the points limit1 and limit2.
537 If lim_dim is XRCHART_DIM_Y then the limit{1,2} are on the
538 y axis otherwise the x axis
541 xrchart_line(cairo_t
*cr
, const struct xrchart_geometry
*geom
,
542 double slope
, double intercept
,
543 double limit1
, double limit2
, enum xrchart_dim lim_dim
)
548 if ( lim_dim
== XRCHART_DIM_Y
)
550 x1
= ( limit1
- intercept
) / slope
;
551 x2
= ( limit2
- intercept
) / slope
;
559 y1
= slope
* x1
+ intercept
;
560 y2
= slope
* x2
+ intercept
;
563 y1
= (y1
- geom
->axis
[SCALE_ORDINATE
].min
) * geom
->axis
[SCALE_ORDINATE
].scale
+ geom
->axis
[SCALE_ORDINATE
].data_min
;
564 y2
= (y2
- geom
->axis
[SCALE_ORDINATE
].min
) * geom
->axis
[SCALE_ORDINATE
].scale
+ geom
->axis
[SCALE_ORDINATE
].data_min
;
565 x1
= (x1
- geom
->axis
[SCALE_ABSCISSA
].min
) * geom
->axis
[SCALE_ABSCISSA
].scale
+ geom
->axis
[SCALE_ABSCISSA
].data_min
;
566 x2
= (x2
- geom
->axis
[SCALE_ABSCISSA
].min
) * geom
->axis
[SCALE_ABSCISSA
].scale
+ geom
->axis
[SCALE_ABSCISSA
].data_min
;
568 cairo_move_to (cr
, x1
, y1
);
569 cairo_line_to (cr
, x2
, y2
);
574 xrchart_text_extents (cairo_t
*cr
, const struct xrchart_geometry
*geom
,
576 double *width
, double *height
)
578 PangoFontDescription
*desc
;
583 desc
= pango_font_description_from_string ("Sans");
586 pango_font_description_set_absolute_size (desc
, geom
->font_size
* PANGO_SCALE
);
587 layout
= pango_cairo_create_layout (cr
);
588 pango_layout_set_font_description (layout
, desc
);
589 pango_layout_set_text (layout
, utf8
, -1);
590 pango_layout_get_size (layout
, &width_pango
, &height_pango
);
591 *width
= (double) width_pango
/ PANGO_SCALE
;
592 *height
= (double) height_pango
/ PANGO_SCALE
;
593 g_object_unref (layout
);
594 pango_font_description_free (desc
);