Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/matplotlib/table.py : 17%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Original code by:
2# John Gill <jng@europe.renre.com>
3# Copyright 2004 John Gill and John Hunter
4#
5# Subsequent changes:
6# The Matplotlib development team
7# Copyright The Matplotlib development team
9"""
10This module provides functionality to add a table to a plot.
12Use the factory function `~matplotlib.table.table` to create a ready-made
13table from texts. If you need more control, use the `.Table` class and its
14methods.
16The table consists of a grid of cells, which are indexed by (row, column).
17The cell (0, 0) is positioned at the top left.
19Thanks to John Gill for providing the class and table.
20"""
22from . import artist, cbook, docstring
23from .artist import Artist, allow_rasterization
24from .patches import Rectangle
25from .text import Text
26from .transforms import Bbox
27from .path import Path
30class Cell(Rectangle):
31 """
32 A cell is a `.Rectangle` with some associated `.Text`.
34 .. note:
35 As a user, you'll most likely not creates cells yourself. Instead, you
36 should use either the `~matplotlib.table.table` factory function or
37 `.Table.add_cell`.
39 Parameters
40 ----------
41 xy : 2-tuple
42 The position of the bottom left corner of the cell.
43 width : float
44 The cell width.
45 height : float
46 The cell height.
47 edgecolor : color
48 The color of the cell border.
49 facecolor : color
50 The cell facecolor.
51 fill : bool
52 Whether the cell background is filled.
53 text : str
54 The cell text.
55 loc : {'left', 'center', 'right'}, default: 'right'
56 The alignment of the text within the cell.
57 fontproperties : dict
58 A dict defining the font properties of the text. Supported keys and
59 values are the keyword arguments accepted by `.FontProperties`.
60 """
62 PAD = 0.1
63 """Padding between text and rectangle."""
65 def __init__(self, xy, width, height,
66 edgecolor='k', facecolor='w',
67 fill=True,
68 text='',
69 loc=None,
70 fontproperties=None
71 ):
73 # Call base
74 Rectangle.__init__(self, xy, width=width, height=height, fill=fill,
75 edgecolor=edgecolor, facecolor=facecolor)
76 self.set_clip_on(False)
78 # Create text object
79 if loc is None:
80 loc = 'right'
81 self._loc = loc
82 self._text = Text(x=xy[0], y=xy[1], text=text,
83 fontproperties=fontproperties)
84 self._text.set_clip_on(False)
86 def set_transform(self, trans):
87 Rectangle.set_transform(self, trans)
88 # the text does not get the transform!
89 self.stale = True
91 def set_figure(self, fig):
92 Rectangle.set_figure(self, fig)
93 self._text.set_figure(fig)
95 def get_text(self):
96 """Return the cell `.Text` instance."""
97 return self._text
99 def set_fontsize(self, size):
100 """Set the text fontsize."""
101 self._text.set_fontsize(size)
102 self.stale = True
104 def get_fontsize(self):
105 """Return the cell fontsize."""
106 return self._text.get_fontsize()
108 def auto_set_font_size(self, renderer):
109 """Shrink font size until the text fits into the cell width."""
110 fontsize = self.get_fontsize()
111 required = self.get_required_width(renderer)
112 while fontsize > 1 and required > self.get_width():
113 fontsize -= 1
114 self.set_fontsize(fontsize)
115 required = self.get_required_width(renderer)
117 return fontsize
119 @allow_rasterization
120 def draw(self, renderer):
121 if not self.get_visible():
122 return
123 # draw the rectangle
124 Rectangle.draw(self, renderer)
126 # position the text
127 self._set_text_position(renderer)
128 self._text.draw(renderer)
129 self.stale = False
131 def _set_text_position(self, renderer):
132 """Set text up so it draws in the right place.
134 Currently support 'left', 'center' and 'right'
135 """
136 bbox = self.get_window_extent(renderer)
137 l, b, w, h = bbox.bounds
139 # draw in center vertically
140 self._text.set_verticalalignment('center')
141 y = b + (h / 2.0)
143 # now position horizontally
144 if self._loc == 'center':
145 self._text.set_horizontalalignment('center')
146 x = l + (w / 2.0)
147 elif self._loc == 'left':
148 self._text.set_horizontalalignment('left')
149 x = l + (w * self.PAD)
150 else:
151 self._text.set_horizontalalignment('right')
152 x = l + (w * (1.0 - self.PAD))
154 self._text.set_position((x, y))
156 def get_text_bounds(self, renderer):
157 """
158 Return the text bounds as *(x, y, width, height)* in table coordinates.
159 """
160 bbox = self._text.get_window_extent(renderer)
161 bboxa = bbox.inverse_transformed(self.get_data_transform())
162 return bboxa.bounds
164 def get_required_width(self, renderer):
165 """Return the minimal required width for the cell."""
166 l, b, w, h = self.get_text_bounds(renderer)
167 return w * (1.0 + (2.0 * self.PAD))
169 @docstring.dedent_interpd
170 def set_text_props(self, **kwargs):
171 """
172 Update the text properties.
174 Valid keyword arguments are:
176 %(Text)s
177 """
178 self._text.update(kwargs)
179 self.stale = True
182class CustomCell(Cell):
183 """
184 A `.Cell` subclass with configurable edge visibility.
185 """
187 _edges = 'BRTL'
188 _edge_aliases = {'open': '',
189 'closed': _edges, # default
190 'horizontal': 'BT',
191 'vertical': 'RL'
192 }
194 def __init__(self, *args, visible_edges, **kwargs):
195 super().__init__(*args, **kwargs)
196 self.visible_edges = visible_edges
198 @property
199 def visible_edges(self):
200 """
201 The cell edges to be drawn with a line.
203 Reading this property returns a substring of 'BRTL' (bottom, right,
204 top, left').
206 When setting this property, you can use a substring of 'BRTL' or one
207 of {'open', 'closed', 'horizontal', 'vertical'}.
208 """
209 return self._visible_edges
211 @visible_edges.setter
212 def visible_edges(self, value):
213 if value is None:
214 self._visible_edges = self._edges
215 elif value in self._edge_aliases:
216 self._visible_edges = self._edge_aliases[value]
217 else:
218 if any(edge not in self._edges for edge in value):
219 raise ValueError('Invalid edge param {}, must only be one of '
220 '{} or string of {}'.format(
221 value,
222 ", ".join(self._edge_aliases),
223 ", ".join(self._edges)))
224 self._visible_edges = value
225 self.stale = True
227 def get_path(self):
228 """Return a `.Path` for the `.visible_edges`."""
229 codes = [Path.MOVETO]
230 codes.extend(
231 Path.LINETO if edge in self._visible_edges else Path.MOVETO
232 for edge in self._edges)
233 if Path.MOVETO not in codes[1:]: # All sides are visible
234 codes[-1] = Path.CLOSEPOLY
235 return Path(
236 [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.0, 0.0]],
237 codes,
238 readonly=True
239 )
242class Table(Artist):
243 """
244 A table of cells.
246 The table consists of a grid of cells, which are indexed by (row, column).
248 For a simple table, you'll have a full grid of cells with indices from
249 (0, 0) to (num_rows-1, num_cols-1), in which the cell (0, 0) is positioned
250 at the top left. However, you can also add cells with negative indices.
251 You don't have to add a cell to every grid position, so you can create
252 tables that have holes.
254 *Note*: You'll usually not create an empty table from scratch. Instead use
255 `~matplotlib.table.table` to create a table from data.
256 """
257 codes = {'best': 0,
258 'upper right': 1, # default
259 'upper left': 2,
260 'lower left': 3,
261 'lower right': 4,
262 'center left': 5,
263 'center right': 6,
264 'lower center': 7,
265 'upper center': 8,
266 'center': 9,
267 'top right': 10,
268 'top left': 11,
269 'bottom left': 12,
270 'bottom right': 13,
271 'right': 14,
272 'left': 15,
273 'top': 16,
274 'bottom': 17,
275 }
276 """Possible values where to place the table relative to the Axes."""
278 FONTSIZE = 10
280 AXESPAD = 0.02
281 """The border between the Axes and the table edge in Axes units."""
283 def __init__(self, ax, loc=None, bbox=None, **kwargs):
284 """
285 Parameters
286 ----------
287 ax : `matplotlib.axes.Axes`
288 The `~.axes.Axes` to plot the table into.
289 loc : str
290 The position of the cell with respect to *ax*. This must be one of
291 the `~.Table.codes`.
292 bbox : `.Bbox` or None
293 A bounding box to draw the table into. If this is not *None*, this
294 overrides *loc*.
296 Other Parameters
297 ----------------
298 **kwargs
299 `.Artist` properties.
300 """
302 Artist.__init__(self)
304 if isinstance(loc, str):
305 if loc not in self.codes:
306 cbook.warn_deprecated(
307 "3.1", message="Unrecognized location {!r}. Falling back "
308 "on 'bottom'; valid locations are\n\t{}\n"
309 "This will raise an exception %(removal)s."
310 .format(loc, '\n\t'.join(self.codes)))
311 loc = 'bottom'
312 loc = self.codes[loc]
313 self.set_figure(ax.figure)
314 self._axes = ax
315 self._loc = loc
316 self._bbox = bbox
318 # use axes coords
319 ax._unstale_viewLim()
320 self.set_transform(ax.transAxes)
322 self._cells = {}
323 self._edges = None
324 self._autoColumns = []
325 self._autoFontsize = True
326 self.update(kwargs)
328 self.set_clip_on(False)
330 def add_cell(self, row, col, *args, **kwargs):
331 """
332 Create a cell and add it to the table.
334 Parameters
335 ----------
336 row : int
337 Row index.
338 col : int
339 Column index.
340 *args, **kwargs
341 All other parameters are passed on to `Cell`.
343 Returns
344 -------
345 cell : `.CustomCell`
346 The created cell.
348 """
349 xy = (0, 0)
350 cell = CustomCell(xy, visible_edges=self.edges, *args, **kwargs)
351 self[row, col] = cell
352 return cell
354 def __setitem__(self, position, cell):
355 """
356 Set a custom cell in a given position.
357 """
358 cbook._check_isinstance(CustomCell, cell=cell)
359 try:
360 row, col = position[0], position[1]
361 except Exception:
362 raise KeyError('Only tuples length 2 are accepted as coordinates')
363 cell.set_figure(self.figure)
364 cell.set_transform(self.get_transform())
365 cell.set_clip_on(False)
366 self._cells[row, col] = cell
367 self.stale = True
369 def __getitem__(self, position):
370 """Retrieve a custom cell from a given position."""
371 return self._cells[position]
373 @property
374 def edges(self):
375 """
376 The default value of `~.CustomCell.visible_edges` for newly added
377 cells using `.add_cell`.
379 Notes
380 -----
381 This setting does currently only affect newly created cells using
382 `.add_cell`.
384 To change existing cells, you have to set their edges explicitly::
386 for c in tab.get_celld().values():
387 c.visible_edges = 'horizontal'
389 """
390 return self._edges
392 @edges.setter
393 def edges(self, value):
394 self._edges = value
395 self.stale = True
397 def _approx_text_height(self):
398 return (self.FONTSIZE / 72.0 * self.figure.dpi /
399 self._axes.bbox.height * 1.2)
401 @allow_rasterization
402 def draw(self, renderer):
403 # docstring inherited
405 # Need a renderer to do hit tests on mouseevent; assume the last one
406 # will do
407 if renderer is None:
408 renderer = self.figure._cachedRenderer
409 if renderer is None:
410 raise RuntimeError('No renderer defined')
412 if not self.get_visible():
413 return
414 renderer.open_group('table', gid=self.get_gid())
415 self._update_positions(renderer)
417 for key in sorted(self._cells):
418 self._cells[key].draw(renderer)
420 renderer.close_group('table')
421 self.stale = False
423 def _get_grid_bbox(self, renderer):
424 """
425 Get a bbox, in axes co-ordinates for the cells.
427 Only include those in the range (0, 0) to (maxRow, maxCol).
428 """
429 boxes = [cell.get_window_extent(renderer)
430 for (row, col), cell in self._cells.items()
431 if row >= 0 and col >= 0]
432 bbox = Bbox.union(boxes)
433 return bbox.inverse_transformed(self.get_transform())
435 def contains(self, mouseevent):
436 # docstring inherited
437 inside, info = self._default_contains(mouseevent)
438 if inside is not None:
439 return inside, info
440 # TODO: Return index of the cell containing the cursor so that the user
441 # doesn't have to bind to each one individually.
442 renderer = self.figure._cachedRenderer
443 if renderer is not None:
444 boxes = [cell.get_window_extent(renderer)
445 for (row, col), cell in self._cells.items()
446 if row >= 0 and col >= 0]
447 bbox = Bbox.union(boxes)
448 return bbox.contains(mouseevent.x, mouseevent.y), {}
449 else:
450 return False, {}
452 def get_children(self):
453 """Return the Artists contained by the table."""
454 return list(self._cells.values())
456 def get_window_extent(self, renderer):
457 """Return the bounding box of the table in window coords."""
458 self._update_positions(renderer)
459 boxes = [cell.get_window_extent(renderer)
460 for cell in self._cells.values()]
461 return Bbox.union(boxes)
463 def _do_cell_alignment(self):
464 """
465 Calculate row heights and column widths; position cells accordingly.
466 """
467 # Calculate row/column widths
468 widths = {}
469 heights = {}
470 for (row, col), cell in self._cells.items():
471 height = heights.setdefault(row, 0.0)
472 heights[row] = max(height, cell.get_height())
473 width = widths.setdefault(col, 0.0)
474 widths[col] = max(width, cell.get_width())
476 # work out left position for each column
477 xpos = 0
478 lefts = {}
479 for col in sorted(widths):
480 lefts[col] = xpos
481 xpos += widths[col]
483 ypos = 0
484 bottoms = {}
485 for row in sorted(heights, reverse=True):
486 bottoms[row] = ypos
487 ypos += heights[row]
489 # set cell positions
490 for (row, col), cell in self._cells.items():
491 cell.set_x(lefts[col])
492 cell.set_y(bottoms[row])
494 def auto_set_column_width(self, col):
495 """
496 Automatically set the widths of given columns to optimal sizes.
498 Parameters
499 ----------
500 col : int or sequence of ints
501 The indices of the columns to auto-scale.
502 """
503 # check for col possibility on iteration
504 try:
505 iter(col)
506 except (TypeError, AttributeError):
507 self._autoColumns.append(col)
508 else:
509 for cell in col:
510 self._autoColumns.append(cell)
512 self.stale = True
514 def _auto_set_column_width(self, col, renderer):
515 """Automatically set width for column."""
516 cells = [cell for key, cell in self._cells.items() if key[1] == col]
517 max_width = max((cell.get_required_width(renderer) for cell in cells),
518 default=0)
519 for cell in cells:
520 cell.set_width(max_width)
522 def auto_set_font_size(self, value=True):
523 """Automatically set font size."""
524 self._autoFontsize = value
525 self.stale = True
527 def _auto_set_font_size(self, renderer):
529 if len(self._cells) == 0:
530 return
531 fontsize = next(iter(self._cells.values())).get_fontsize()
532 cells = []
533 for key, cell in self._cells.items():
534 # ignore auto-sized columns
535 if key[1] in self._autoColumns:
536 continue
537 size = cell.auto_set_font_size(renderer)
538 fontsize = min(fontsize, size)
539 cells.append(cell)
541 # now set all fontsizes equal
542 for cell in self._cells.values():
543 cell.set_fontsize(fontsize)
545 def scale(self, xscale, yscale):
546 """Scale column widths by *xscale* and row heights by *yscale*."""
547 for c in self._cells.values():
548 c.set_width(c.get_width() * xscale)
549 c.set_height(c.get_height() * yscale)
551 def set_fontsize(self, size):
552 """
553 Set the font size, in points, of the cell text.
555 Parameters
556 ----------
557 size : float
559 Notes
560 -----
561 As long as auto font size has not been disabled, the value will be
562 clipped such that the text fits horizontally into the cell.
564 You can disable this behavior using `.auto_set_font_size`.
566 >>> the_table.auto_set_font_size(False)
567 >>> the_table.set_fontsize(20)
569 However, there is no automatic scaling of the row height so that the
570 text may exceed the cell boundary.
571 """
572 for cell in self._cells.values():
573 cell.set_fontsize(size)
574 self.stale = True
576 def _offset(self, ox, oy):
577 """Move all the artists by ox, oy (axes coords)."""
578 for c in self._cells.values():
579 x, y = c.get_x(), c.get_y()
580 c.set_x(x + ox)
581 c.set_y(y + oy)
583 def _update_positions(self, renderer):
584 # called from renderer to allow more precise estimates of
585 # widths and heights with get_window_extent
587 # Do any auto width setting
588 for col in self._autoColumns:
589 self._auto_set_column_width(col, renderer)
591 if self._autoFontsize:
592 self._auto_set_font_size(renderer)
594 # Align all the cells
595 self._do_cell_alignment()
597 bbox = self._get_grid_bbox(renderer)
598 l, b, w, h = bbox.bounds
600 if self._bbox is not None:
601 # Position according to bbox
602 rl, rb, rw, rh = self._bbox
603 self.scale(rw / w, rh / h)
604 ox = rl - l
605 oy = rb - b
606 self._do_cell_alignment()
607 else:
608 # Position using loc
609 (BEST, UR, UL, LL, LR, CL, CR, LC, UC, C,
610 TR, TL, BL, BR, R, L, T, B) = range(len(self.codes))
611 # defaults for center
612 ox = (0.5 - w / 2) - l
613 oy = (0.5 - h / 2) - b
614 if self._loc in (UL, LL, CL): # left
615 ox = self.AXESPAD - l
616 if self._loc in (BEST, UR, LR, R, CR): # right
617 ox = 1 - (l + w + self.AXESPAD)
618 if self._loc in (BEST, UR, UL, UC): # upper
619 oy = 1 - (b + h + self.AXESPAD)
620 if self._loc in (LL, LR, LC): # lower
621 oy = self.AXESPAD - b
622 if self._loc in (LC, UC, C): # center x
623 ox = (0.5 - w / 2) - l
624 if self._loc in (CL, CR, C): # center y
625 oy = (0.5 - h / 2) - b
627 if self._loc in (TL, BL, L): # out left
628 ox = - (l + w)
629 if self._loc in (TR, BR, R): # out right
630 ox = 1.0 - l
631 if self._loc in (TR, TL, T): # out top
632 oy = 1.0 - b
633 if self._loc in (BL, BR, B): # out bottom
634 oy = - (b + h)
636 self._offset(ox, oy)
638 def get_celld(self):
639 r"""
640 Return a dict of cells in the table mapping *(row, column)* to
641 `.Cell`\s.
643 Notes
644 -----
645 You can also directly index into the Table object to access individual
646 cells::
648 cell = table[row, col]
650 """
651 return self._cells
654docstring.interpd.update(Table=artist.kwdoc(Table))
657@docstring.dedent_interpd
658def table(ax,
659 cellText=None, cellColours=None,
660 cellLoc='right', colWidths=None,
661 rowLabels=None, rowColours=None, rowLoc='left',
662 colLabels=None, colColours=None, colLoc='center',
663 loc='bottom', bbox=None, edges='closed',
664 **kwargs):
665 """
666 Add a table to an `~.axes.Axes`.
668 At least one of *cellText* or *cellColours* must be specified. These
669 parameters must be 2D lists, in which the outer lists define the rows and
670 the inner list define the column values per row. Each row must have the
671 same number of elements.
673 The table can optionally have row and column headers, which are configured
674 using *rowLabels*, *rowColours*, *rowLoc* and *colLabels*, *colColours*,
675 *colLoc* respectively.
677 For finer grained control over tables, use the `.Table` class and add it to
678 the axes with `.Axes.add_table`.
680 Parameters
681 ----------
682 cellText : 2D list of str, optional
683 The texts to place into the table cells.
685 *Note*: Line breaks in the strings are currently not accounted for and
686 will result in the text exceeding the cell boundaries.
688 cellColours : 2D list of colors, optional
689 The background colors of the cells.
691 cellLoc : {'left', 'center', 'right'}, default: 'right'
692 The alignment of the text within the cells.
694 colWidths : list of float, optional
695 The column widths in units of the axes. If not given, all columns will
696 have a width of *1 / ncols*.
698 rowLabels : list of str, optional
699 The text of the row header cells.
701 rowColours : list of colors, optional
702 The colors of the row header cells.
704 rowLoc : {'left', 'center', 'right'}, optional, default: 'left'
705 The text alignment of the row header cells.
707 colLabels : list of str, optional
708 The text of the column header cells.
710 colColours : list of colors, optional
711 The colors of the column header cells.
713 colLoc : {'left', 'center', 'right'}, optional, default: 'left'
714 The text alignment of the column header cells.
716 loc : str, optional
717 The position of the cell with respect to *ax*. This must be one of
718 the `~.Table.codes`.
720 bbox : `.Bbox`, optional
721 A bounding box to draw the table into. If this is not *None*, this
722 overrides *loc*.
724 edges : substring of 'BRTL' or {'open', 'closed', 'horizontal', 'vertical'}
725 The cell edges to be drawn with a line. See also
726 `~.CustomCell.visible_edges`.
728 Other Parameters
729 ----------------
730 **kwargs
731 `.Table` properties.
733 %(Table)s
735 Returns
736 -------
737 table : `~matplotlib.table.Table`
738 The created table.
739 """
741 if cellColours is None and cellText is None:
742 raise ValueError('At least one argument from "cellColours" or '
743 '"cellText" must be provided to create a table.')
745 # Check we have some cellText
746 if cellText is None:
747 # assume just colours are needed
748 rows = len(cellColours)
749 cols = len(cellColours[0])
750 cellText = [[''] * cols] * rows
752 rows = len(cellText)
753 cols = len(cellText[0])
754 for row in cellText:
755 if len(row) != cols:
756 raise ValueError("Each row in 'cellText' must have {} columns"
757 .format(cols))
759 if cellColours is not None:
760 if len(cellColours) != rows:
761 raise ValueError("'cellColours' must have {} rows".format(rows))
762 for row in cellColours:
763 if len(row) != cols:
764 raise ValueError("Each row in 'cellColours' must have {} "
765 "columns".format(cols))
766 else:
767 cellColours = ['w' * cols] * rows
769 # Set colwidths if not given
770 if colWidths is None:
771 colWidths = [1.0 / cols] * cols
773 # Fill in missing information for column
774 # and row labels
775 rowLabelWidth = 0
776 if rowLabels is None:
777 if rowColours is not None:
778 rowLabels = [''] * rows
779 rowLabelWidth = colWidths[0]
780 elif rowColours is None:
781 rowColours = 'w' * rows
783 if rowLabels is not None:
784 if len(rowLabels) != rows:
785 raise ValueError("'rowLabels' must be of length {0}".format(rows))
787 # If we have column labels, need to shift
788 # the text and colour arrays down 1 row
789 offset = 1
790 if colLabels is None:
791 if colColours is not None:
792 colLabels = [''] * cols
793 else:
794 offset = 0
795 elif colColours is None:
796 colColours = 'w' * cols
798 # Set up cell colours if not given
799 if cellColours is None:
800 cellColours = ['w' * cols] * rows
802 # Now create the table
803 table = Table(ax, loc, bbox, **kwargs)
804 table.edges = edges
805 height = table._approx_text_height()
807 # Add the cells
808 for row in range(rows):
809 for col in range(cols):
810 table.add_cell(row + offset, col,
811 width=colWidths[col], height=height,
812 text=cellText[row][col],
813 facecolor=cellColours[row][col],
814 loc=cellLoc)
815 # Do column labels
816 if colLabels is not None:
817 for col in range(cols):
818 table.add_cell(0, col,
819 width=colWidths[col], height=height,
820 text=colLabels[col], facecolor=colColours[col],
821 loc=colLoc)
823 # Do row labels
824 if rowLabels is not None:
825 for row in range(rows):
826 table.add_cell(row + offset, -1,
827 width=rowLabelWidth or 1e-15, height=height,
828 text=rowLabels[row], facecolor=rowColours[row],
829 loc=rowLoc)
830 if rowLabelWidth == 0:
831 table.auto_set_column_width(-1)
833 ax.add_table(table)
834 return table