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/page-eject-item.h"
27 #include "output/pivot-table.h"
28 #include "output/table-item.h"
29 #include "output/table-provider.h"
30 #include "output/table.h"
31 #include "output/text-item.h"
33 #include "gl/minmax.h"
34 #include "gl/xalloc.h"
40 pivot_output_next_layer (const struct pivot_table *pt, size_t *indexes,
43 const struct pivot_axis *layer_axis = &pt->axes[PIVOT_AXIS_LAYER];
44 if (print && pt->look->print_all_layers)
45 return pivot_axis_iterator_next (indexes, layer_axis);
48 size_t size = layer_axis->n_dimensions * sizeof *pt->current_layer;
49 return size ? xmemdup (pt->current_layer, size) : xmalloc (1);
58 static const struct pivot_category *
59 find_category (const struct pivot_dimension *d, int dim_index,
60 const size_t *indexes, int row_ofs)
62 size_t index = indexes[dim_index];
63 assert (index < d->n_leaves);
64 for (const struct pivot_category *c = d->presentation_leaves[index];
67 /* A category can cover multiple rows. Only return the category for its
69 if (row_ofs == c->extra_depth)
72 row_ofs -= 1 + c->extra_depth;
79 static struct table_area_style *
80 table_area_style_override (struct pool *pool,
81 const struct table_area_style *in,
82 const struct cell_style *cell_,
83 const struct font_style *font_,
86 const struct cell_style *cell = cell_ ? cell_ : &in->cell_style;
87 const struct font_style *font = font_ ? font_ : &in->font_style;
89 struct table_area_style *out = (pool
90 ? pool_alloc (pool, sizeof *out)
91 : xmalloc (sizeof *out));
92 *out = (struct table_area_style) {
93 .cell_style.halign = rotate_label ? TABLE_HALIGN_CENTER : cell->halign,
94 .cell_style.valign = rotate_label ? TABLE_VALIGN_CENTER : cell->valign,
95 .cell_style.decimal_offset = cell->decimal_offset,
96 .cell_style.margin[H][0] = cell->margin[H][0],
97 .cell_style.margin[H][1] = cell->margin[H][1],
98 .cell_style.margin[V][0] = cell->margin[V][0],
99 .cell_style.margin[V][1] = cell->margin[V][1],
100 .font_style.fg[0] = font->fg[0],
101 .font_style.fg[1] = font->fg[1],
102 .font_style.bg[0] = font->bg[0],
103 .font_style.bg[1] = font->bg[1],
104 .font_style.typeface = (font->typeface
105 ? pool_strdup (pool, font->typeface)
107 .font_style.size = font->size,
108 .font_style.bold = font->bold,
109 .font_style.italic = font->italic,
110 .font_style.underline = font->underline,
111 .font_style.markup = font->markup,
117 fill_cell (struct table *t, int x1, int y1, int x2, int y2,
118 int style_idx, const struct pivot_value *value,
121 int options = style_idx << TAB_STYLE_SHIFT;
123 options |= TAB_ROTATE;
125 table_put (t, x1, y1, x2, y2, options, value);
129 fill_cell_owned (struct table *t, int x1, int y1, int x2, int y2,
130 int style_idx, struct string *s, bool rotate_label)
132 int options = style_idx << TAB_STYLE_SHIFT;
134 options |= TAB_ROTATE;
136 table_put_owned (t, x1, y1, x2, y2, options,
137 pivot_value_new_user_text_nocopy (ds_steal_cstr (s)));
141 get_table_rule (const struct table_border_style *styles,
142 enum pivot_border style_idx)
144 return styles[style_idx].stroke | (style_idx << TAB_RULE_STYLE_SHIFT);
148 draw_line (struct table *t, const struct table_border_style *styles,
149 enum pivot_border style_idx,
150 enum table_axis axis, int a, int b0, int b1)
152 int rule = get_table_rule (styles, style_idx);
154 table_hline (t, rule, b0, b1, a);
156 table_vline (t, rule, a, b0, b1);
159 /* Fills row or column headings into T.
161 This function uses terminology and variable names for column headings, but
162 it also applies to row headings because it uses variables for the
163 differences, e.g. when for column headings it would use the H axis, it
164 instead uses 'h', which is set to H for column headings and V for row
167 compose_headings (struct table *t,
168 const struct pivot_axis *h_axis, enum table_axis h,
169 const struct pivot_axis *v_axis,
170 const struct table_border_style *borders,
171 enum pivot_border dim_col_horz,
172 enum pivot_border dim_col_vert,
173 enum pivot_border cat_col_horz,
174 enum pivot_border cat_col_vert,
175 const size_t *column_enumeration, size_t n_columns,
177 bool rotate_inner_labels, bool rotate_outer_labels)
179 const enum table_axis v = !h;
180 const int v_size = h_axis->label_depth;
181 const int h_ofs = v_axis->label_depth;
183 if (!h_axis->n_dimensions || !n_columns || !v_size)
186 const int stride = MAX (1, h_axis->n_dimensions);
188 /* Below, we're going to iterate through the dimensions. Each dimension
189 occupies one or more rows in the heading. 'top_row' is the top row of
190 these (and 'top_row + d->label_depth - 1' is the bottom row). */
193 /* We're going to iterate through dimensions and the rows that label them
194 from top to bottom (from outer to inner dimensions). As we move downward,
195 we start drawing vertical rules to separate categories and groups. After
196 we start drawing a vertical rule in a particular horizontal position, it
197 continues until the bottom of the heading. vrules[pos] indicates whether,
198 in our current row, we have already started drawing a vertical rule in
199 horizontal position 'pos'. (There are n_columns + 1 horizontal positions.
200 We allocate all of them for convenience below but only the inner n_columns
201 - 1 of them really matter.)
203 Here's an example that shows how vertical rules continue all the way
206 +-----------------------------------------------------+ __
208 +-----------------+-----------------+-----------------+ |dimension "bbbb"
209 | bbbb1 | bbbb2 | bbbb3 | _|
210 +-----------------+-----------------+-----------------+ __
211 | aaaa | aaaa | aaaa | |
212 +-----+-----+-----+-----+-----+-----+-----+-----+-----+ |dimension "aaaa"
213 |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3| _|
214 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
219 |___________________vrules[] indexes__________________|
221 Our data structures are more naturally iterated from bottom to top (inner
222 to outer dimensions). A previous version of this code actually worked
223 like that, but it didn't draw all of the vertical lines correctly as shown
224 above. It ended up rendering the above heading much like shown below,
225 which isn't what users expect. The "aaaa" label really needs to be shown
226 three times for clarity:
228 +-----------------------------------------------------+
230 +-----------------+-----------------+-----------------+
231 | bbbb1 | bbbb2 | bbbb3 |
232 +-----------------+-----------------+-----------------+
234 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
235 |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|
236 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
238 bool *vrules = xzalloc (n_columns + 1);
239 vrules[0] = vrules[n_columns] = true;
240 for (int dim_index = h_axis->n_dimensions; --dim_index >= 0; )
242 const struct pivot_dimension *d = h_axis->dimensions[dim_index];
243 if (d->hide_all_labels)
246 for (int row_ofs = 0; row_ofs < d->label_depth; row_ofs++)
248 for (size_t x1 = 0; x1 < n_columns;)
250 const struct pivot_category *c = find_category (
251 d, dim_index, column_enumeration + x1 * stride,
252 d->label_depth - row_ofs - 1);
260 for (x2 = x1 + 1; x2 < n_columns; x2++)
264 const struct pivot_category *c2 = find_category (
265 d, dim_index, column_enumeration + x2 * stride,
266 d->label_depth - row_ofs - 1);
271 int y1 = top_row + row_ofs;
272 int y2 = top_row + row_ofs + c->extra_depth + 1;
273 bool is_outer_row = y1 == 0;
274 bool is_inner_row = y2 == v_size;
275 if (pivot_category_is_leaf (c) || c->show_label)
277 int bb[TABLE_N_AXES][2];
278 bb[h][0] = x1 + h_ofs;
279 bb[h][1] = x2 + h_ofs - 1;
282 bool rotate = ((rotate_inner_labels && is_inner_row)
283 || (rotate_outer_labels && is_outer_row));
284 fill_cell (t, bb[H][0], bb[V][0], bb[H][1], bb[V][1],
285 label_style_idx, c->name, rotate);
287 /* Draw all the vertical lines in our running example, other
288 than the far left and far right ones. Only the ones that
289 start in the last row of the heading are drawn with the
290 "category" style, the rest with the "dimension" style,
291 e.g. only the # below are category style:
293 +-----------------------------------------------------+
295 +-----------------+-----------------+-----------------+
296 | bbbb1 | bbbb2 | bbbb3 |
297 +-----------------+-----------------+-----------------+
298 | aaaa | aaaa | aaaa |
299 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
300 |aaaa1#aaaa2#aaaa3|aaaa1#aaaa2#aaaa3|aaaa1#aaaa2#aaaa3|
301 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
303 enum pivot_border style
304 = (y1 == v_size - 1 ? cat_col_vert : dim_col_vert);
307 draw_line (t, borders, style, v, x2 + h_ofs, y1,
313 draw_line (t, borders, style, v, x1 + h_ofs, y1,
319 /* Draws the horizontal lines within a dimension, that is, those
320 that separate a separating a category (or group) from its
321 parent group or dimension's label. Our running example
322 doesn't have groups but the ==== lines below show the
323 separators between categories and their dimension label:
325 +-----------------------------------------------------+
327 +=================+=================+=================+
328 | bbbb1 | bbbb2 | bbbb3 |
329 +-----------------+-----------------+-----------------+
330 | aaaa | aaaa | aaaa |
331 +=====+=====+=====+=====+=====+=====+=====+=====+=====+
332 |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|
333 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
335 if (c->parent && c->parent->show_label)
336 draw_line (t, borders, cat_col_horz, h, y1,
337 x1 + h_ofs, x2 + h_ofs - 1);
342 if (d->root->show_label_in_corner && h_ofs > 0)
344 int bb[TABLE_N_AXES][2];
346 bb[h][1] = h_ofs - 1;
348 bb[v][1] = top_row + d->label_depth - 1;
349 fill_cell (t, bb[H][0], bb[V][0], bb[H][1], bb[V][1],
350 PIVOT_AREA_CORNER, d->root->name, false);
353 /* Draw the horizontal line between dimensions, e.g. the ===== line here:
355 +-----------------------------------------------------+ __
357 +-----------------+-----------------+-----------------+ |dim "bbbb"
358 | bbbb1 | bbbb2 | bbbb3 | _|
359 +=================+=================+=================+ __
360 | aaaa | aaaa | aaaa | |
361 +-----+-----+-----+-----+-----+-----+-----+-----+-----+ |dim "aaaa"
362 |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3| _|
363 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
365 if (dim_index != h_axis->n_dimensions - 1)
366 draw_line (t, borders, dim_col_horz, h, top_row, h_ofs,
368 top_row += d->label_depth;
373 static struct table *
374 create_aux_table (const struct pivot_table *pt, int nc, int nr,
377 struct table *table = table_create (nc, nr, 0, 0, 0, 0);
378 table->styles[style_idx] = table_area_style_override (
379 table->container, &pt->look->areas[style_idx], NULL, NULL, false);
385 add_references (const struct pivot_table *pt, const struct table *table,
386 bool *refs, size_t *n_refs)
391 for (int y = 0; y < table->n[V]; y++)
392 for (int x = 0; x < table->n[H]; )
394 struct table_cell cell;
395 table_get_cell (table, x, y, &cell);
397 if (x == cell.d[H][0] && y == cell.d[V][0])
399 for (size_t i = 0; i < cell.value->n_footnotes; i++)
401 size_t idx = cell.value->footnote_indexes[i];
402 assert (idx < pt->n_footnotes);
404 if (!refs[idx] && pt->footnotes[idx]->show)
412 x = cell.d[TABLE_HORZ][1];
416 static struct pivot_footnote **
417 collect_footnotes (const struct pivot_table *pt,
418 const struct table *title,
419 const struct table *layers,
420 const struct table *body,
421 const struct table *caption,
422 size_t *n_footnotesp)
424 if (!pt->n_footnotes)
430 bool *refs = xzalloc (pt->n_footnotes);
432 add_references (pt, title, refs, &n_refs);
433 add_references (pt, layers, refs, &n_refs);
434 add_references (pt, body, refs, &n_refs);
435 add_references (pt, caption, refs, &n_refs);
437 struct pivot_footnote **footnotes = xnmalloc (n_refs, sizeof *footnotes);
438 size_t n_footnotes = 0;
439 for (size_t i = 0; i < pt->n_footnotes; i++)
441 footnotes[n_footnotes++] = pt->footnotes[i];
442 assert (n_footnotes == n_refs);
446 *n_footnotesp = n_footnotes;
451 pivot_output (const struct pivot_table *pt,
452 const size_t *layer_indexes,
453 bool printing UNUSED,
454 struct table **titlep,
455 struct table **layersp,
456 struct table **bodyp,
457 struct table **captionp,
458 struct table **footnotesp,
459 struct pivot_footnote ***fp, size_t *nfp)
461 const size_t *pindexes[PIVOT_N_AXES]
462 = { [PIVOT_AXIS_LAYER] = layer_indexes };
464 size_t data[TABLE_N_AXES];
465 size_t *column_enumeration = pivot_table_enumerate_axis (
466 pt, PIVOT_AXIS_COLUMN, layer_indexes, pt->look->omit_empty, &data[H]);
467 size_t *row_enumeration = pivot_table_enumerate_axis (
468 pt, PIVOT_AXIS_ROW, layer_indexes, pt->look->omit_empty, &data[V]);
470 int stub[TABLE_N_AXES] = {
471 [H] = pt->axes[PIVOT_AXIS_ROW].label_depth,
472 [V] = pt->axes[PIVOT_AXIS_COLUMN].label_depth,
474 struct table *body = table_create (data[H] + stub[H],
476 stub[H], 0, stub[V], 0);
477 for (size_t i = 0; i < PIVOT_N_AREAS; i++)
478 body->styles[i] = table_area_style_override (
479 body->container, &pt->look->areas[i], NULL, NULL, false);
481 struct table_border_style borders[PIVOT_N_BORDERS];
482 memcpy (borders, pt->look->borders, sizeof borders);
483 if (!printing && pt->show_grid_lines)
484 for (int b = 0; b < PIVOT_N_BORDERS; b++)
485 if (borders[b].stroke == TABLE_STROKE_NONE)
486 borders[b].stroke = TABLE_STROKE_DASHED;
488 for (size_t i = 0; i < PIVOT_N_BORDERS; i++)
490 const struct table_border_style *in = &pt->look->borders[i];
491 body->rule_colors[i] = pool_alloc (body->container,
492 sizeof *body->rule_colors[i]);
493 *body->rule_colors[i] = in->color;
496 compose_headings (body,
497 &pt->axes[PIVOT_AXIS_COLUMN], H, &pt->axes[PIVOT_AXIS_ROW],
499 PIVOT_BORDER_DIM_COL_HORZ,
500 PIVOT_BORDER_DIM_COL_VERT,
501 PIVOT_BORDER_CAT_COL_HORZ,
502 PIVOT_BORDER_CAT_COL_VERT,
503 column_enumeration, data[H],
504 PIVOT_AREA_COLUMN_LABELS,
505 pt->rotate_outer_row_labels, false);
507 compose_headings (body,
508 &pt->axes[PIVOT_AXIS_ROW], V, &pt->axes[PIVOT_AXIS_COLUMN],
510 PIVOT_BORDER_DIM_ROW_VERT,
511 PIVOT_BORDER_DIM_ROW_HORZ,
512 PIVOT_BORDER_CAT_ROW_VERT,
513 PIVOT_BORDER_CAT_ROW_HORZ,
514 row_enumeration, data[V],
515 PIVOT_AREA_ROW_LABELS,
516 false, pt->rotate_inner_column_labels);
518 size_t *dindexes = XCALLOC (pt->n_dimensions, size_t);
520 PIVOT_ENUMERATION_FOR_EACH (pindexes[PIVOT_AXIS_ROW], row_enumeration,
521 &pt->axes[PIVOT_AXIS_ROW])
524 PIVOT_ENUMERATION_FOR_EACH (pindexes[PIVOT_AXIS_COLUMN],
526 &pt->axes[PIVOT_AXIS_COLUMN])
528 pivot_table_convert_indexes_ptod (pt, pindexes, dindexes);
529 const struct pivot_value *value = pivot_table_get (pt, dindexes);
530 fill_cell (body, x + stub[H], y + stub[V], x + stub[H], y + stub[V],
531 PIVOT_AREA_DATA, value, false);
540 if ((pt->corner_text || !pt->look->row_labels_in_corner)
541 && stub[H] && stub[V])
542 fill_cell (body, 0, 0, stub[H] - 1, stub[V] - 1,
543 PIVOT_AREA_CORNER, pt->corner_text, false);
545 if (body->n[H] && body->n[V])
548 body, get_table_rule (borders, PIVOT_BORDER_INNER_TOP),
549 0, body->n[H] - 1, 0);
551 body, get_table_rule (borders, PIVOT_BORDER_INNER_BOTTOM),
552 0, body->n[H] - 1, body->n[V]);
554 body, get_table_rule (borders, PIVOT_BORDER_INNER_LEFT),
555 0, 0, body->n[V] - 1);
557 body, get_table_rule (borders, PIVOT_BORDER_INNER_RIGHT),
558 body->n[H], 0, body->n[V] - 1);
562 body, get_table_rule (borders, PIVOT_BORDER_DATA_TOP),
563 0, body->n[H] - 1, stub[V]);
566 body, get_table_rule (borders, PIVOT_BORDER_DATA_LEFT),
567 stub[H], 0, body->n[V] - 1);
570 free (column_enumeration);
571 free (row_enumeration);
575 if (pt->title && pt->show_title && titlep)
577 title = create_aux_table (pt, 1, 1, PIVOT_AREA_TITLE);
578 fill_cell (title, 0, 0, 0, 0, PIVOT_AREA_TITLE, pt->title, false);
584 const struct pivot_axis *layer_axis = &pt->axes[PIVOT_AXIS_LAYER];
587 for (size_t i = 0; i < layer_axis->n_dimensions; i++)
589 const struct pivot_dimension *d = layer_axis->dimensions[i];
594 struct table *layers;
597 layers = create_aux_table (pt, 1, n_layers, PIVOT_AREA_LAYERS);
598 size_t y = n_layers - 1;
599 for (size_t i = 0; i < layer_axis->n_dimensions; i++)
601 const struct pivot_dimension *d = layer_axis->dimensions[i];
605 struct string s = DS_EMPTY_INITIALIZER;
606 pivot_value_format (d->data_leaves[layer_indexes[i]]->name, pt, &s);
607 fill_cell_owned (layers, 0, y, 0, y, PIVOT_AREA_LAYERS, &s, false);
615 struct table *caption;
616 if (pt->caption && pt->show_caption && captionp)
618 caption = create_aux_table (pt, 1, 1, PIVOT_AREA_CAPTION);
619 fill_cell (caption, 0, 0, 0, 0, PIVOT_AREA_CAPTION, pt->caption, false);
626 struct pivot_footnote **f = collect_footnotes (pt, title, layers, body,
628 struct table *footnotes;
629 if (nf && footnotesp)
631 footnotes = create_aux_table (pt, 1, nf, PIVOT_AREA_FOOTER);
633 for (size_t i = 0; i < nf; i++)
635 struct string s = DS_EMPTY_INITIALIZER;
636 pivot_footnote_format_marker (f[i], pt, &s);
637 ds_put_cstr (&s, ". ");
638 pivot_value_format (f[i]->content, pt, &s);
639 fill_cell_owned (footnotes, 0, i, 0, i, PIVOT_AREA_FOOTER, &s,
653 *footnotesp = footnotes;
664 pivot_table_submit (struct pivot_table *pt)
666 table_item_submit (table_item_create (pt));