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 vrules[0] = vrules[n_columns] = true;
272 for (int dim_index = a_axis->n_dimensions; --dim_index >= 0; )
274 const struct pivot_dimension *d = a_axis->dimensions[dim_index];
275 if (d->hide_all_labels)
278 for (int row_ofs = 0; row_ofs < d->label_depth; row_ofs++)
280 for (size_t x1 = 0; x1 < n_columns;)
282 const struct pivot_category *c = find_category (
283 d, dim_index, column_enumeration + x1 * stride,
284 d->label_depth - row_ofs - 1);
292 for (x2 = x1 + 1; x2 < n_columns; x2++)
296 const struct pivot_category *c2 = find_category (
297 d, dim_index, column_enumeration + x2 * stride,
298 d->label_depth - row_ofs - 1);
303 int y1 = top_row + row_ofs;
304 int y2 = top_row + row_ofs + c->extra_depth + 1;
305 bool is_outer_row = y1 == 0;
306 bool is_inner_row = y2 == b_size;
307 if (pivot_category_is_leaf (c) || c->show_label)
309 int bb[TABLE_N_AXES][2];
310 bb[a][0] = x1 + a_ofs;
311 bb[a][1] = x2 + a_ofs - 1;
314 bool rotate = ((rotate_inner_labels && is_inner_row)
315 || (rotate_outer_labels && is_outer_row));
316 fill_cell (t, bb[H][0], bb[V][0], bb[H][1], bb[V][1],
317 label_style, label_style_idx, c->name, footnotes,
318 show_values, show_variables, rotate);
320 /* Draw all the vertical lines in our running example, other
321 than the far left and far right ones. Only the ones that
322 start in the last row of the heading are drawn with the
323 "category" style, the rest with the "dimension" style,
324 e.g. only the # below are category style:
326 +-----------------------------------------------------+
328 +-----------------+-----------------+-----------------+
329 | bbbb1 | bbbb2 | bbbb3 |
330 +-----------------+-----------------+-----------------+
331 | aaaa | aaaa | aaaa |
332 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
333 |aaaa1#aaaa2#aaaa3|aaaa1#aaaa2#aaaa3|aaaa1#aaaa2#aaaa3|
334 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
336 enum pivot_border style
337 = (y1 == b_size - 1 ? cat_col_vert : dim_col_vert);
340 draw_line (t, borders, style, b, x2 + a_ofs, y1,
346 draw_line (t, borders, style, b, x1 + a_ofs, y1,
351 if (c->parent && c->parent->show_label)
352 draw_line (t, borders, cat_col_horz, a, y1,
353 x1 + a_ofs, x2 + a_ofs - 1);
358 if (d->root->show_label_in_corner && a_ofs > 0)
360 int bb[TABLE_N_AXES][2];
362 bb[a][1] = a_ofs - 1;
364 bb[b][1] = top_row + d->label_depth - 1;
365 fill_cell (t, bb[H][0], bb[V][0], bb[H][1], bb[V][1],
366 corner_style, PIVOT_AREA_CORNER, d->root->name, footnotes,
367 show_values, show_variables, false);
370 /* Draw the horizontal line between dimensions, e.g. the ===== line here:
372 +-----------------------------------------------------+ __
374 +-----------------+-----------------+-----------------+ |dim "bbbb"
375 | bbbb1 | bbbb2 | bbbb3 | _|
376 +=================+=================+=================+ __
377 | aaaa | aaaa | aaaa | |
378 +-----+-----+-----+-----+-----+-----+-----+-----+-----+ |dim "aaaa"
379 |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3| _|
380 +-----+-----+-----+-----+-----+-----+-----+-----+-----+
382 if (dim_index != a_axis->n_dimensions - 1)
383 draw_line (t, borders, dim_col_horz, a, top_row, a_ofs,
385 top_row += d->label_depth;
391 pivot_table_submit_layer (const struct pivot_table *pt,
392 const size_t *layer_indexes)
394 const size_t *pindexes[PIVOT_N_AXES]
395 = { [PIVOT_AXIS_LAYER] = layer_indexes };
397 size_t body[TABLE_N_AXES];
398 size_t *column_enumeration = pivot_table_enumerate_axis (
399 pt, PIVOT_AXIS_COLUMN, layer_indexes, pt->look->omit_empty, &body[H]);
400 size_t *row_enumeration = pivot_table_enumerate_axis (
401 pt, PIVOT_AXIS_ROW, layer_indexes, pt->look->omit_empty, &body[V]);
403 int stub[TABLE_N_AXES] = {
404 [H] = pt->axes[PIVOT_AXIS_ROW].label_depth,
405 [V] = pt->axes[PIVOT_AXIS_COLUMN].label_depth,
407 struct table *table = table_create (body[H] + stub[H],
409 stub[H], 0, stub[V], 0);
411 for (size_t i = 0; i < PIVOT_N_AREAS; i++)
412 table->styles[i] = table_area_style_override (
413 table->container, &pt->look->areas[i], NULL, NULL, false);
415 for (size_t i = 0; i < PIVOT_N_BORDERS; i++)
417 const struct table_border_style *in = &pt->look->borders[i];
418 table->rule_colors[i] = pool_alloc (table->container,
419 sizeof *table->rule_colors[i]);
420 struct cell_color *out = table->rule_colors[i];
421 out->alpha = in->color.alpha;
422 out->r = in->color.r;
423 out->g = in->color.g;
424 out->b = in->color.b;
427 struct footnote **footnotes = XCALLOC (pt->n_footnotes, struct footnote *);
428 for (size_t i = 0; i < pt->n_footnotes; i++)
430 const struct pivot_footnote *pf = pt->footnotes[i];
435 char *content = pivot_value_to_string (pf->content, pt->show_values,
437 char *marker = pivot_value_to_string (pf->marker, pt->show_values,
439 footnotes[i] = table_create_footnote (
440 table, i, content, marker,
441 table_area_style_override (table->container,
442 &pt->look->areas[PIVOT_AREA_FOOTER],
443 pf->content->cell_style,
444 pf->content->font_style,
450 compose_headings (table,
451 &pt->axes[PIVOT_AXIS_COLUMN], H, &pt->axes[PIVOT_AXIS_ROW],
453 PIVOT_BORDER_DIM_COL_HORZ,
454 PIVOT_BORDER_DIM_COL_VERT,
455 PIVOT_BORDER_CAT_COL_HORZ,
456 PIVOT_BORDER_CAT_COL_VERT,
457 column_enumeration, body[H],
458 &pt->look->areas[PIVOT_AREA_COLUMN_LABELS],
459 PIVOT_AREA_COLUMN_LABELS,
460 &pt->look->areas[PIVOT_AREA_CORNER], footnotes,
461 pt->show_values, pt->show_variables,
462 pt->rotate_outer_row_labels, false);
464 compose_headings (table,
465 &pt->axes[PIVOT_AXIS_ROW], V, &pt->axes[PIVOT_AXIS_COLUMN],
467 PIVOT_BORDER_DIM_ROW_VERT,
468 PIVOT_BORDER_DIM_ROW_HORZ,
469 PIVOT_BORDER_CAT_ROW_VERT,
470 PIVOT_BORDER_CAT_ROW_HORZ,
471 row_enumeration, body[V],
472 &pt->look->areas[PIVOT_AREA_ROW_LABELS],
473 PIVOT_AREA_ROW_LABELS,
474 &pt->look->areas[PIVOT_AREA_CORNER], footnotes,
475 pt->show_values, pt->show_variables,
476 false, pt->rotate_inner_column_labels);
478 size_t *dindexes = XCALLOC (pt->n_dimensions, size_t);
480 PIVOT_ENUMERATION_FOR_EACH (pindexes[PIVOT_AXIS_ROW], row_enumeration,
481 &pt->axes[PIVOT_AXIS_ROW])
484 PIVOT_ENUMERATION_FOR_EACH (pindexes[PIVOT_AXIS_COLUMN],
486 &pt->axes[PIVOT_AXIS_COLUMN])
488 pivot_table_convert_indexes_ptod (pt, pindexes, dindexes);
489 const struct pivot_value *value = pivot_table_get (pt, dindexes);
491 x + stub[H], y + stub[V],
492 x + stub[H], y + stub[V],
493 &pt->look->areas[PIVOT_AREA_DATA], PIVOT_AREA_DATA,
495 pt->show_values, pt->show_variables, false);
504 if ((pt->corner_text || !pt->look->row_labels_in_corner)
505 && stub[H] && stub[V])
506 fill_cell (table, 0, 0, stub[H] - 1, stub[V] - 1,
507 &pt->look->areas[PIVOT_AREA_CORNER], PIVOT_AREA_CORNER,
508 pt->corner_text, footnotes,
509 pt->show_values, pt->show_variables, false);
511 if (table->n[H] && table->n[V])
514 table, get_table_rule (pt->look->borders, PIVOT_BORDER_INNER_TOP),
515 0, table->n[H] - 1, 0);
517 table, get_table_rule (pt->look->borders, PIVOT_BORDER_INNER_BOTTOM),
518 0, table->n[H] - 1, table->n[V]);
520 table, get_table_rule (pt->look->borders, PIVOT_BORDER_INNER_LEFT),
521 0, 0, table->n[V] - 1);
523 table, get_table_rule (pt->look->borders, PIVOT_BORDER_INNER_RIGHT),
524 table->n[H], 0, table->n[V] - 1);
528 table, get_table_rule (pt->look->borders, PIVOT_BORDER_DATA_TOP),
529 0, table->n[H] - 1, stub[V]);
532 table, get_table_rule (pt->look->borders, PIVOT_BORDER_DATA_LEFT),
533 stub[H], 0, table->n[V] - 1);
536 free (column_enumeration);
537 free (row_enumeration);
539 struct table_item *ti = table_item_create (table, NULL, NULL, pt->notes);
541 if (pt->title && pt->show_title)
543 struct table_item_text *title = pivot_value_to_table_item_text (
544 pt->title, &pt->look->areas[PIVOT_AREA_TITLE], footnotes,
545 pt->show_values, pt->show_variables);
546 table_item_set_title (ti, title);
547 table_item_text_destroy (title);
550 const struct pivot_axis *layer_axis = &pt->axes[PIVOT_AXIS_LAYER];
551 struct table_item_layers *layers = NULL;
552 for (size_t i = 0; i < layer_axis->n_dimensions; i++)
554 const struct pivot_dimension *d = layer_axis->dimensions[i];
559 layers = xzalloc (sizeof *layers);
560 layers->style = table_area_style_override (
561 NULL, &pt->look->areas[PIVOT_AREA_LAYERS], NULL, NULL, false);
562 layers->layers = xnmalloc (layer_axis->n_dimensions,
563 sizeof *layers->layers);
566 const struct pivot_value *name
567 = d->data_leaves[layer_indexes[i]]->name;
568 struct table_item_layer *layer = &layers->layers[layers->n_layers++];
569 struct string s = DS_EMPTY_INITIALIZER;
570 pivot_value_format_body (name, pt->show_values, pt->show_variables,
572 layer->content = ds_steal_cstr (&s);
573 layer->n_footnotes = 0;
574 layer->footnotes = xnmalloc (name->n_footnotes,
575 sizeof *layer->footnotes);
576 for (size_t i = 0; i < name->n_footnotes; i++)
578 struct footnote *f = footnotes[name->footnotes[i]->idx];
580 layer->footnotes[layer->n_footnotes++] = f;
586 table_item_set_layers (ti, layers);
587 table_item_layers_destroy (layers);
590 if (pt->caption && pt->show_caption)
592 struct table_item_text *caption = pivot_value_to_table_item_text (
593 pt->caption, &pt->look->areas[PIVOT_AREA_CAPTION], footnotes,
594 pt->show_values, pt->show_variables);
595 table_item_set_caption (ti, caption);
596 table_item_text_destroy (caption);
600 ti->pt = pivot_table_ref (pt);
602 table_item_submit (ti);
606 pivot_table_submit (struct pivot_table *pt)
608 pivot_table_assign_label_depth (CONST_CAST (struct pivot_table *, pt));
610 int old_decimal = settings_get_decimal_char (FMT_COMMA);
611 if (pt->decimal == '.' || pt->decimal == ',')
612 settings_set_decimal_char (pt->decimal);
614 if (pt->look->print_all_layers)
616 size_t *layer_indexes;
618 PIVOT_AXIS_FOR_EACH (layer_indexes, &pt->axes[PIVOT_AXIS_LAYER])
620 if (pt->look->paginate_layers)
621 page_eject_item_submit (page_eject_item_create ());
622 pivot_table_submit_layer (pt, layer_indexes);
626 pivot_table_submit_layer (pt, pt->current_layer);
628 settings_set_decimal_char (old_decimal);
630 pivot_table_unref (pt);