1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 2018 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/>. */
21 #include "output/pivot-output.h"
23 #include "data/settings.h"
24 #include "libpspp/assertion.h"
25 #include "libpspp/pool.h"
26 #include "output/output-item.h"
27 #include "output/pivot-table.h"
28 #include "output/table-provider.h"
29 #include "output/table.h"
31 #include "gl/minmax.h"
32 #include "gl/xalloc.h"
38 pivot_output_next_layer (const struct pivot_table *pt, size_t *indexes,
41 const struct pivot_axis *layer_axis = &pt->axes[PIVOT_AXIS_LAYER];
42 if (print && pt->look->print_all_layers)
43 return pivot_axis_iterator_next (indexes, layer_axis);
46 size_t size = layer_axis->n_dimensions * sizeof *pt->current_layer;
47 return size ? xmemdup (pt->current_layer, size) : xmalloc (1);
56 static const struct pivot_category *
57 find_category (const struct pivot_dimension *d, int dim_index,
58 const size_t *indexes, int row_ofs)
60 size_t index = indexes[dim_index];
61 assert (index < d->n_leaves);
62 for (const struct pivot_category *c = d->presentation_leaves[index];
65 /* A category can cover multiple rows. Only return the category for its
67 if (row_ofs == c->extra_depth)
70 row_ofs -= 1 + c->extra_depth;
77 static struct table_area_style *
78 table_area_style_override (struct pool *pool,
79 const struct table_area_style *in,
80 const struct cell_style *cell_,
81 const struct font_style *font_,
84 const struct cell_style *cell = cell_ ? cell_ : &in->cell_style;
85 const struct font_style *font = font_ ? font_ : &in->font_style;
87 struct table_area_style *out = (pool
88 ? pool_alloc (pool, sizeof *out)
89 : xmalloc (sizeof *out));
90 *out = (struct table_area_style) {
91 .cell_style.halign = rotate_label ? TABLE_HALIGN_CENTER : cell->halign,
92 .cell_style.valign = rotate_label ? TABLE_VALIGN_CENTER : cell->valign,
93 .cell_style.decimal_offset = cell->decimal_offset,
94 .cell_style.margin[H][0] = cell->margin[H][0],
95 .cell_style.margin[H][1] = cell->margin[H][1],
96 .cell_style.margin[V][0] = cell->margin[V][0],
97 .cell_style.margin[V][1] = cell->margin[V][1],
98 .font_style.fg[0] = font->fg[0],
99 .font_style.fg[1] = font->fg[1],
100 .font_style.bg[0] = font->bg[0],
101 .font_style.bg[1] = font->bg[1],
102 .font_style.typeface = (font->typeface
103 ? pool_strdup (pool, font->typeface)
105 .font_style.size = font->size,
106 .font_style.bold = font->bold,
107 .font_style.italic = font->italic,
108 .font_style.underline = font->underline,
109 .font_style.markup = font->markup,
115 fill_cell (struct table *t, int x1, int y1, int x2, int y2,
116 int style_idx, const struct pivot_value *value,
119 int options = style_idx << TABLE_CELL_STYLE_SHIFT;
121 options |= TABLE_CELL_ROTATE;
123 table_put (t, x1, y1, x2, y2, options, value);
127 fill_cell_owned (struct table *t, int x1, int y1, int x2, int y2,
128 int style_idx, struct string *s, bool rotate_label)
130 int options = style_idx << TABLE_CELL_STYLE_SHIFT;
132 options |= TABLE_CELL_ROTATE;
134 table_put_owned (t, x1, y1, x2, y2, options,
135 pivot_value_new_user_text_nocopy (ds_steal_cstr (s)));
139 draw_line (struct table *t, enum pivot_border border_idx,
140 enum table_axis axis, int a, int b0, int b1)
143 table_hline (t, border_idx, b0, b1, a);
145 table_vline (t, border_idx, a, b0, b1);
148 /* Fills row or column headings into T.
150 This function uses terminology and variable names for column headings, but
151 it also applies to row headings because it uses variables for the
152 differences, e.g. when for column headings it would use the H axis, it
153 instead uses 'h', which is set to H for column headings and V for row
156 compose_headings (struct table *t,
157 const struct pivot_axis *h_axis, enum table_axis h,
158 const struct pivot_axis *v_axis,
159 enum pivot_border dim_col_horz,
160 enum pivot_border dim_col_vert,
161 enum pivot_border cat_col_horz,
162 enum pivot_border cat_col_vert,
163 const size_t *column_enumeration, size_t n_columns,
165 bool rotate_inner_labels, bool rotate_outer_labels)
167 const enum table_axis v = !h;
168 const int v_size = h_axis->label_depth;
169 const int h_ofs = v_axis->label_depth;
171 if (!h_axis->n_dimensions || !n_columns || !v_size)
174 const int stride = MAX (1, h_axis->n_dimensions);
176 /* Below, we're going to iterate through the dimensions. Each dimension
177 occupies one or more rows in the heading. 'top_row' is the top row of
178 these (and 'top_row + d->label_depth - 1' is the bottom row). */
181 /* We're going to iterate through dimensions and the rows that label them
182 from top to bottom (from outer to inner dimensions). As we move downward,
183 we start drawing vertical rules to separate categories and groups. After
184 we start drawing a vertical rule in a particular horizontal position, it
185 continues until the bottom of the heading. vrules[pos] indicates whether,
186 in our current row, we have already started drawing a vertical rule in
187 horizontal position 'pos'. (There are n_columns + 1 horizontal positions.
188 We allocate all of them for convenience below but only the inner n_columns
189 - 1 of them really matter.)
191 Here's an example that shows how vertical rules continue all the way
194 +-----------------------------------------------------+ __
196 +-----------------+-----------------+-----------------+ |dimension "bbbb"
197 | bbbb1 | bbbb2 | bbbb3 | _|
198 +-----------------+-----------------+-----------------+ __
199 | aaaa | aaaa | aaaa | |
200 +-----+-----+-----+-----+-----+-----+-----+-----+-----+ |dimension "aaaa"
201 |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3| _|
202 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
207 |___________________vrules[] indexes__________________|
209 Our data structures are more naturally iterated from bottom to top (inner
210 to outer dimensions). A previous version of this code actually worked
211 like that, but it didn't draw all of the vertical lines correctly as shown
212 above. It ended up rendering the above heading much like shown below,
213 which isn't what users expect. The "aaaa" label really needs to be shown
214 three times for clarity:
216 +-----------------------------------------------------+
218 +-----------------+-----------------+-----------------+
219 | bbbb1 | bbbb2 | bbbb3 |
220 +-----------------+-----------------+-----------------+
222 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
223 |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|
224 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
226 bool *vrules = XCALLOC (n_columns + 1, bool);
227 vrules[0] = vrules[n_columns] = true;
228 for (int dim_index = h_axis->n_dimensions; --dim_index >= 0; )
230 const struct pivot_dimension *d = h_axis->dimensions[dim_index];
231 if (d->hide_all_labels)
234 for (int row_ofs = 0; row_ofs < d->label_depth; row_ofs++)
236 for (size_t x1 = 0; x1 < n_columns;)
238 const struct pivot_category *c = find_category (
239 d, dim_index, column_enumeration + x1 * stride,
240 d->label_depth - row_ofs - 1);
248 for (x2 = x1 + 1; x2 < n_columns; x2++)
252 const struct pivot_category *c2 = find_category (
253 d, dim_index, column_enumeration + x2 * stride,
254 d->label_depth - row_ofs - 1);
259 int y1 = top_row + row_ofs;
260 int y2 = top_row + row_ofs + c->extra_depth + 1;
261 bool is_outer_row = y1 == 0;
262 bool is_inner_row = y2 == v_size;
263 if (pivot_category_is_leaf (c) || c->show_label)
265 int bb[TABLE_N_AXES][2];
266 bb[h][0] = x1 + h_ofs;
267 bb[h][1] = x2 + h_ofs - 1;
270 bool rotate = ((rotate_inner_labels && is_inner_row)
271 || (rotate_outer_labels && is_outer_row));
272 fill_cell (t, bb[H][0], bb[V][0], bb[H][1], bb[V][1],
273 label_style_idx, c->name, rotate);
275 /* Draw all the vertical lines in our running example, other
276 than the far left and far right ones. Only the ones that
277 start in the last row of the heading are drawn with the
278 "category" style, the rest with the "dimension" style,
279 e.g. only the # below are category style:
281 +-----------------------------------------------------+
283 +-----------------+-----------------+-----------------+
284 | bbbb1 | bbbb2 | bbbb3 |
285 +-----------------+-----------------+-----------------+
286 | aaaa | aaaa | aaaa |
287 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
288 |aaaa1#aaaa2#aaaa3|aaaa1#aaaa2#aaaa3|aaaa1#aaaa2#aaaa3|
289 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
291 enum pivot_border style
292 = (y1 == v_size - 1 ? cat_col_vert : dim_col_vert);
295 draw_line (t, style, v, x2 + h_ofs, y1, t->n[v] - 1);
300 draw_line (t, style, v, x1 + h_ofs, y1, t->n[v] - 1);
305 /* Draws the horizontal lines within a dimension, that is, those
306 that separate a separating a category (or group) from its
307 parent group or dimension's label. Our running example
308 doesn't have groups but the ==== lines below show the
309 separators between categories and their dimension label:
311 +-----------------------------------------------------+
313 +=================+=================+=================+
314 | bbbb1 | bbbb2 | bbbb3 |
315 +-----------------+-----------------+-----------------+
316 | aaaa | aaaa | aaaa |
317 +=====+=====+=====+=====+=====+=====+=====+=====+=====+
318 |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|
319 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
321 if (c->parent && c->parent->show_label)
322 draw_line (t, cat_col_horz, h, y1, x1 + h_ofs, x2 + h_ofs - 1);
327 if (d->root->show_label_in_corner && h_ofs > 0)
329 int bb[TABLE_N_AXES][2];
331 bb[h][1] = h_ofs - 1;
333 bb[v][1] = top_row + d->label_depth - 1;
334 fill_cell (t, bb[H][0], bb[V][0], bb[H][1], bb[V][1],
335 PIVOT_AREA_CORNER, d->root->name, false);
338 /* Draw the horizontal line between dimensions, e.g. the ===== line here:
340 +-----------------------------------------------------+ __
342 +-----------------+-----------------+-----------------+ |dim "bbbb"
343 | bbbb1 | bbbb2 | bbbb3 | _|
344 +=================+=================+=================+ __
345 | aaaa | aaaa | aaaa | |
346 +-----+-----+-----+-----+-----+-----+-----+-----+-----+ |dim "aaaa"
347 |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3| _|
348 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
350 if (dim_index != h_axis->n_dimensions - 1)
351 draw_line (t, dim_col_horz, h, top_row, h_ofs, t->n[h] - 1);
352 top_row += d->label_depth;
357 static struct table *
358 create_aux_table (const struct pivot_table *pt, int nc, int nr,
361 struct table *table = table_create (nc, nr, 0, 0, 0, 0);
362 table->styles[style_idx] = table_area_style_override (
363 table->container, &pt->look->areas[style_idx], NULL, NULL, false);
369 add_references (const struct pivot_table *pt, const struct table *table,
370 bool *refs, size_t *n_refs)
375 for (int y = 0; y < table->n[V]; y++)
376 for (int x = 0; x < table->n[H]; )
378 struct table_cell cell;
379 table_get_cell (table, x, y, &cell);
381 if (x == cell.d[H][0] && y == cell.d[V][0])
383 const struct pivot_value_ex *ex = pivot_value_ex (cell.value);
384 for (size_t i = 0; i < ex->n_footnotes; i++)
386 size_t idx = ex->footnote_indexes[i];
387 assert (idx < pt->n_footnotes);
389 if (!refs[idx] && pt->footnotes[idx]->show)
397 x = cell.d[TABLE_HORZ][1];
401 static struct pivot_footnote **
402 collect_footnotes (const struct pivot_table *pt,
403 const struct table *title,
404 const struct table *layers,
405 const struct table *body,
406 const struct table *caption,
407 size_t *n_footnotesp)
409 if (!pt->n_footnotes)
415 bool *refs = XCALLOC (pt->n_footnotes, bool);
417 add_references (pt, title, refs, &n_refs);
418 add_references (pt, layers, refs, &n_refs);
419 add_references (pt, body, refs, &n_refs);
420 add_references (pt, caption, refs, &n_refs);
422 struct pivot_footnote **footnotes = xnmalloc (n_refs, sizeof *footnotes);
423 size_t n_footnotes = 0;
424 for (size_t i = 0; i < pt->n_footnotes; i++)
426 footnotes[n_footnotes++] = pt->footnotes[i];
427 assert (n_footnotes == n_refs);
431 *n_footnotesp = n_footnotes;
435 static enum pivot_border
436 pivot_border_fallback (enum pivot_border border)
440 case PIVOT_BORDER_TITLE:
441 case PIVOT_BORDER_OUTER_LEFT:
442 case PIVOT_BORDER_OUTER_TOP:
443 case PIVOT_BORDER_OUTER_RIGHT:
444 case PIVOT_BORDER_OUTER_BOTTOM:
445 case PIVOT_BORDER_INNER_LEFT:
446 case PIVOT_BORDER_INNER_TOP:
447 case PIVOT_BORDER_INNER_RIGHT:
448 case PIVOT_BORDER_INNER_BOTTOM:
449 case PIVOT_BORDER_DATA_LEFT:
450 case PIVOT_BORDER_DATA_TOP:
454 case PIVOT_BORDER_DIM_ROW_HORZ:
455 return PIVOT_BORDER_CAT_ROW_HORZ;
456 case PIVOT_BORDER_DIM_ROW_VERT:
457 return PIVOT_BORDER_CAT_ROW_VERT;
458 case PIVOT_BORDER_DIM_COL_HORZ:
459 return PIVOT_BORDER_CAT_COL_HORZ;
460 case PIVOT_BORDER_DIM_COL_VERT:
461 return PIVOT_BORDER_CAT_COL_VERT;
464 case PIVOT_BORDER_CAT_ROW_HORZ:
465 case PIVOT_BORDER_CAT_ROW_VERT:
466 case PIVOT_BORDER_CAT_COL_HORZ:
467 case PIVOT_BORDER_CAT_COL_VERT:
470 case PIVOT_N_BORDERS:
476 static struct table_border_style
477 resolve_border_style (const struct pivot_table_look *look, enum pivot_border b,
478 bool show_grid_lines)
480 struct table_border_style style = look->borders[b];
481 if (style.stroke != TABLE_STROKE_NONE)
484 style = look->borders[pivot_border_fallback (b)];
485 if (style.stroke != TABLE_STROKE_NONE)
489 return (struct table_border_style) { .stroke = TABLE_STROKE_DASHED,
490 .color = CELL_COLOR_BLACK };
496 pivot_output (const struct pivot_table *pt,
497 const size_t *layer_indexes,
499 struct table **titlep,
500 struct table **layersp,
501 struct table **bodyp,
502 struct table **captionp,
503 struct table **footnotesp,
504 struct pivot_footnote ***fp, size_t *nfp)
506 const size_t *pindexes[PIVOT_N_AXES]
507 = { [PIVOT_AXIS_LAYER] = layer_indexes };
509 size_t data[TABLE_N_AXES];
510 size_t *column_enumeration = pivot_table_enumerate_axis (
511 pt, PIVOT_AXIS_COLUMN, layer_indexes, pt->look->omit_empty, &data[H]);
512 size_t *row_enumeration = pivot_table_enumerate_axis (
513 pt, PIVOT_AXIS_ROW, layer_indexes, pt->look->omit_empty, &data[V]);
515 int stub[TABLE_N_AXES] = {
516 [H] = pt->axes[PIVOT_AXIS_ROW].label_depth,
517 [V] = pt->axes[PIVOT_AXIS_COLUMN].label_depth,
519 struct table *body = table_create (data[H] + stub[H],
521 stub[H], 0, stub[V], 0);
522 for (size_t i = 0; i < PIVOT_N_AREAS; i++)
523 body->styles[i] = table_area_style_override (
524 body->container, &pt->look->areas[i], NULL, NULL, false);
526 body->n_borders = PIVOT_N_BORDERS;
527 body->borders = pool_nmalloc (body->container, PIVOT_N_BORDERS,
528 sizeof *body->borders);
529 for (size_t i = 0; i < PIVOT_N_BORDERS; i++)
530 body->borders[i] = resolve_border_style (pt->look, i,
531 printing && pt->show_grid_lines);
533 compose_headings (body,
534 &pt->axes[PIVOT_AXIS_COLUMN], H, &pt->axes[PIVOT_AXIS_ROW],
535 PIVOT_BORDER_DIM_COL_HORZ,
536 PIVOT_BORDER_DIM_COL_VERT,
537 PIVOT_BORDER_CAT_COL_HORZ,
538 PIVOT_BORDER_CAT_COL_VERT,
539 column_enumeration, data[H],
540 PIVOT_AREA_COLUMN_LABELS,
541 pt->rotate_outer_row_labels, false);
543 compose_headings (body,
544 &pt->axes[PIVOT_AXIS_ROW], V, &pt->axes[PIVOT_AXIS_COLUMN],
545 PIVOT_BORDER_DIM_ROW_VERT,
546 PIVOT_BORDER_DIM_ROW_HORZ,
547 PIVOT_BORDER_CAT_ROW_VERT,
548 PIVOT_BORDER_CAT_ROW_HORZ,
549 row_enumeration, data[V],
550 PIVOT_AREA_ROW_LABELS,
551 false, pt->rotate_inner_column_labels);
553 size_t *dindexes = XCALLOC (pt->n_dimensions, size_t);
555 PIVOT_ENUMERATION_FOR_EACH (pindexes[PIVOT_AXIS_ROW], row_enumeration,
556 &pt->axes[PIVOT_AXIS_ROW])
559 PIVOT_ENUMERATION_FOR_EACH (pindexes[PIVOT_AXIS_COLUMN],
561 &pt->axes[PIVOT_AXIS_COLUMN])
563 pivot_table_convert_indexes_ptod (pt, pindexes, dindexes);
564 const struct pivot_value *value = pivot_table_get (pt, dindexes);
565 fill_cell (body, x + stub[H], y + stub[V], x + stub[H], y + stub[V],
566 PIVOT_AREA_DATA, value, false);
575 if ((pt->corner_text || !pt->look->row_labels_in_corner)
576 && stub[H] && stub[V])
577 fill_cell (body, 0, 0, stub[H] - 1, stub[V] - 1,
578 PIVOT_AREA_CORNER, pt->corner_text, false);
580 if (body->n[H] && body->n[V])
582 table_hline (body, PIVOT_BORDER_INNER_TOP, 0, body->n[H] - 1, 0);
583 table_hline (body, PIVOT_BORDER_INNER_BOTTOM, 0, body->n[H] - 1,
585 table_vline (body, PIVOT_BORDER_INNER_LEFT, 0, 0, body->n[V] - 1);
586 table_vline (body, PIVOT_BORDER_INNER_RIGHT, body->n[H], 0,
590 table_hline (body, PIVOT_BORDER_DATA_TOP, 0, body->n[H] - 1, stub[V]);
592 table_vline (body, PIVOT_BORDER_DATA_LEFT, stub[H], 0, body->n[V] - 1);
595 free (column_enumeration);
596 free (row_enumeration);
600 if (pt->title && pt->show_title && titlep)
602 title = create_aux_table (pt, 1, 1, PIVOT_AREA_TITLE);
603 fill_cell (title, 0, 0, 0, 0, PIVOT_AREA_TITLE, pt->title, false);
609 const struct pivot_axis *layer_axis = &pt->axes[PIVOT_AXIS_LAYER];
612 for (size_t i = 0; i < layer_axis->n_dimensions; i++)
614 const struct pivot_dimension *d = layer_axis->dimensions[i];
619 struct table *layers;
622 layers = create_aux_table (pt, 1, n_layers, PIVOT_AREA_LAYERS);
623 size_t y = n_layers - 1;
624 for (size_t i = 0; i < layer_axis->n_dimensions; i++)
626 const struct pivot_dimension *d = layer_axis->dimensions[i];
630 struct string s = DS_EMPTY_INITIALIZER;
631 pivot_value_format (d->data_leaves[layer_indexes[i]]->name, pt, &s);
632 fill_cell_owned (layers, 0, y, 0, y, PIVOT_AREA_LAYERS, &s, false);
640 struct table *caption;
641 if (pt->caption && pt->show_caption && captionp)
643 caption = create_aux_table (pt, 1, 1, PIVOT_AREA_CAPTION);
644 fill_cell (caption, 0, 0, 0, 0, PIVOT_AREA_CAPTION, pt->caption, false);
651 struct pivot_footnote **f = collect_footnotes (pt, title, layers, body,
653 struct table *footnotes;
654 if (nf && footnotesp)
656 footnotes = create_aux_table (pt, 1, nf, PIVOT_AREA_FOOTER);
658 for (size_t i = 0; i < nf; i++)
660 struct string s = DS_EMPTY_INITIALIZER;
661 pivot_footnote_format_marker (f[i], pt, &s);
662 ds_put_cstr (&s, ". ");
663 pivot_value_format (f[i]->content, pt, &s);
664 fill_cell_owned (footnotes, 0, i, 0, i, PIVOT_AREA_FOOTER, &s,
678 *footnotesp = footnotes;
689 pivot_table_submit (struct pivot_table *pt)
691 output_item_submit (table_item_create (pt));