Example #1
0
    def draw_line(self, label, label_style=None, *args, **kwargs):
        """
		Draw a line legend for the given label.
		Any additional arguments and keyword arguments are provided to the plotting function.

		:param label: The text of the legend label.
		:type label: str
		:param label_style: The style of the label.
							If `None` is given, a default style is used.
		:type label_style: None or dict

		:return: A tuple made up of the function return value and the drawn label.
		:rtype: tuple
		"""

        figure = self.drawable.figure
        axis = self.drawable.axis

        label_style = label_style or {'alpha': 0.8}
        default_style = self._get_legend_params('fontsize')
        default_style.update(label_style)
        """
		Get the offset for the new legend.
		Then, draw the line first and the annotation second.
		"""
        offset = self._get_offset(transform=axis.transAxes)
        linespacing = util.get_linespacing(figure,
                                           axis,
                                           transform=axis.transAxes,
                                           **default_style)

        line = lines.Line2D([offset, offset + 0.025],
                            [1 + linespacing / 2.] * 2,
                            transform=axis.transAxes,
                            *args,
                            **kwargs)
        line.set_clip_on(False)
        axis.add_line(line)
        """
		Load the default legend style and update the styling.
		If a custom style is given, it overwrites the styling.
		"""
        line_offset = util.get_bb(figure, axis, line,
                                  transform=axis.transAxes).x1 + 0.00625
        annotation = self.draw_annotation(label, line_offset, 1,
                                          **default_style)
        if annotation.get_virtual_bb(transform=axis.transAxes).x1 > 1:
            self._newline(line, annotation, linespacing)
        else:
            self.lines[-1].append((line, annotation))

        self.drawable.redraw()
        return (line, annotation)
Example #2
0
	def draw(self, data, wordspacing=0.005, lineheight=1.25,
			 align='left', with_legend=True, lpad=0, rpad=0, tpad=0, *args, **kwargs):
		"""
		Draw the text annotation visualization.
		The method receives text as a list of tokens and draws them as text.

		The text can be provided either as a string, a list of strings or as dictionaries.
		If strings are provided, the function converts them into dictionaries.
		Dictionaries should have the following format:

		.. code-block:: python

			{
			  'label': None,
			  'style': { 'facecolor': 'None' },
			  'text': 'token',
			}

		Of these keys, only `text` is required.
		The correct styling options are those accepted by the :class:`matplotlib.text.Text` class.
		Anything not given uses default values.

		Any other styling options, common to all tokens, should be provided as keyword arguments.

		:param data: The text data.
					 The visualization expects a string, a `list` of tokens, or a `list` of `dict` instances as shown above.
		:type data: str or list of str or list of dict
		:param wordspacing: The space between words.
		:type wordspacing: float
		:param lineheight: The space between lines.
		:type lineheight: float
		:param align: The text's alignment.
					  Possible values:

					    - left
					    - center
					    - right
					    - justify
					    - justify-start (or justify-left)
					    - justify-center
					    - justify-end or (justify-right)
		:type align: str
		:param with_legend: A boolean indicating whether labels should create a legend.
		:type with_legend: bool
		:param lpad: The left padding as a percentage of the plot.
					 The range is expected to be between 0 and 1.
		:type lpad: float
		:param rpad: The right padding as a percentage of the plot.
					 The range is expected to be between 0 and 1.
		:type rpad: float
		:param tpad: The top padding as a percentage of the plot.
					 The range is expected to be between 0 and 1.
		:type tpad: float

		:return: The drawn lines.
				 Each line is made up of tuples of lists.
				 The first list in each tuple is the list of legend labels.
				 The second list in each tuple is the list of actual tokens.
		:rtype: list of tuple

		:raises: ValueError
		"""

		figure = self.drawable.figure
		axis = self.drawable.axis

		"""
		Validate the arguments.
		All padding arguments should be non-negative.
		The left-padding and the right-padding should not overlap.
		"""
		if lpad < 0:
			raise ValueError("The left padding should be between 0 and 1, received %d" % lpad)

		if rpad < 0:
			raise ValueError("The right padding should be between 0 and 1, received %d" % rpad)

		if lpad + rpad >= 1:
			raise ValueError("The left and right padding should not overlap, received %d left padding and %d right padding" % (lpad, rpad))

		"""
		Gradually convert text inputs to dictionary inputs: from `str` to `list`, and from `list` to `dict`.
		"""
		tokens = data.split() if type(data) is str else data
		tokens = [ { 'text': token } if type(token) is str else token for token in tokens ]

		"""
		Draw the text as an annotation first.
		"""
		annotation = Annotation(self.drawable)
		lines = annotation.draw(tokens, (lpad, axis.get_xlim()[1] - rpad), 0,
								 wordspacing=wordspacing, lineheight=lineheight,
								 align=align, va='top', *args, **kwargs)

		"""
		Draw a legend if it is requested.
		"""
		linespacing = util.get_linespacing(figure, axis, wordspacing, *args, **kwargs)
		labels = self._draw_legend(tokens, lines, wordspacing, linespacing,
								   *args, **kwargs) if with_legend else [ [] ] * len(lines)

		"""
		The entire visualization is shifted so that the legends start at x-coordinate 0.
		This way, the title is aligned with the visualization.
		This process is meant to tighten the layout.
		The axis is turned off since it has no purpose, and the y-limit is re-calculated.
		"""
		drawn_lines = list(zip(labels, lines))
		self._tighten(drawn_lines)
		axis.axis('off')
		axis.set_ylim(- len(lines) * linespacing, tpad + linespacing)

		return drawn_lines
Example #3
0
    def _draw_tokens(self,
                     tokens,
                     x,
                     y,
                     wordspacing,
                     lineheight,
                     align,
                     va,
                     transform=None,
                     *args,
                     **kwargs):
        """
		Draw the tokens on the plot.

		:param tokens: The text tokens to draw.
					   The method expects a `list` of tokens, each one a `dict`.
		:type tokens: list of str
		:param x: The start and end x-position of the annotation.
		:type x: tuple
		:param y: The starting y-position of the annotation.
		:type y: float
		:param wordspacing: The space between words.
							If `None` is given, the space is calculated based on the height of the line.
		:type wordspacing: float or None
		:param lineheight: The space between lines.
		:type lineheight: float
		:param align: The text's alignment.
					  Possible values:

					    - left
					    - center
					    - right
					    - justify
					    - justify-start (or justify-left)
					    - justify-center
					    - justify-end or (justify-right)
		:type align: str
		:param va: The vertical alignment, can be one of `top` or `bottom`.
				   If the vertical alignment is `bottom`, the annotation grows up.
				   If the vertical alignment is `top`, the annotation grows down.
		:type va: str
		:param transform: The bounding box transformation.
						  If `None` is given, the data transformation is used.
		:type transform: None or :class:`matplotlib.transforms.TransformNode`

		:return: The drawn lines.
				 Each line is made up of the text tokens.
		:rtype: list of list of :class:`matplotlib.text.Text`
		"""

        figure = self.drawable.figure
        axis = self.drawable.axis
        transform = transform if transform is not None else axis.transData

        linespacing = util.get_linespacing(
            figure, axis, wordspacing, transform=transform, *args, **
            kwargs) * lineheight
        if wordspacing is None:
            wordspacing = linespacing / 10.
        """
		Go through each token and draw it on the axis.
		"""
        drawn_lines, line_tokens = [], []
        offset = x[0]
        for token in tokens:
            """
			Draw the text token.
			If the vertical alignment is top, the annotation grows downwards: one line after the other.
			If the vertical alignment is bottom, the annotation grows upwards.
			When the vertical alignment is bottom, new text is always added to the same place.
			New lines push previous lines up.

			Note that the center alignment is not considered here.
			There is no way of knowing how many lines there will be in advance.
			Therefore lines are centered at a later stage.
			"""
            va = 'top' if va == 'center' else va
            text = text_util.draw_token(
                figure,
                axis,
                token.get('text'),
                offset,
                y - len(drawn_lines) * linespacing if va == 'top' else y,
                token.get('style', {}),
                wordspacing,
                va=va,
                transform=transform,
                *args,
                **kwargs)
            line_tokens.append(text)
            """
			If the token exceeds the x-limit, break it into a new line.
			The offset is reset to the left, and a new line is added.
			The token is moved to this new line.
			Lines do not break on punctuation marks.

			Note that lists are passed by reference.
			Therefore when the last token is removed from drawn lines when create a new line, the change is reflected here.
			"""
            bb = util.get_bb(figure, axis, text, transform=transform)
            if bb.x1 > x[1] and token.get('text') not in string.punctuation:
                self._newline(line_tokens,
                              drawn_lines,
                              linespacing,
                              x[0],
                              y,
                              va,
                              transform=transform)
                util.align(figure,
                           axis,
                           line_tokens,
                           xpad=wordspacing,
                           align=util.get_alignment(align),
                           xlim=x,
                           va=va,
                           transform=transform)
                offset = x[0]
                line_tokens = [text]

            offset += bb.width + wordspacing
        """
		Align the last line.
		"""
        drawn_lines.append(line_tokens)
        util.align(figure,
                   axis,
                   line_tokens,
                   xpad=wordspacing,
                   align=util.get_alignment(align, end=True),
                   xlim=x,
                   va=va,
                   transform=transform)

        return drawn_lines
Example #4
0
        def wrapper(self, label, label_style=None, *args, **kwargs):
            """
            Call the test function with any arguments and keyword arguments.

            :param label: The text of the legend label.
            :type label: str
            :param label_style: The style of the label.
                                If `None` is given, a default style is used.
            :type label_style: None or dict

            :return: A tuple containing the drawn visual and the annotation.
            :rtype: tuple
            """

            figure = self.drawable.figure
            axes = self.drawable.axes

            """
            If the label is already in the legend, return it.
            """
            drawn = self._contains(label)
            if drawn:
                return drawn

            """
            Load the default legend style and update the styling.
            If a custom style is given, it overwrites the styling.
            """
            label_style = label_style or { }
            default_style = self._get_legend_params('fontsize')
            default_style.update(label_style)
            default_style['alpha'] = default_style.get('alpha', 0.8)

            """
            Get the x and y offsets for the new legend.
            Then, draw the line first and the annotation second.
            """
            offset = self._get_offset(transform=axes.transAxes)
            linespacing = util.get_linespacing(figure, axes, transform=axes.transAxes, **default_style)
            y = 1.05
            if axes.xaxis.get_label_position() == 'top':
                y += self.drawable._get_xlabel(transform=axes.transAxes).height * 2

                xtick_labels_bb = self.drawable._get_xtick_labels(transform=axes.transAxes)
                if xtick_labels_bb:
                    y += max(xtick_labels_bb, key=lambda bb: bb.height).height * 2

            visual = f(self, *args, offset=offset, y=y, linespacing=linespacing, **kwargs)

            """
            Calculate the offset of the annotation.
            """
            if visual:
                offset = util.get_bb(figure, axes, visual, transform=axes.transAxes).x1 + 0.00625
            annotation = self.draw_annotation(label, offset, y, **default_style)

            """
            If need be, create a new line for the legend.
            """
            if annotation.get_virtual_bb(transform=axes.transAxes).x1 > 1:
                self._newline(visual, annotation, linespacing)
            else:
                self.lines[-1].append((visual, annotation))

            self.drawable.redraw()
            return (visual, annotation)
Example #5
0
    def draw(self,
             annotation,
             wordspacing=0.005,
             lineheight=1.25,
             align='left',
             with_legend=True,
             lpad=0,
             rpad=0,
             tpad=0,
             *args,
             **kwargs):
        """
        Draw the text annotation visualization.

        The method expects, at least, the text annotation.
        You can pass on the text as a string, or segment the text into tokens yourself and pass them on as a ``list``.

        You can also split the text into words and pass them on as ``dict`` instances to style them individually.
        Dictionaries should have the following format:

        .. code-block:: python

            {
              'text': 'token',
              'style': { 'facecolor': 'None' },
              'label': None
            }

        Of these keys, only the ``text`` is required.

        You can use the ``style`` to override the general styling options, which you can specify as ``kwargs``.
        Once again, the accepted styling options are those supported by the `matplotlib.text.Text <https://matplotlib.org/3.2.2/api/text_api.html#matplotlib.text.Text>`_ class.

        Use the ``kwargs`` as a general style, and the dictionary's ``style`` as a specific style for each word.
        If you specify a ``kwargs`` styling option, but it is missing from the dictionary's ``style``, the general style is used.

        .. note::

            For example, imagine you specify the text ``color`` to be ``blue`` and the ``fontsize`` to be ``12`` in the ``**kwargs``.
            If in the dictionary's ``style`` of a particular word you set the ``color`` to be ``red``, its color will be ``red``.
            However, since the ``fontsize`` is not specified, it will use the general font size: ``12``.

        The last key is the ``label``.
        If you set a ``label``, Multiplex automatically creates a legend for you.

        :param annotation: The text annotation.
                     The visualization expects a string, a `list` of tokens, or a `list` of `dict` instances as shown above.
        :type annotation: str or list of str or list of dict
        :param wordspacing: The space between words.
        :type wordspacing: float
        :param lineheight: The space between lines.
        :type lineheight: float
        :param align: The text's alignment.
                      Possible values:

                          - ``left``
                          - ``center``
                          - ``right``
                          - ``justify``
                          - ``justify-start`` (or ``justify-left``)
                          - ``justify-center``
                          - ``justify-end`` or (``justify-right``)
        :type align: str
        :param with_legend: A boolean indicating whether the visualization should create a legend when it finds labels.
        :type with_legend: bool
        :param lpad: The left padding as a fraction of the plot.
                     The range is expected to be between 0 and 1.
        :type lpad: float
        :param rpad: The right padding as a fraction of the plot.
                     The range is expected to be between 0 and 1.
        :type rpad: float
        :param tpad: The top padding as a percentage of the plot.
        :type tpad: float

        :return: The drawn lines.
                 Each line is made up of tuples, and each tuple is made up of:

                     1. The list of legend labels in the line, and
                     2. The list of tokens drawn in the line.
        :rtype: list of tuple

        :raises ValueError: When the left padding is not between 0 and 1.
        :raises ValueError: When the right padding is not between 0 and 1.
        :raises ValueError: When the left padding and the right padding combined are larger than 1.
        """

        figure = self.drawable.figure
        axes = self.drawable.axes
        """
        Validate the arguments.
        All padding arguments should be non-negative.
        The left-padding and the right-padding should not overlap.
        """
        if not 0 <= lpad <= 1:
            raise ValueError(
                "The left padding should be between 0 and 1, received %d" %
                lpad)

        if not 0 <= rpad <= 1:
            raise ValueError(
                "The right padding should be between 0 and 1, received %d" %
                rpad)

        if lpad + rpad >= 1:
            raise ValueError(
                "The left and right padding should not overlap, received %d left padding and %d right padding"
                % (lpad, rpad))
        """
        Gradually convert text inputs to dictionary inputs: from `str` to `list`, and from `list` to `dict`.
        """
        tokens = annotation.split() if type(annotation) is str else annotation
        tokens = [{
            'text': token
        } if type(token) is str else token for token in tokens]
        """
        Draw the text as an annotation first.
        """
        annotation = Annotation(self.drawable)
        lines = annotation.draw(tokens, (lpad, axes.get_xlim()[1] - rpad),
                                0,
                                wordspacing=wordspacing,
                                lineheight=lineheight,
                                align=align,
                                va='top',
                                *args,
                                **kwargs)
        """
        Draw a legend if it is requested.
        """
        linespacing = util.get_linespacing(figure, axes, wordspacing, *args,
                                           **kwargs)
        labels = self._draw_legend(
            tokens, lines, wordspacing, linespacing, *args, **
            kwargs) if with_legend else [[]] * len(lines)
        """
        The entire visualization is shifted so that the legends start at x-coordinate 0.
        This way, the title is aligned with the visualization.
        This process is meant to tighten the layout.
        The axes is turned off since it has no purpose, and the y-limit is re-calculated.
        """
        drawn_lines = list(zip(labels, lines))
        self._tighten(drawn_lines)
        axes.axis('off')
        axes.set_ylim(-len(lines) * linespacing, tpad + linespacing)

        return drawn_lines