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 << TAB_STYLE_SHIFT;
121 options |= TAB_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 << TAB_STYLE_SHIFT;
132 options |= TAB_ROTATE;
134 table_put_owned (t, x1, y1, x2, y2, options,
135 pivot_value_new_user_text_nocopy (ds_steal_cstr (s)));
139 get_table_rule (const struct table_border_style *styles,
140 enum pivot_border style_idx)
142 return styles[style_idx].stroke | (style_idx << TAB_RULE_STYLE_SHIFT);
146 draw_line (struct table *t, const struct table_border_style *styles,
147 enum pivot_border style_idx,
148 enum table_axis axis, int a, int b0, int b1)
150 int rule = get_table_rule (styles, style_idx);
152 table_hline (t, rule, b0, b1, a);
154 table_vline (t, rule, a, b0, b1);
157 /* Fills row or column headings into T.
159 This function uses terminology and variable names for column headings, but
160 it also applies to row headings because it uses variables for the
161 differences, e.g. when for column headings it would use the H axis, it
162 instead uses 'h', which is set to H for column headings and V for row
165 compose_headings (struct table *t,
166 const struct pivot_axis *h_axis, enum table_axis h,
167 const struct pivot_axis *v_axis,
168 const struct table_border_style *borders,
169 enum pivot_border dim_col_horz,
170 enum pivot_border dim_col_vert,
171 enum pivot_border cat_col_horz,
172 enum pivot_border cat_col_vert,
173 const size_t *column_enumeration, size_t n_columns,
175 bool rotate_inner_labels, bool rotate_outer_labels)
177 const enum table_axis v = !h;
178 const int v_size = h_axis->label_depth;
179 const int h_ofs = v_axis->label_depth;
181 if (!h_axis->n_dimensions || !n_columns || !v_size)
184 const int stride = MAX (1, h_axis->n_dimensions);
186 /* Below, we're going to iterate through the dimensions. Each dimension
187 occupies one or more rows in the heading. 'top_row' is the top row of
188 these (and 'top_row + d->label_depth - 1' is the bottom row). */
191 /* We're going to iterate through dimensions and the rows that label them
192 from top to bottom (from outer to inner dimensions). As we move downward,
193 we start drawing vertical rules to separate categories and groups. After
194 we start drawing a vertical rule in a particular horizontal position, it
195 continues until the bottom of the heading. vrules[pos] indicates whether,
196 in our current row, we have already started drawing a vertical rule in
197 horizontal position 'pos'. (There are n_columns + 1 horizontal positions.
198 We allocate all of them for convenience below but only the inner n_columns
199 - 1 of them really matter.)
201 Here's an example that shows how vertical rules continue all the way
204 +-----------------------------------------------------+ __
206 +-----------------+-----------------+-----------------+ |dimension "bbbb"
207 | bbbb1 | bbbb2 | bbbb3 | _|
208 +-----------------+-----------------+-----------------+ __
209 | aaaa | aaaa | aaaa | |
210 +-----+-----+-----+-----+-----+-----+-----+-----+-----+ |dimension "aaaa"
211 |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3| _|
212 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
217 |___________________vrules[] indexes__________________|
219 Our data structures are more naturally iterated from bottom to top (inner
220 to outer dimensions). A previous version of this code actually worked
221 like that, but it didn't draw all of the vertical lines correctly as shown
222 above. It ended up rendering the above heading much like shown below,
223 which isn't what users expect. The "aaaa" label really needs to be shown
224 three times for clarity:
226 +-----------------------------------------------------+
228 +-----------------+-----------------+-----------------+
229 | bbbb1 | bbbb2 | bbbb3 |
230 +-----------------+-----------------+-----------------+
232 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
233 |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|
234 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
236 bool *vrules = xzalloc (n_columns + 1);
237 vrules[0] = vrules[n_columns] = true;
238 for (int dim_index = h_axis->n_dimensions; --dim_index >= 0; )
240 const struct pivot_dimension *d = h_axis->dimensions[dim_index];
241 if (d->hide_all_labels)
244 for (int row_ofs = 0; row_ofs < d->label_depth; row_ofs++)
246 for (size_t x1 = 0; x1 < n_columns;)
248 const struct pivot_category *c = find_category (
249 d, dim_index, column_enumeration + x1 * stride,
250 d->label_depth - row_ofs - 1);
258 for (x2 = x1 + 1; x2 < n_columns; x2++)
262 const struct pivot_category *c2 = find_category (
263 d, dim_index, column_enumeration + x2 * stride,
264 d->label_depth - row_ofs - 1);
269 int y1 = top_row + row_ofs;
270 int y2 = top_row + row_ofs + c->extra_depth + 1;
271 bool is_outer_row = y1 == 0;
272 bool is_inner_row = y2 == v_size;
273 if (pivot_category_is_leaf (c) || c->show_label)
275 int bb[TABLE_N_AXES][2];
276 bb[h][0] = x1 + h_ofs;
277 bb[h][1] = x2 + h_ofs - 1;
280 bool rotate = ((rotate_inner_labels && is_inner_row)
281 || (rotate_outer_labels && is_outer_row));
282 fill_cell (t, bb[H][0], bb[V][0], bb[H][1], bb[V][1],
283 label_style_idx, c->name, rotate);
285 /* Draw all the vertical lines in our running example, other
286 than the far left and far right ones. Only the ones that
287 start in the last row of the heading are drawn with the
288 "category" style, the rest with the "dimension" style,
289 e.g. only the # below are category style:
291 +-----------------------------------------------------+
293 +-----------------+-----------------+-----------------+
294 | bbbb1 | bbbb2 | bbbb3 |
295 +-----------------+-----------------+-----------------+
296 | aaaa | aaaa | aaaa |
297 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
298 |aaaa1#aaaa2#aaaa3|aaaa1#aaaa2#aaaa3|aaaa1#aaaa2#aaaa3|
299 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
301 enum pivot_border style
302 = (y1 == v_size - 1 ? cat_col_vert : dim_col_vert);
305 draw_line (t, borders, style, v, x2 + h_ofs, y1,
311 draw_line (t, borders, style, v, x1 + h_ofs, y1,
317 /* Draws the horizontal lines within a dimension, that is, those
318 that separate a separating a category (or group) from its
319 parent group or dimension's label. Our running example
320 doesn't have groups but the ==== lines below show the
321 separators between categories and their dimension label:
323 +-----------------------------------------------------+
325 +=================+=================+=================+
326 | bbbb1 | bbbb2 | bbbb3 |
327 +-----------------+-----------------+-----------------+
328 | aaaa | aaaa | aaaa |
329 +=====+=====+=====+=====+=====+=====+=====+=====+=====+
330 |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|
331 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
333 if (c->parent && c->parent->show_label)
334 draw_line (t, borders, cat_col_horz, h, y1,
335 x1 + h_ofs, x2 + h_ofs - 1);
340 if (d->root->show_label_in_corner && h_ofs > 0)
342 int bb[TABLE_N_AXES][2];
344 bb[h][1] = h_ofs - 1;
346 bb[v][1] = top_row + d->label_depth - 1;
347 fill_cell (t, bb[H][0], bb[V][0], bb[H][1], bb[V][1],
348 PIVOT_AREA_CORNER, d->root->name, false);
351 /* Draw the horizontal line between dimensions, e.g. the ===== line here:
353 +-----------------------------------------------------+ __
355 +-----------------+-----------------+-----------------+ |dim "bbbb"
356 | bbbb1 | bbbb2 | bbbb3 | _|
357 +=================+=================+=================+ __
358 | aaaa | aaaa | aaaa | |
359 +-----+-----+-----+-----+-----+-----+-----+-----+-----+ |dim "aaaa"
360 |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3| _|
361 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
363 if (dim_index != h_axis->n_dimensions - 1)
364 draw_line (t, borders, dim_col_horz, h, top_row, h_ofs,
366 top_row += d->label_depth;
371 static struct table *
372 create_aux_table (const struct pivot_table *pt, int nc, int nr,
375 struct table *table = table_create (nc, nr, 0, 0, 0, 0);
376 table->styles[style_idx] = table_area_style_override (
377 table->container, &pt->look->areas[style_idx], NULL, NULL, false);
383 add_references (const struct pivot_table *pt, const struct table *table,
384 bool *refs, size_t *n_refs)
389 for (int y = 0; y < table->n[V]; y++)
390 for (int x = 0; x < table->n[H]; )
392 struct table_cell cell;
393 table_get_cell (table, x, y, &cell);
395 if (x == cell.d[H][0] && y == cell.d[V][0])
397 for (size_t i = 0; i < cell.value->n_footnotes; i++)
399 size_t idx = cell.value->footnote_indexes[i];
400 assert (idx < pt->n_footnotes);
402 if (!refs[idx] && pt->footnotes[idx]->show)
410 x = cell.d[TABLE_HORZ][1];
414 static struct pivot_footnote **
415 collect_footnotes (const struct pivot_table *pt,
416 const struct table *title,
417 const struct table *layers,
418 const struct table *body,
419 const struct table *caption,
420 size_t *n_footnotesp)
422 if (!pt->n_footnotes)
428 bool *refs = xzalloc (pt->n_footnotes);
430 add_references (pt, title, refs, &n_refs);
431 add_references (pt, layers, refs, &n_refs);
432 add_references (pt, body, refs, &n_refs);
433 add_references (pt, caption, refs, &n_refs);
435 struct pivot_footnote **footnotes = xnmalloc (n_refs, sizeof *footnotes);
436 size_t n_footnotes = 0;
437 for (size_t i = 0; i < pt->n_footnotes; i++)
439 footnotes[n_footnotes++] = pt->footnotes[i];
440 assert (n_footnotes == n_refs);
444 *n_footnotesp = n_footnotes;
449 pivot_output (const struct pivot_table *pt,
450 const size_t *layer_indexes,
451 bool printing UNUSED,
452 struct table **titlep,
453 struct table **layersp,
454 struct table **bodyp,
455 struct table **captionp,
456 struct table **footnotesp,
457 struct pivot_footnote ***fp, size_t *nfp)
459 const size_t *pindexes[PIVOT_N_AXES]
460 = { [PIVOT_AXIS_LAYER] = layer_indexes };
462 size_t data[TABLE_N_AXES];
463 size_t *column_enumeration = pivot_table_enumerate_axis (
464 pt, PIVOT_AXIS_COLUMN, layer_indexes, pt->look->omit_empty, &data[H]);
465 size_t *row_enumeration = pivot_table_enumerate_axis (
466 pt, PIVOT_AXIS_ROW, layer_indexes, pt->look->omit_empty, &data[V]);
468 int stub[TABLE_N_AXES] = {
469 [H] = pt->axes[PIVOT_AXIS_ROW].label_depth,
470 [V] = pt->axes[PIVOT_AXIS_COLUMN].label_depth,
472 struct table *body = table_create (data[H] + stub[H],
474 stub[H], 0, stub[V], 0);
475 for (size_t i = 0; i < PIVOT_N_AREAS; i++)
476 body->styles[i] = table_area_style_override (
477 body->container, &pt->look->areas[i], NULL, NULL, false);
479 struct table_border_style borders[PIVOT_N_BORDERS];
480 memcpy (borders, pt->look->borders, sizeof borders);
481 if (!printing && pt->show_grid_lines)
482 for (int b = 0; b < PIVOT_N_BORDERS; b++)
483 if (borders[b].stroke == TABLE_STROKE_NONE)
484 borders[b].stroke = TABLE_STROKE_DASHED;
486 for (size_t i = 0; i < PIVOT_N_BORDERS; i++)
488 const struct table_border_style *in = &pt->look->borders[i];
489 body->rule_colors[i] = pool_alloc (body->container,
490 sizeof *body->rule_colors[i]);
491 *body->rule_colors[i] = in->color;
494 compose_headings (body,
495 &pt->axes[PIVOT_AXIS_COLUMN], H, &pt->axes[PIVOT_AXIS_ROW],
497 PIVOT_BORDER_DIM_COL_HORZ,
498 PIVOT_BORDER_DIM_COL_VERT,
499 PIVOT_BORDER_CAT_COL_HORZ,
500 PIVOT_BORDER_CAT_COL_VERT,
501 column_enumeration, data[H],
502 PIVOT_AREA_COLUMN_LABELS,
503 pt->rotate_outer_row_labels, false);
505 compose_headings (body,
506 &pt->axes[PIVOT_AXIS_ROW], V, &pt->axes[PIVOT_AXIS_COLUMN],
508 PIVOT_BORDER_DIM_ROW_VERT,
509 PIVOT_BORDER_DIM_ROW_HORZ,
510 PIVOT_BORDER_CAT_ROW_VERT,
511 PIVOT_BORDER_CAT_ROW_HORZ,
512 row_enumeration, data[V],
513 PIVOT_AREA_ROW_LABELS,
514 false, pt->rotate_inner_column_labels);
516 size_t *dindexes = XCALLOC (pt->n_dimensions, size_t);
518 PIVOT_ENUMERATION_FOR_EACH (pindexes[PIVOT_AXIS_ROW], row_enumeration,
519 &pt->axes[PIVOT_AXIS_ROW])
522 PIVOT_ENUMERATION_FOR_EACH (pindexes[PIVOT_AXIS_COLUMN],
524 &pt->axes[PIVOT_AXIS_COLUMN])
526 pivot_table_convert_indexes_ptod (pt, pindexes, dindexes);
527 const struct pivot_value *value = pivot_table_get (pt, dindexes);
528 fill_cell (body, x + stub[H], y + stub[V], x + stub[H], y + stub[V],
529 PIVOT_AREA_DATA, value, false);
538 if ((pt->corner_text || !pt->look->row_labels_in_corner)
539 && stub[H] && stub[V])
540 fill_cell (body, 0, 0, stub[H] - 1, stub[V] - 1,
541 PIVOT_AREA_CORNER, pt->corner_text, false);
543 if (body->n[H] && body->n[V])
546 body, get_table_rule (borders, PIVOT_BORDER_INNER_TOP),
547 0, body->n[H] - 1, 0);
549 body, get_table_rule (borders, PIVOT_BORDER_INNER_BOTTOM),
550 0, body->n[H] - 1, body->n[V]);
552 body, get_table_rule (borders, PIVOT_BORDER_INNER_LEFT),
553 0, 0, body->n[V] - 1);
555 body, get_table_rule (borders, PIVOT_BORDER_INNER_RIGHT),
556 body->n[H], 0, body->n[V] - 1);
560 body, get_table_rule (borders, PIVOT_BORDER_DATA_TOP),
561 0, body->n[H] - 1, stub[V]);
564 body, get_table_rule (borders, PIVOT_BORDER_DATA_LEFT),
565 stub[H], 0, body->n[V] - 1);
568 free (column_enumeration);
569 free (row_enumeration);
573 if (pt->title && pt->show_title && titlep)
575 title = create_aux_table (pt, 1, 1, PIVOT_AREA_TITLE);
576 fill_cell (title, 0, 0, 0, 0, PIVOT_AREA_TITLE, pt->title, false);
582 const struct pivot_axis *layer_axis = &pt->axes[PIVOT_AXIS_LAYER];
585 for (size_t i = 0; i < layer_axis->n_dimensions; i++)
587 const struct pivot_dimension *d = layer_axis->dimensions[i];
592 struct table *layers;
595 layers = create_aux_table (pt, 1, n_layers, PIVOT_AREA_LAYERS);
596 size_t y = n_layers - 1;
597 for (size_t i = 0; i < layer_axis->n_dimensions; i++)
599 const struct pivot_dimension *d = layer_axis->dimensions[i];
603 struct string s = DS_EMPTY_INITIALIZER;
604 pivot_value_format (d->data_leaves[layer_indexes[i]]->name, pt, &s);
605 fill_cell_owned (layers, 0, y, 0, y, PIVOT_AREA_LAYERS, &s, false);
613 struct table *caption;
614 if (pt->caption && pt->show_caption && captionp)
616 caption = create_aux_table (pt, 1, 1, PIVOT_AREA_CAPTION);
617 fill_cell (caption, 0, 0, 0, 0, PIVOT_AREA_CAPTION, pt->caption, false);
624 struct pivot_footnote **f = collect_footnotes (pt, title, layers, body,
626 struct table *footnotes;
627 if (nf && footnotesp)
629 footnotes = create_aux_table (pt, 1, nf, PIVOT_AREA_FOOTER);
631 for (size_t i = 0; i < nf; i++)
633 struct string s = DS_EMPTY_INITIALIZER;
634 pivot_footnote_format_marker (f[i], pt, &s);
635 ds_put_cstr (&s, ". ");
636 pivot_value_format (f[i]->content, pt, &s);
637 fill_cell_owned (footnotes, 0, i, 0, i, PIVOT_AREA_FOOTER, &s,
651 *footnotesp = footnotes;
662 pivot_table_submit (struct pivot_table *pt)
664 output_item_submit (table_item_create (pt));