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-table.h"
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"
32 #include "gl/minmax.h"
33 #include "gl/xalloc.h"
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)
42 size_t index = indexes[dim_index];
43 assert (index < d->n_leaves);
44 for (const struct pivot_category *c = d->presentation_leaves[index];
47 /* A category can covert multiple rows. Only return the category for its
49 if (row_ofs == c->extra_depth)
52 row_ofs -= 1 + c->extra_depth;
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_,
66 const struct cell_style *cell = cell_ ? cell_ : &in->cell_style;
67 const struct font_style *font = font_ ? font_ : &in->font_style;
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)
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,
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,
105 struct string s = DS_EMPTY_INITIALIZER;
106 int opts = style_idx << TAB_STYLE_SHIFT;
109 bool numeric = pivot_value_format_body (value, show_values,
113 if (value->font_style && value->font_style->markup)
118 table_joint_text (t, x1, y1, x2, y2, opts, ds_cstr (&s));
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,
130 for (size_t i = 0; i < value->n_footnotes; i++)
132 struct footnote *f = footnotes[value->footnotes[i]->idx];
134 table_add_footnote (t, x1, y1, f);
137 if (value->n_subscripts)
138 table_add_subscripts (t, x1, y1,
139 value->subscripts, value->n_subscripts);
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)
153 struct string s = DS_EMPTY_INITIALIZER;
154 pivot_value_format_body (value, show_values, show_variables, &s);
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),
164 for (size_t i = 0; i < value->n_footnotes; i++)
166 struct footnote *f = footnotes[value->footnotes[i]->idx];
168 text->footnotes[text->n_footnotes++] = f;
175 get_table_rule (const struct table_border_style *styles,
176 enum pivot_border style_idx)
178 return styles[style_idx].stroke | (style_idx << TAB_RULE_STYLE_SHIFT);
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)
186 int rule = get_table_rule (styles, style_idx);
188 table_hline (t, rule, b0, b1, a);
190 table_vline (t, rule, a, b0, b1);
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,
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)
211 enum table_axis b = !a;
212 int b_size = a_axis->label_depth;
213 int a_ofs = b_axis->label_depth;
215 if (!a_axis->n_dimensions || !n_columns || !b_size)
218 const int stride = MAX (1, a_axis->n_dimensions);
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). */
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.)
235 Here's an example that shows how vertical rules continue all the way
238 +-----------------------------------------------------+ __
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 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
251 |___________________vrules[] indexes__________________|
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:
260 +-----------------------------------------------------+
262 +-----------------+-----------------+-----------------+
263 | bbbb1 | bbbb2 | bbbb3 |
264 +-----------------+-----------------+-----------------+
266 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
267 |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|
268 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
270 bool *vrules = xzalloc (n_columns + 1);
271 for (int dim_index = a_axis->n_dimensions; --dim_index >= 0; )
273 const struct pivot_dimension *d = a_axis->dimensions[dim_index];
274 if (d->hide_all_labels)
277 for (int row_ofs = 0; row_ofs < d->label_depth; row_ofs++)
279 for (size_t x1 = 0; x1 < n_columns;)
281 const struct pivot_category *c = find_category (
282 d, dim_index, column_enumeration + x1 * stride,
283 d->label_depth - row_ofs - 1);
291 for (x2 = x1 + 1; x2 < n_columns; x2++)
295 const struct pivot_category *c2 = find_category (
296 d, dim_index, column_enumeration + x2 * stride,
297 d->label_depth - row_ofs - 1);
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)
308 int bb[TABLE_N_AXES][2];
309 bb[a][0] = x1 + a_ofs;
310 bb[a][1] = x2 + a_ofs - 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);
319 if (pivot_category_is_leaf (c) && x2 + 1 <= n_columns)
321 enum pivot_border style
322 = (y1 == 0 && a_axis->label_depth > d->label_depth
325 draw_line (t, borders, style, b, x2 + a_ofs, y1,
329 if (pivot_category_is_leaf (c) && x1 > 0)
331 enum pivot_border style
332 = (y1 == 0 && a_axis->label_depth > d->label_depth
335 draw_line (t, borders, style, b, x1 + a_ofs, y1,
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);
347 if (d->root->show_label_in_corner && a_ofs > 0)
349 int bb[TABLE_N_AXES][2];
351 bb[a][1] = a_ofs - 1;
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);
359 /* Draw the horizontal line between dimensions, e.g. the ===== line here:
361 +-----------------------------------------------------+ __
363 +-----------------+-----------------+-----------------+ |dim "bbbb"
364 | bbbb1 | bbbb2 | bbbb3 | _|
365 +=================+=================+=================+ __
366 | aaaa | aaaa | aaaa | |
367 +-----+-----+-----+-----+-----+-----+-----+-----+-----+ |dim "aaaa"
368 |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3| _|
369 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
371 if (dim_index != a_axis->n_dimensions - 1)
372 draw_line (t, borders, dim_col_horz, a, top_row, a_ofs,
374 top_row += d->label_depth;
380 pivot_table_submit_layer (const struct pivot_table *pt,
381 const size_t *layer_indexes)
383 const size_t *pindexes[PIVOT_N_AXES]
384 = { [PIVOT_AXIS_LAYER] = layer_indexes };
386 size_t body[TABLE_N_AXES];
387 size_t *column_enumeration = pivot_table_enumerate_axis (
388 pt, PIVOT_AXIS_COLUMN, layer_indexes, pt->look->omit_empty, &body[H]);
389 size_t *row_enumeration = pivot_table_enumerate_axis (
390 pt, PIVOT_AXIS_ROW, layer_indexes, pt->look->omit_empty, &body[V]);
392 int stub[TABLE_N_AXES] = {
393 [H] = pt->axes[PIVOT_AXIS_ROW].label_depth,
394 [V] = pt->axes[PIVOT_AXIS_COLUMN].label_depth,
396 struct table *table = table_create (body[H] + stub[H],
398 stub[H], 0, stub[V], 0);
400 for (size_t i = 0; i < PIVOT_N_AREAS; i++)
401 table->styles[i] = table_area_style_override (
402 table->container, &pt->look->areas[i], NULL, NULL, false);
404 for (size_t i = 0; i < PIVOT_N_BORDERS; i++)
406 const struct table_border_style *in = &pt->look->borders[i];
407 table->rule_colors[i] = pool_alloc (table->container,
408 sizeof *table->rule_colors[i]);
409 struct cell_color *out = table->rule_colors[i];
410 out->alpha = in->color.alpha;
411 out->r = in->color.r;
412 out->g = in->color.g;
413 out->b = in->color.b;
416 struct footnote **footnotes = XCALLOC (pt->n_footnotes, struct footnote *);
417 for (size_t i = 0; i < pt->n_footnotes; i++)
419 const struct pivot_footnote *pf = pt->footnotes[i];
424 char *content = pivot_value_to_string (pf->content, pt->show_values,
426 char *marker = pivot_value_to_string (pf->marker, pt->show_values,
428 footnotes[i] = table_create_footnote (
429 table, i, content, marker,
430 table_area_style_override (table->container,
431 &pt->look->areas[PIVOT_AREA_FOOTER],
432 pf->content->cell_style,
433 pf->content->font_style,
439 compose_headings (table,
440 &pt->axes[PIVOT_AXIS_COLUMN], H, &pt->axes[PIVOT_AXIS_ROW],
442 PIVOT_BORDER_DIM_COL_HORZ,
443 PIVOT_BORDER_DIM_COL_VERT,
444 PIVOT_BORDER_CAT_COL_HORZ,
445 PIVOT_BORDER_CAT_COL_VERT,
446 column_enumeration, body[H],
447 &pt->look->areas[PIVOT_AREA_COLUMN_LABELS],
448 PIVOT_AREA_COLUMN_LABELS,
449 &pt->look->areas[PIVOT_AREA_CORNER], footnotes,
450 pt->show_values, pt->show_variables,
451 pt->rotate_outer_row_labels, false);
453 compose_headings (table,
454 &pt->axes[PIVOT_AXIS_ROW], V, &pt->axes[PIVOT_AXIS_COLUMN],
456 PIVOT_BORDER_DIM_ROW_VERT,
457 PIVOT_BORDER_DIM_ROW_HORZ,
458 PIVOT_BORDER_CAT_ROW_VERT,
459 PIVOT_BORDER_CAT_ROW_HORZ,
460 row_enumeration, body[V],
461 &pt->look->areas[PIVOT_AREA_ROW_LABELS],
462 PIVOT_AREA_ROW_LABELS,
463 &pt->look->areas[PIVOT_AREA_CORNER], footnotes,
464 pt->show_values, pt->show_variables,
465 false, pt->rotate_inner_column_labels);
467 size_t *dindexes = XCALLOC (pt->n_dimensions, size_t);
469 PIVOT_ENUMERATION_FOR_EACH (pindexes[PIVOT_AXIS_ROW], row_enumeration,
470 &pt->axes[PIVOT_AXIS_ROW])
473 PIVOT_ENUMERATION_FOR_EACH (pindexes[PIVOT_AXIS_COLUMN],
475 &pt->axes[PIVOT_AXIS_COLUMN])
477 pivot_table_convert_indexes_ptod (pt, pindexes, dindexes);
478 const struct pivot_value *value = pivot_table_get (pt, dindexes);
480 x + stub[H], y + stub[V],
481 x + stub[H], y + stub[V],
482 &pt->look->areas[PIVOT_AREA_DATA], PIVOT_AREA_DATA,
484 pt->show_values, pt->show_variables, false);
493 if ((pt->corner_text || !pt->look->row_labels_in_corner)
494 && stub[H] && stub[V])
495 fill_cell (table, 0, 0, stub[H] - 1, stub[V] - 1,
496 &pt->look->areas[PIVOT_AREA_CORNER], PIVOT_AREA_CORNER,
497 pt->corner_text, footnotes,
498 pt->show_values, pt->show_variables, false);
500 if (table->n[H] && table->n[V])
503 table, get_table_rule (pt->look->borders, PIVOT_BORDER_INNER_TOP),
504 0, table->n[H] - 1, 0);
506 table, get_table_rule (pt->look->borders, PIVOT_BORDER_INNER_BOTTOM),
507 0, table->n[H] - 1, table->n[V]);
509 table, get_table_rule (pt->look->borders, PIVOT_BORDER_INNER_LEFT),
510 0, 0, table->n[V] - 1);
512 table, get_table_rule (pt->look->borders, PIVOT_BORDER_INNER_RIGHT),
513 table->n[H], 0, table->n[V] - 1);
517 table, get_table_rule (pt->look->borders, PIVOT_BORDER_DATA_TOP),
518 0, table->n[H] - 1, stub[V]);
521 table, get_table_rule (pt->look->borders, PIVOT_BORDER_DATA_LEFT),
522 stub[H], 0, table->n[V] - 1);
525 free (column_enumeration);
526 free (row_enumeration);
528 struct table_item *ti = table_item_create (table, NULL, NULL, pt->notes);
530 if (pt->title && pt->show_title)
532 struct table_item_text *title = pivot_value_to_table_item_text (
533 pt->title, &pt->look->areas[PIVOT_AREA_TITLE], footnotes,
534 pt->show_values, pt->show_variables);
535 table_item_set_title (ti, title);
536 table_item_text_destroy (title);
539 const struct pivot_axis *layer_axis = &pt->axes[PIVOT_AXIS_LAYER];
540 struct table_item_layers *layers = NULL;
541 for (size_t i = 0; i < layer_axis->n_dimensions; i++)
543 const struct pivot_dimension *d = layer_axis->dimensions[i];
548 layers = xzalloc (sizeof *layers);
549 layers->style = table_area_style_override (
550 NULL, &pt->look->areas[PIVOT_AREA_LAYERS], NULL, NULL, false);
551 layers->layers = xnmalloc (layer_axis->n_dimensions,
552 sizeof *layers->layers);
555 const struct pivot_value *name
556 = d->data_leaves[layer_indexes[i]]->name;
557 struct table_item_layer *layer = &layers->layers[layers->n_layers++];
558 struct string s = DS_EMPTY_INITIALIZER;
559 pivot_value_format_body (name, pt->show_values, pt->show_variables,
561 layer->content = ds_steal_cstr (&s);
562 layer->n_footnotes = 0;
563 layer->footnotes = xnmalloc (name->n_footnotes,
564 sizeof *layer->footnotes);
565 for (size_t i = 0; i < name->n_footnotes; i++)
567 struct footnote *f = footnotes[name->footnotes[i]->idx];
569 layer->footnotes[layer->n_footnotes++] = f;
575 table_item_set_layers (ti, layers);
576 table_item_layers_destroy (layers);
579 if (pt->caption && pt->show_caption)
581 struct table_item_text *caption = pivot_value_to_table_item_text (
582 pt->caption, &pt->look->areas[PIVOT_AREA_CAPTION], footnotes,
583 pt->show_values, pt->show_variables);
584 table_item_set_caption (ti, caption);
585 table_item_text_destroy (caption);
589 ti->pt = pivot_table_ref (pt);
591 table_item_submit (ti);
595 pivot_table_submit (struct pivot_table *pt)
597 pivot_table_assign_label_depth (CONST_CAST (struct pivot_table *, pt));
599 int old_decimal = settings_get_decimal_char (FMT_COMMA);
600 if (pt->decimal == '.' || pt->decimal == ',')
601 settings_set_decimal_char (pt->decimal);
603 if (pt->look->print_all_layers)
605 size_t *layer_indexes;
607 PIVOT_AXIS_FOR_EACH (layer_indexes, &pt->axes[PIVOT_AXIS_LAYER])
609 if (pt->look->paginate_layers)
610 page_eject_item_submit (page_eject_item_create ());
611 pivot_table_submit_layer (pt, layer_indexes);
615 pivot_table_submit_layer (pt, pt->current_layer);
617 settings_set_decimal_char (old_decimal);
619 pivot_table_unref (pt);