6f92f87c966cfccbad2a372133f6f13f8507e972
[pspp] / src / output / pivot-output.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2018 Free Software Foundation, Inc.
3
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.
8
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.
13
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/>. */
16
17 #include <config.h>
18
19 #include <stdlib.h>
20
21 #include "output/pivot-table.h"
22
23 #include "data/settings.h"
24 #include "libpspp/assertion.h"
25 #include "libpspp/pool.h"
26 #include "output/table.h"
27 #include "output/page-eject-item.h"
28 #include "output/table-item.h"
29 #include "output/text-item.h"
30 #include "output/table-provider.h"
31
32 #include "gl/minmax.h"
33 #include "gl/xalloc.h"
34
35 #define H TABLE_HORZ
36 #define V TABLE_VERT
37
38 static const struct pivot_category *
39 find_category (const struct pivot_dimension *d, int dim_index,
40                const size_t *indexes, int row_ofs)
41 {
42   size_t index = indexes[dim_index];
43   assert (index < d->n_leaves);
44   for (const struct pivot_category *c = d->presentation_leaves[index];
45        c; c = c->parent)
46     {
47       /* A category can covert multiple rows.  Only return the category for its
48          top row. */
49       if (row_ofs == c->extra_depth)
50         return c;
51
52       row_ofs -= 1 + c->extra_depth;
53       if (row_ofs < 0)
54         return NULL;
55     }
56   return NULL;
57 }
58
59 static struct table_area_style *
60 table_area_style_override (struct pool *pool,
61                            const struct table_area_style *in,
62                            const struct cell_style *cell_,
63                            const struct font_style *font_,
64                            bool rotate_label)
65 {
66   const struct cell_style *cell = cell_ ? cell_ : &in->cell_style;
67   const struct font_style *font = font_ ? font_ : &in->font_style;
68
69   struct table_area_style *out = (pool
70                             ? pool_alloc (pool, sizeof *out)
71                             : xmalloc (sizeof *out));
72   *out = (struct table_area_style) {
73     .cell_style.halign = rotate_label ? TABLE_HALIGN_CENTER : cell->halign,
74     .cell_style.valign = rotate_label ? TABLE_VALIGN_CENTER : cell->valign,
75     .cell_style.decimal_offset = cell->decimal_offset,
76     .cell_style.margin[H][0] = cell->margin[H][0],
77     .cell_style.margin[H][1] = cell->margin[H][1],
78     .cell_style.margin[V][0] = cell->margin[V][0],
79     .cell_style.margin[V][1] = cell->margin[V][1],
80     .font_style.fg[0] = font->fg[0],
81     .font_style.fg[1] = font->fg[1],
82     .font_style.bg[0] = font->bg[0],
83     .font_style.bg[1] = font->bg[1],
84     .font_style.typeface = (font->typeface
85                             ? pool_strdup (pool, font->typeface)
86                             : NULL),
87     .font_style.size = font->size,
88     .font_style.bold = font->bold,
89     .font_style.italic = font->italic,
90     .font_style.underline = font->underline,
91     .font_style.markup = font->markup,
92   };
93   return out;
94 }
95
96 static void
97 fill_cell (struct table *t, int x1, int y1, int x2, int y2,
98            const struct table_area_style *style, int style_idx,
99            const struct pivot_value *value, struct footnote **footnotes,
100            enum settings_value_show show_values,
101            enum settings_value_show show_variables,
102            bool rotate_label)
103 {
104
105   struct string s = DS_EMPTY_INITIALIZER;
106   int opts = style_idx << TAB_STYLE_SHIFT;
107   if (value)
108     {
109       bool numeric = pivot_value_format_body (value, show_values,
110                                               show_variables, &s);
111       if (numeric)
112         opts |= TAB_NUMERIC;
113       if (value->font_style && value->font_style->markup)
114         opts |= TAB_MARKUP;
115       if (rotate_label)
116         opts |= TAB_ROTATE;
117     }
118   table_joint_text (t, x1, y1, x2, y2, opts, ds_cstr (&s));
119   ds_destroy (&s);
120
121   if (value)
122     {
123       if (value->cell_style || value->font_style || rotate_label)
124         table_add_style (t, x1, y1,
125                          table_area_style_override (t->container, style,
126                                                     value->cell_style,
127                                                     value->font_style,
128                                                     rotate_label));
129
130       for (size_t i = 0; i < value->n_footnotes; i++)
131         {
132           struct footnote *f = footnotes[value->footnotes[i]->idx];
133           if (f)
134             table_add_footnote (t, x1, y1, f);
135         }
136
137       if (value->n_subscripts)
138         table_add_subscripts (t, x1, y1,
139                               value->subscripts, value->n_subscripts);
140     }
141 }
142
143 static struct table_item_text *
144 pivot_value_to_table_item_text (const struct pivot_value *value,
145                                 const struct table_area_style *area,
146                                 struct footnote **footnotes,
147                                 enum settings_value_show show_values,
148                                 enum settings_value_show show_variables)
149 {
150   if (!value)
151     return NULL;
152
153   struct string s = DS_EMPTY_INITIALIZER;
154   pivot_value_format_body (value, show_values, show_variables, &s);
155
156   struct table_item_text *text = xmalloc (sizeof *text);
157   *text = (struct table_item_text) {
158     .content = ds_steal_cstr (&s),
159     .footnotes = xnmalloc (value->n_footnotes, sizeof *text->footnotes),
160     .style = table_area_style_override (
161       NULL, area, value->cell_style, value->font_style, false),
162   };
163
164   for (size_t i = 0; i < value->n_footnotes; i++)
165     {
166       struct footnote *f = footnotes[value->footnotes[i]->idx];
167       if (f)
168         text->footnotes[text->n_footnotes++] = f;
169     }
170
171   return text;
172 }
173
174 static int
175 get_table_rule (const struct table_border_style *styles,
176                 enum pivot_border style_idx)
177 {
178   return styles[style_idx].stroke | (style_idx << TAB_RULE_STYLE_SHIFT);
179 }
180
181 static void
182 draw_line (struct table *t, const struct table_border_style *styles,
183            enum pivot_border style_idx,
184            enum table_axis axis, int a, int b0, int b1)
185 {
186   int rule = get_table_rule (styles, style_idx);
187   if (axis == H)
188     table_hline (t, rule, b0, b1, a);
189   else
190     table_vline (t, rule, a, b0, b1);
191 }
192
193 static void
194 compose_headings (struct table *t,
195                   const struct pivot_axis *a_axis, enum table_axis a,
196                   const struct pivot_axis *b_axis,
197                   const struct table_border_style *borders,
198                   enum pivot_border dim_col_horz,
199                   enum pivot_border dim_col_vert,
200                   enum pivot_border cat_col_horz,
201                   enum pivot_border cat_col_vert,
202                   const size_t *column_enumeration, size_t n_columns,
203                   const struct table_area_style *label_style,
204                   int label_style_idx,
205                   const struct table_area_style *corner_style,
206                   struct footnote **footnotes,
207                   enum settings_value_show show_values,
208                   enum settings_value_show show_variables,
209                   bool rotate_inner_labels, bool rotate_outer_labels)
210 {
211   enum table_axis b = !a;
212   int b_size = a_axis->label_depth;
213   int a_ofs = b_axis->label_depth;
214
215   if (!a_axis->n_dimensions || !n_columns || !b_size)
216     return;
217
218   const int stride = MAX (1, a_axis->n_dimensions);
219
220   /* Below, we're going to iterate through the dimensions.  Each dimension
221      occupies one or more rows in the heading.  'top_row' is the top row of
222      these (and 'top_row + d->label_depth - 1' is the bottom row). */
223   int top_row = 0;
224
225   /* We're going to iterate through dimensions and the rows that label them
226      from top to bottom (from outer to inner dimensions).  As we move downward,
227      we start drawing vertical rules to separate categories and groups.  After
228      we start drawing a vertical rule in a particular horizontal position, it
229      continues until the bottom of the heading.  vrules[pos] indicates whether,
230      in our current row, we have already started drawing a vertical rule in
231      horizontal position 'pos'.  (There are n_columns + 1 horizontal positions.
232      We allocate all of them for convenience below but only the inner n_columns
233      - 1 of them really matter.)
234
235      Here's an example that shows how vertical rules continue all the way
236      downward:
237
238      +-----------------------------------------------------+ __
239      |                         bbbb                        |  |
240      +-----------------+-----------------+-----------------+  |dimension "bbbb"
241      |      bbbb1      |      bbbb2      |      bbbb3      | _|
242      +-----------------+-----------------+-----------------+ __
243      |       aaaa      |       aaaa      |       aaaa      |  |
244      +-----+-----+-----+-----+-----+-----+-----+-----+-----+  |dimension "aaaa"
245      |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3| _|
246      +-----+-----+-----+-----+-----+-----+-----+-----+-----+
247
248      ^     ^     ^     ^     ^     ^     ^     ^     ^     ^
249      |     |     |     |     |     |     |     |     |     |
250      0     1     2     3     4     5     6     7     8     9
251      |___________________vrules[] indexes__________________|
252
253      Our data structures are more naturally iterated from bottom to top (inner
254      to outer dimensions).  A previous version of this code actually worked
255      like that, but it didn't draw all of the vertical lines correctly as shown
256      above.  It ended up rendering the above heading much like shown below,
257      which isn't what users expect.  The "aaaa" label really needs to be shown
258      three times for clarity:
259
260      +-----------------------------------------------------+
261      |                         bbbb                        |
262      +-----------------+-----------------+-----------------+
263      |      bbbb1      |      bbbb2      |      bbbb3      |
264      +-----------------+-----------------+-----------------+
265      |                 |       aaaa      |                 |
266      +-----+-----+-----+-----+-----+-----+-----+-----+-----+
267      |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|
268      +-----+-----+-----+-----+-----+-----+-----+-----+-----+
269   */
270   bool *vrules = xzalloc (n_columns + 1);
271   for (int dim_index = a_axis->n_dimensions; --dim_index >= 0; )
272     {
273       const struct pivot_dimension *d = a_axis->dimensions[dim_index];
274       if (d->hide_all_labels)
275         continue;
276
277       for (int row_ofs = 0; row_ofs < d->label_depth; row_ofs++)
278         {
279           for (size_t x1 = 0; x1 < n_columns;)
280             {
281               const struct pivot_category *c = find_category (
282                 d, dim_index, column_enumeration + x1 * stride,
283                 d->label_depth - row_ofs - 1);
284               if (!c)
285                 {
286                   x1++;
287                   continue;
288                 }
289
290               size_t x2;
291               for (x2 = x1 + 1; x2 < n_columns; x2++)
292                 {
293                   if (vrules[x2])
294                     break;
295                   const struct pivot_category *c2 = find_category (
296                     d, dim_index, column_enumeration + x2 * stride,
297                     d->label_depth - row_ofs - 1);
298                   if (c != c2)
299                     break;
300                 }
301
302               int y1 = top_row + row_ofs;
303               int y2 = top_row + row_ofs + c->extra_depth + 1;
304               bool is_outer_row = y1 == 0;
305               bool is_inner_row = y2 == b_size;
306               if (pivot_category_is_leaf (c) || c->show_label)
307                 {
308                   int bb[TABLE_N_AXES][2];
309                   bb[a][0] = x1 + a_ofs;
310                   bb[a][1] = x2 + a_ofs - 1;
311                   bb[b][0] = y1;
312                   bb[b][1] = y2 - 1;
313                   bool rotate = ((rotate_inner_labels && is_inner_row)
314                                  || (rotate_outer_labels && is_outer_row));
315                   fill_cell (t, bb[H][0], bb[V][0], bb[H][1], bb[V][1],
316                              label_style, label_style_idx, c->name, footnotes,
317                              show_values, show_variables, rotate);
318
319                   if (pivot_category_is_leaf (c) && x2 + 1 <= n_columns)
320                     {
321                       enum pivot_border style
322                         = (y1 == 0 && a_axis->label_depth > d->label_depth
323                            ? dim_col_vert
324                            : cat_col_vert);
325                       draw_line (t, borders, style, b, x2 + a_ofs, y1,
326                                  t->n[b] - 1);
327                       vrules[x2] = true;
328                     }
329                   if (pivot_category_is_leaf (c) && x1 > 0)
330                     {
331                       enum pivot_border style
332                         = (y1 == 0 && a_axis->label_depth > d->label_depth
333                            ? dim_col_vert
334                            : cat_col_vert);
335                       draw_line (t, borders, style, b, x1 + a_ofs, y1,
336                                  t->n[b] - 1);
337                       vrules[x1] = true;
338                     }
339                 }
340               if (c->parent && c->parent->show_label)
341                 draw_line (t, borders, cat_col_horz, a, y1,
342                            x1 + a_ofs, x2 + a_ofs - 1);
343               x1 = x2;
344             }
345         }
346
347       if (d->root->show_label_in_corner && a_ofs > 0)
348         {
349           int bb[TABLE_N_AXES][2];
350           bb[a][0] = 0;
351           bb[a][1] = a_ofs - 1;
352           bb[b][0] = top_row;
353           bb[b][1] = top_row + d->label_depth - 1;
354           fill_cell (t, bb[H][0], bb[V][0], bb[H][1], bb[V][1],
355                      corner_style, PIVOT_AREA_CORNER, d->root->name, footnotes,
356                      show_values, show_variables, false);
357         }
358
359       if (dim_index > 1)
360         draw_line (t, borders, dim_col_horz, a, top_row, a_ofs,
361                    t->n[a] - 1);
362       top_row += d->label_depth;
363     }
364   free (vrules);
365 }
366
367 static void
368 pivot_table_submit_layer (const struct pivot_table *pt,
369                           const size_t *layer_indexes)
370 {
371   const size_t *pindexes[PIVOT_N_AXES]
372     = { [PIVOT_AXIS_LAYER] = layer_indexes };
373
374   size_t body[TABLE_N_AXES];
375   size_t *column_enumeration = pivot_table_enumerate_axis (
376     pt, PIVOT_AXIS_COLUMN, layer_indexes, pt->look->omit_empty, &body[H]);
377   size_t *row_enumeration = pivot_table_enumerate_axis (
378     pt, PIVOT_AXIS_ROW, layer_indexes, pt->look->omit_empty, &body[V]);
379
380   int stub[TABLE_N_AXES] = {
381     [H] = pt->axes[PIVOT_AXIS_ROW].label_depth,
382     [V] = pt->axes[PIVOT_AXIS_COLUMN].label_depth,
383   };
384   struct table *table = table_create (body[H] + stub[H],
385                                       body[V] + stub[V],
386                                       stub[H], 0, stub[V], 0);
387
388   for (size_t i = 0; i < PIVOT_N_AREAS; i++)
389     table->styles[i] = table_area_style_override (
390       table->container, &pt->look->areas[i], NULL, NULL, false);
391
392   for (size_t i = 0; i < PIVOT_N_BORDERS; i++)
393     {
394       const struct table_border_style *in = &pt->look->borders[i];
395       table->rule_colors[i] = pool_alloc (table->container,
396                                           sizeof *table->rule_colors[i]);
397       struct cell_color *out = table->rule_colors[i];
398       out->alpha = in->color.alpha;
399       out->r = in->color.r;
400       out->g = in->color.g;
401       out->b = in->color.b;
402     }
403
404   struct footnote **footnotes = XCALLOC (pt->n_footnotes,  struct footnote *);
405   for (size_t i = 0; i < pt->n_footnotes; i++)
406     {
407       const struct pivot_footnote *pf = pt->footnotes[i];
408
409       if (!pf->show)
410         continue;
411
412       char *content = pivot_value_to_string (pf->content, pt->show_values,
413                                              pt->show_variables);
414       char *marker = pivot_value_to_string (pf->marker, pt->show_values,
415                                             pt->show_variables);
416       footnotes[i] = table_create_footnote (
417         table, i, content, marker,
418         table_area_style_override (table->container,
419                                    &pt->look->areas[PIVOT_AREA_FOOTER],
420                                    pf->content->cell_style,
421                                    pf->content->font_style,
422                                    false));
423       free (marker);
424       free (content);
425     }
426
427   compose_headings (table,
428                     &pt->axes[PIVOT_AXIS_COLUMN], H, &pt->axes[PIVOT_AXIS_ROW],
429                     pt->look->borders,
430                     PIVOT_BORDER_DIM_COL_HORZ,
431                     PIVOT_BORDER_DIM_COL_VERT,
432                     PIVOT_BORDER_CAT_COL_HORZ,
433                     PIVOT_BORDER_CAT_COL_VERT,
434                     column_enumeration, body[H],
435                     &pt->look->areas[PIVOT_AREA_COLUMN_LABELS],
436                     PIVOT_AREA_COLUMN_LABELS,
437                     &pt->look->areas[PIVOT_AREA_CORNER], footnotes,
438                     pt->show_values, pt->show_variables,
439                     pt->rotate_outer_row_labels, false);
440
441   compose_headings (table,
442                     &pt->axes[PIVOT_AXIS_ROW], V, &pt->axes[PIVOT_AXIS_COLUMN],
443                     pt->look->borders,
444                     PIVOT_BORDER_DIM_ROW_VERT,
445                     PIVOT_BORDER_DIM_ROW_HORZ,
446                     PIVOT_BORDER_CAT_ROW_VERT,
447                     PIVOT_BORDER_CAT_ROW_HORZ,
448                     row_enumeration, body[V],
449                     &pt->look->areas[PIVOT_AREA_ROW_LABELS],
450                     PIVOT_AREA_ROW_LABELS,
451                     &pt->look->areas[PIVOT_AREA_CORNER], footnotes,
452                     pt->show_values, pt->show_variables,
453                     false, pt->rotate_inner_column_labels);
454
455   size_t *dindexes = XCALLOC (pt->n_dimensions, size_t);
456   size_t y = 0;
457   PIVOT_ENUMERATION_FOR_EACH (pindexes[PIVOT_AXIS_ROW], row_enumeration,
458                               &pt->axes[PIVOT_AXIS_ROW])
459     {
460       size_t x = 0;
461       PIVOT_ENUMERATION_FOR_EACH (pindexes[PIVOT_AXIS_COLUMN],
462                                   column_enumeration,
463                                   &pt->axes[PIVOT_AXIS_COLUMN])
464         {
465           pivot_table_convert_indexes_ptod (pt, pindexes, dindexes);
466           const struct pivot_value *value = pivot_table_get (pt, dindexes);
467           fill_cell (table,
468                      x + stub[H], y + stub[V],
469                      x + stub[H], y + stub[V],
470                      &pt->look->areas[PIVOT_AREA_DATA], PIVOT_AREA_DATA,
471                      value, footnotes,
472                      pt->show_values, pt->show_variables, false);
473
474           x++;
475         }
476
477       y++;
478     }
479   free (dindexes);
480
481   if ((pt->corner_text || !pt->look->row_labels_in_corner)
482       && stub[H] && stub[V])
483     fill_cell (table, 0, 0, stub[H] - 1, stub[V] - 1,
484                &pt->look->areas[PIVOT_AREA_CORNER], PIVOT_AREA_CORNER,
485                pt->corner_text, footnotes,
486                pt->show_values, pt->show_variables, false);
487
488   if (table->n[H] && table->n[V])
489     {
490       table_hline (
491         table, get_table_rule (pt->look->borders, PIVOT_BORDER_INNER_TOP),
492         0, table->n[H] - 1, 0);
493       table_hline (
494         table, get_table_rule (pt->look->borders, PIVOT_BORDER_INNER_BOTTOM),
495         0, table->n[H] - 1, table->n[V]);
496       table_vline (
497         table, get_table_rule (pt->look->borders, PIVOT_BORDER_INNER_LEFT),
498         0, 0, table->n[V] - 1);
499       table_vline (
500         table, get_table_rule (pt->look->borders, PIVOT_BORDER_INNER_RIGHT),
501         table->n[H], 0, table->n[V] - 1);
502
503       if (stub[V])
504         table_hline (
505           table, get_table_rule (pt->look->borders, PIVOT_BORDER_DATA_TOP),
506           0, table->n[H] - 1, stub[V]);
507       if (stub[H])
508         table_vline (
509           table, get_table_rule (pt->look->borders, PIVOT_BORDER_DATA_LEFT),
510           stub[H], 0, table->n[V] - 1);
511
512     }
513   free (column_enumeration);
514   free (row_enumeration);
515
516   struct table_item *ti = table_item_create (table, NULL, NULL, pt->notes);
517
518   if (pt->title && pt->show_title)
519     {
520       struct table_item_text *title = pivot_value_to_table_item_text (
521         pt->title, &pt->look->areas[PIVOT_AREA_TITLE], footnotes,
522         pt->show_values, pt->show_variables);
523       table_item_set_title (ti, title);
524       table_item_text_destroy (title);
525     }
526
527   const struct pivot_axis *layer_axis = &pt->axes[PIVOT_AXIS_LAYER];
528   struct table_item_layers *layers = NULL;
529   for (size_t i = 0; i < layer_axis->n_dimensions; i++)
530     {
531       const struct pivot_dimension *d = layer_axis->dimensions[i];
532       if (d->n_leaves)
533         {
534           if (!layers)
535             {
536               layers = xzalloc (sizeof *layers);
537               layers->style = table_area_style_override (
538                 NULL, &pt->look->areas[PIVOT_AREA_LAYERS], NULL, NULL, false);
539               layers->layers = xnmalloc (layer_axis->n_dimensions,
540                                          sizeof *layers->layers);
541             }
542
543           const struct pivot_value *name
544             = d->data_leaves[layer_indexes[i]]->name;
545           struct table_item_layer *layer = &layers->layers[layers->n_layers++];
546           struct string s = DS_EMPTY_INITIALIZER;
547           pivot_value_format_body (name, pt->show_values, pt->show_variables,
548                                    &s);
549           layer->content = ds_steal_cstr (&s);
550           layer->n_footnotes = 0;
551           layer->footnotes = xnmalloc (name->n_footnotes,
552                                        sizeof *layer->footnotes);
553           for (size_t i = 0; i < name->n_footnotes; i++)
554             {
555               struct footnote *f = footnotes[name->footnotes[i]->idx];
556               if (f)
557                 layer->footnotes[layer->n_footnotes++] = f;
558             }
559         }
560     }
561   if (layers)
562     {
563       table_item_set_layers (ti, layers);
564       table_item_layers_destroy (layers);
565     }
566
567   if (pt->caption && pt->show_caption)
568     {
569       struct table_item_text *caption = pivot_value_to_table_item_text (
570         pt->caption, &pt->look->areas[PIVOT_AREA_CAPTION], footnotes,
571         pt->show_values, pt->show_variables);
572       table_item_set_caption (ti, caption);
573       table_item_text_destroy (caption);
574     }
575
576   free (footnotes);
577   ti->pt = pivot_table_ref (pt);
578
579   table_item_submit (ti);
580 }
581
582 void
583 pivot_table_submit (struct pivot_table *pt)
584 {
585   pivot_table_assign_label_depth (CONST_CAST (struct pivot_table *, pt));
586
587   int old_decimal = settings_get_decimal_char (FMT_COMMA);
588   if (pt->decimal == '.' || pt->decimal == ',')
589     settings_set_decimal_char (pt->decimal);
590
591   if (pt->look->print_all_layers)
592     {
593       size_t *layer_indexes;
594
595       PIVOT_AXIS_FOR_EACH (layer_indexes, &pt->axes[PIVOT_AXIS_LAYER])
596         {
597           if (pt->look->paginate_layers)
598             page_eject_item_submit (page_eject_item_create ());
599           pivot_table_submit_layer (pt, layer_indexes);
600         }
601     }
602   else
603     pivot_table_submit_layer (pt, pt->current_layer);
604
605   settings_set_decimal_char (old_decimal);
606
607   pivot_table_unref (pt);
608 }