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/pivot-table.h"
27 #include "output/table-item.h"
28 #include "output/table-provider.h"
29 #include "output/table.h"
30 #include "output/text-item.h"
32 #include "gl/minmax.h"
33 #include "gl/xalloc.h"
39 pivot_output_next_layer (const struct pivot_table *pt, size_t *indexes,
42 const struct pivot_axis *layer_axis = &pt->axes[PIVOT_AXIS_LAYER];
43 if (print && pt->look->print_all_layers)
44 return pivot_axis_iterator_next (indexes, layer_axis);
47 size_t size = layer_axis->n_dimensions * sizeof *pt->current_layer;
48 return size ? xmemdup (pt->current_layer, size) : xmalloc (1);
57 static const struct pivot_category *
58 find_category (const struct pivot_dimension *d, int dim_index,
59 const size_t *indexes, int row_ofs)
61 size_t index = indexes[dim_index];
62 assert (index < d->n_leaves);
63 for (const struct pivot_category *c = d->presentation_leaves[index];
66 /* A category can cover multiple rows. Only return the category for its
68 if (row_ofs == c->extra_depth)
71 row_ofs -= 1 + c->extra_depth;
78 static struct table_area_style *
79 table_area_style_override (struct pool *pool,
80 const struct table_area_style *in,
81 const struct cell_style *cell_,
82 const struct font_style *font_,
85 const struct cell_style *cell = cell_ ? cell_ : &in->cell_style;
86 const struct font_style *font = font_ ? font_ : &in->font_style;
88 struct table_area_style *out = (pool
89 ? pool_alloc (pool, sizeof *out)
90 : xmalloc (sizeof *out));
91 *out = (struct table_area_style) {
92 .cell_style.halign = rotate_label ? TABLE_HALIGN_CENTER : cell->halign,
93 .cell_style.valign = rotate_label ? TABLE_VALIGN_CENTER : cell->valign,
94 .cell_style.decimal_offset = cell->decimal_offset,
95 .cell_style.margin[H][0] = cell->margin[H][0],
96 .cell_style.margin[H][1] = cell->margin[H][1],
97 .cell_style.margin[V][0] = cell->margin[V][0],
98 .cell_style.margin[V][1] = cell->margin[V][1],
99 .font_style.fg[0] = font->fg[0],
100 .font_style.fg[1] = font->fg[1],
101 .font_style.bg[0] = font->bg[0],
102 .font_style.bg[1] = font->bg[1],
103 .font_style.typeface = (font->typeface
104 ? pool_strdup (pool, font->typeface)
106 .font_style.size = font->size,
107 .font_style.bold = font->bold,
108 .font_style.italic = font->italic,
109 .font_style.underline = font->underline,
110 .font_style.markup = font->markup,
116 fill_cell (struct table *t, int x1, int y1, int x2, int y2,
117 int style_idx, const struct pivot_value *value,
120 int options = style_idx << TAB_STYLE_SHIFT;
122 options |= TAB_ROTATE;
124 table_put (t, x1, y1, x2, y2, options, value);
128 fill_cell_owned (struct table *t, int x1, int y1, int x2, int y2,
129 int style_idx, struct string *s, bool rotate_label)
131 int options = style_idx << TAB_STYLE_SHIFT;
133 options |= TAB_ROTATE;
135 table_put_owned (t, x1, y1, x2, y2, options,
136 pivot_value_new_user_text_nocopy (ds_steal_cstr (s)));
140 get_table_rule (const struct table_border_style *styles,
141 enum pivot_border style_idx)
143 return styles[style_idx].stroke | (style_idx << TAB_RULE_STYLE_SHIFT);
147 draw_line (struct table *t, const struct table_border_style *styles,
148 enum pivot_border style_idx,
149 enum table_axis axis, int a, int b0, int b1)
151 int rule = get_table_rule (styles, style_idx);
153 table_hline (t, rule, b0, b1, a);
155 table_vline (t, rule, a, b0, b1);
158 /* Fills row or column headings into T.
160 This function uses terminology and variable names for column headings, but
161 it also applies to row headings because it uses variables for the
162 differences, e.g. when for column headings it would use the H axis, it
163 instead uses 'h', which is set to H for column headings and V for row
166 compose_headings (struct table *t,
167 const struct pivot_axis *h_axis, enum table_axis h,
168 const struct pivot_axis *v_axis,
169 const struct table_border_style *borders,
170 enum pivot_border dim_col_horz,
171 enum pivot_border dim_col_vert,
172 enum pivot_border cat_col_horz,
173 enum pivot_border cat_col_vert,
174 const size_t *column_enumeration, size_t n_columns,
176 bool rotate_inner_labels, bool rotate_outer_labels)
178 const enum table_axis v = !h;
179 const int v_size = h_axis->label_depth;
180 const int h_ofs = v_axis->label_depth;
182 if (!h_axis->n_dimensions || !n_columns || !v_size)
185 const int stride = MAX (1, h_axis->n_dimensions);
187 /* Below, we're going to iterate through the dimensions. Each dimension
188 occupies one or more rows in the heading. 'top_row' is the top row of
189 these (and 'top_row + d->label_depth - 1' is the bottom row). */
192 /* We're going to iterate through dimensions and the rows that label them
193 from top to bottom (from outer to inner dimensions). As we move downward,
194 we start drawing vertical rules to separate categories and groups. After
195 we start drawing a vertical rule in a particular horizontal position, it
196 continues until the bottom of the heading. vrules[pos] indicates whether,
197 in our current row, we have already started drawing a vertical rule in
198 horizontal position 'pos'. (There are n_columns + 1 horizontal positions.
199 We allocate all of them for convenience below but only the inner n_columns
200 - 1 of them really matter.)
202 Here's an example that shows how vertical rules continue all the way
205 +-----------------------------------------------------+ __
207 +-----------------+-----------------+-----------------+ |dimension "bbbb"
208 | bbbb1 | bbbb2 | bbbb3 | _|
209 +-----------------+-----------------+-----------------+ __
210 | aaaa | aaaa | aaaa | |
211 +-----+-----+-----+-----+-----+-----+-----+-----+-----+ |dimension "aaaa"
212 |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3| _|
213 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
218 |___________________vrules[] indexes__________________|
220 Our data structures are more naturally iterated from bottom to top (inner
221 to outer dimensions). A previous version of this code actually worked
222 like that, but it didn't draw all of the vertical lines correctly as shown
223 above. It ended up rendering the above heading much like shown below,
224 which isn't what users expect. The "aaaa" label really needs to be shown
225 three times for clarity:
227 +-----------------------------------------------------+
229 +-----------------+-----------------+-----------------+
230 | bbbb1 | bbbb2 | bbbb3 |
231 +-----------------+-----------------+-----------------+
233 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
234 |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|
235 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
237 bool *vrules = xzalloc (n_columns + 1);
238 vrules[0] = vrules[n_columns] = true;
239 for (int dim_index = h_axis->n_dimensions; --dim_index >= 0; )
241 const struct pivot_dimension *d = h_axis->dimensions[dim_index];
242 if (d->hide_all_labels)
245 for (int row_ofs = 0; row_ofs < d->label_depth; row_ofs++)
247 for (size_t x1 = 0; x1 < n_columns;)
249 const struct pivot_category *c = find_category (
250 d, dim_index, column_enumeration + x1 * stride,
251 d->label_depth - row_ofs - 1);
259 for (x2 = x1 + 1; x2 < n_columns; x2++)
263 const struct pivot_category *c2 = find_category (
264 d, dim_index, column_enumeration + x2 * stride,
265 d->label_depth - row_ofs - 1);
270 int y1 = top_row + row_ofs;
271 int y2 = top_row + row_ofs + c->extra_depth + 1;
272 bool is_outer_row = y1 == 0;
273 bool is_inner_row = y2 == v_size;
274 if (pivot_category_is_leaf (c) || c->show_label)
276 int bb[TABLE_N_AXES][2];
277 bb[h][0] = x1 + h_ofs;
278 bb[h][1] = x2 + h_ofs - 1;
281 bool rotate = ((rotate_inner_labels && is_inner_row)
282 || (rotate_outer_labels && is_outer_row));
283 fill_cell (t, bb[H][0], bb[V][0], bb[H][1], bb[V][1],
284 label_style_idx, c->name, rotate);
286 /* Draw all the vertical lines in our running example, other
287 than the far left and far right ones. Only the ones that
288 start in the last row of the heading are drawn with the
289 "category" style, the rest with the "dimension" style,
290 e.g. only the # below are category style:
292 +-----------------------------------------------------+
294 +-----------------+-----------------+-----------------+
295 | bbbb1 | bbbb2 | bbbb3 |
296 +-----------------+-----------------+-----------------+
297 | aaaa | aaaa | aaaa |
298 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
299 |aaaa1#aaaa2#aaaa3|aaaa1#aaaa2#aaaa3|aaaa1#aaaa2#aaaa3|
300 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
302 enum pivot_border style
303 = (y1 == v_size - 1 ? cat_col_vert : dim_col_vert);
306 draw_line (t, borders, style, v, x2 + h_ofs, y1,
312 draw_line (t, borders, style, v, x1 + h_ofs, y1,
318 /* Draws the horizontal lines within a dimension, that is, those
319 that separate a separating a category (or group) from its
320 parent group or dimension's label. Our running example
321 doesn't have groups but the ==== lines below show the
322 separators between categories and their dimension label:
324 +-----------------------------------------------------+
326 +=================+=================+=================+
327 | bbbb1 | bbbb2 | bbbb3 |
328 +-----------------+-----------------+-----------------+
329 | aaaa | aaaa | aaaa |
330 +=====+=====+=====+=====+=====+=====+=====+=====+=====+
331 |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|
332 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
334 if (c->parent && c->parent->show_label)
335 draw_line (t, borders, cat_col_horz, h, y1,
336 x1 + h_ofs, x2 + h_ofs - 1);
341 if (d->root->show_label_in_corner && h_ofs > 0)
343 int bb[TABLE_N_AXES][2];
345 bb[h][1] = h_ofs - 1;
347 bb[v][1] = top_row + d->label_depth - 1;
348 fill_cell (t, bb[H][0], bb[V][0], bb[H][1], bb[V][1],
349 PIVOT_AREA_CORNER, d->root->name, false);
352 /* Draw the horizontal line between dimensions, e.g. the ===== line here:
354 +-----------------------------------------------------+ __
356 +-----------------+-----------------+-----------------+ |dim "bbbb"
357 | bbbb1 | bbbb2 | bbbb3 | _|
358 +=================+=================+=================+ __
359 | aaaa | aaaa | aaaa | |
360 +-----+-----+-----+-----+-----+-----+-----+-----+-----+ |dim "aaaa"
361 |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3| _|
362 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
364 if (dim_index != h_axis->n_dimensions - 1)
365 draw_line (t, borders, dim_col_horz, h, top_row, h_ofs,
367 top_row += d->label_depth;
372 static struct table *
373 create_aux_table (const struct pivot_table *pt, int nc, int nr,
376 struct table *table = table_create (nc, nr, 0, 0, 0, 0);
377 table->styles[style_idx] = table_area_style_override (
378 table->container, &pt->look->areas[style_idx], NULL, NULL, false);
384 add_references (const struct pivot_table *pt, const struct table *table,
385 bool *refs, size_t *n_refs)
390 for (int y = 0; y < table->n[V]; y++)
391 for (int x = 0; x < table->n[H]; )
393 struct table_cell cell;
394 table_get_cell (table, x, y, &cell);
396 if (x == cell.d[H][0] && y == cell.d[V][0])
398 for (size_t i = 0; i < cell.value->n_footnotes; i++)
400 size_t idx = cell.value->footnote_indexes[i];
401 assert (idx < pt->n_footnotes);
403 if (!refs[idx] && pt->footnotes[idx]->show)
411 x = cell.d[TABLE_HORZ][1];
415 static struct pivot_footnote **
416 collect_footnotes (const struct pivot_table *pt,
417 const struct table *title,
418 const struct table *layers,
419 const struct table *body,
420 const struct table *caption,
421 size_t *n_footnotesp)
423 if (!pt->n_footnotes)
429 bool *refs = xzalloc (pt->n_footnotes);
431 add_references (pt, title, refs, &n_refs);
432 add_references (pt, layers, refs, &n_refs);
433 add_references (pt, body, refs, &n_refs);
434 add_references (pt, caption, refs, &n_refs);
436 struct pivot_footnote **footnotes = xnmalloc (n_refs, sizeof *footnotes);
437 size_t n_footnotes = 0;
438 for (size_t i = 0; i < pt->n_footnotes; i++)
440 footnotes[n_footnotes++] = pt->footnotes[i];
441 assert (n_footnotes == n_refs);
445 *n_footnotesp = n_footnotes;
450 pivot_output (const struct pivot_table *pt,
451 const size_t *layer_indexes,
452 bool printing UNUSED,
453 struct table **titlep,
454 struct table **layersp,
455 struct table **bodyp,
456 struct table **captionp,
457 struct table **footnotesp,
458 struct pivot_footnote ***fp, size_t *nfp)
460 const size_t *pindexes[PIVOT_N_AXES]
461 = { [PIVOT_AXIS_LAYER] = layer_indexes };
463 size_t data[TABLE_N_AXES];
464 size_t *column_enumeration = pivot_table_enumerate_axis (
465 pt, PIVOT_AXIS_COLUMN, layer_indexes, pt->look->omit_empty, &data[H]);
466 size_t *row_enumeration = pivot_table_enumerate_axis (
467 pt, PIVOT_AXIS_ROW, layer_indexes, pt->look->omit_empty, &data[V]);
469 int stub[TABLE_N_AXES] = {
470 [H] = pt->axes[PIVOT_AXIS_ROW].label_depth,
471 [V] = pt->axes[PIVOT_AXIS_COLUMN].label_depth,
473 struct table *body = table_create (data[H] + stub[H],
475 stub[H], 0, stub[V], 0);
476 for (size_t i = 0; i < PIVOT_N_AREAS; i++)
477 body->styles[i] = table_area_style_override (
478 body->container, &pt->look->areas[i], NULL, NULL, false);
480 struct table_border_style borders[PIVOT_N_BORDERS];
481 memcpy (borders, pt->look->borders, sizeof borders);
482 if (!printing && pt->show_grid_lines)
483 for (int b = 0; b < PIVOT_N_BORDERS; b++)
484 if (borders[b].stroke == TABLE_STROKE_NONE)
485 borders[b].stroke = TABLE_STROKE_DASHED;
487 for (size_t i = 0; i < PIVOT_N_BORDERS; i++)
489 const struct table_border_style *in = &pt->look->borders[i];
490 body->rule_colors[i] = pool_alloc (body->container,
491 sizeof *body->rule_colors[i]);
492 *body->rule_colors[i] = in->color;
495 compose_headings (body,
496 &pt->axes[PIVOT_AXIS_COLUMN], H, &pt->axes[PIVOT_AXIS_ROW],
498 PIVOT_BORDER_DIM_COL_HORZ,
499 PIVOT_BORDER_DIM_COL_VERT,
500 PIVOT_BORDER_CAT_COL_HORZ,
501 PIVOT_BORDER_CAT_COL_VERT,
502 column_enumeration, data[H],
503 PIVOT_AREA_COLUMN_LABELS,
504 pt->rotate_outer_row_labels, false);
506 compose_headings (body,
507 &pt->axes[PIVOT_AXIS_ROW], V, &pt->axes[PIVOT_AXIS_COLUMN],
509 PIVOT_BORDER_DIM_ROW_VERT,
510 PIVOT_BORDER_DIM_ROW_HORZ,
511 PIVOT_BORDER_CAT_ROW_VERT,
512 PIVOT_BORDER_CAT_ROW_HORZ,
513 row_enumeration, data[V],
514 PIVOT_AREA_ROW_LABELS,
515 false, pt->rotate_inner_column_labels);
517 size_t *dindexes = XCALLOC (pt->n_dimensions, size_t);
519 PIVOT_ENUMERATION_FOR_EACH (pindexes[PIVOT_AXIS_ROW], row_enumeration,
520 &pt->axes[PIVOT_AXIS_ROW])
523 PIVOT_ENUMERATION_FOR_EACH (pindexes[PIVOT_AXIS_COLUMN],
525 &pt->axes[PIVOT_AXIS_COLUMN])
527 pivot_table_convert_indexes_ptod (pt, pindexes, dindexes);
528 const struct pivot_value *value = pivot_table_get (pt, dindexes);
529 fill_cell (body, x + stub[H], y + stub[V], x + stub[H], y + stub[V],
530 PIVOT_AREA_DATA, value, false);
539 if ((pt->corner_text || !pt->look->row_labels_in_corner)
540 && stub[H] && stub[V])
541 fill_cell (body, 0, 0, stub[H] - 1, stub[V] - 1,
542 PIVOT_AREA_CORNER, pt->corner_text, false);
544 if (body->n[H] && body->n[V])
547 body, get_table_rule (borders, PIVOT_BORDER_INNER_TOP),
548 0, body->n[H] - 1, 0);
550 body, get_table_rule (borders, PIVOT_BORDER_INNER_BOTTOM),
551 0, body->n[H] - 1, body->n[V]);
553 body, get_table_rule (borders, PIVOT_BORDER_INNER_LEFT),
554 0, 0, body->n[V] - 1);
556 body, get_table_rule (borders, PIVOT_BORDER_INNER_RIGHT),
557 body->n[H], 0, body->n[V] - 1);
561 body, get_table_rule (borders, PIVOT_BORDER_DATA_TOP),
562 0, body->n[H] - 1, stub[V]);
565 body, get_table_rule (borders, PIVOT_BORDER_DATA_LEFT),
566 stub[H], 0, body->n[V] - 1);
569 free (column_enumeration);
570 free (row_enumeration);
574 if (pt->title && pt->show_title && titlep)
576 title = create_aux_table (pt, 1, 1, PIVOT_AREA_TITLE);
577 fill_cell (title, 0, 0, 0, 0, PIVOT_AREA_TITLE, pt->title, false);
583 const struct pivot_axis *layer_axis = &pt->axes[PIVOT_AXIS_LAYER];
586 for (size_t i = 0; i < layer_axis->n_dimensions; i++)
588 const struct pivot_dimension *d = layer_axis->dimensions[i];
593 struct table *layers;
596 layers = create_aux_table (pt, 1, n_layers, PIVOT_AREA_LAYERS);
597 size_t y = n_layers - 1;
598 for (size_t i = 0; i < layer_axis->n_dimensions; i++)
600 const struct pivot_dimension *d = layer_axis->dimensions[i];
604 struct string s = DS_EMPTY_INITIALIZER;
605 pivot_value_format (d->data_leaves[layer_indexes[i]]->name, pt, &s);
606 fill_cell_owned (layers, 0, y, 0, y, PIVOT_AREA_LAYERS, &s, false);
614 struct table *caption;
615 if (pt->caption && pt->show_caption && captionp)
617 caption = create_aux_table (pt, 1, 1, PIVOT_AREA_CAPTION);
618 fill_cell (caption, 0, 0, 0, 0, PIVOT_AREA_CAPTION, pt->caption, false);
625 struct pivot_footnote **f = collect_footnotes (pt, title, layers, body,
627 struct table *footnotes;
628 if (nf && footnotesp)
630 footnotes = create_aux_table (pt, 1, nf, PIVOT_AREA_FOOTER);
632 for (size_t i = 0; i < nf; i++)
634 struct string s = DS_EMPTY_INITIALIZER;
635 pivot_footnote_format_marker (f[i], pt, &s);
636 ds_put_cstr (&s, ". ");
637 pivot_value_format (f[i]->content, pt, &s);
638 fill_cell_owned (footnotes, 0, i, 0, i, PIVOT_AREA_FOOTER, &s,
652 *footnotesp = footnotes;
663 pivot_table_submit (struct pivot_table *pt)
665 table_item_submit (table_item_create (pt));