patmalcolm91
patmalcolm91

Reputation: 51

How to create lines with dash spacing and width in data units?

I'm attempting to subclass matplotlib.lines.Line2D to create lines whose width and dash spacings are in the same coordinates as the data being plotted. I've expanded on this answer to try to support dashed lines (with the dash spacings also being in data units) and have the following code currently:

import matplotlib.pyplot as plt
from matplotlib.lines import Line2D

class LineDataUnits(Line2D):
    """
    A Line2D object, but with the linewidth and dash properties defined in data coordinates.
    """
    def __init__(self, *args, **kwargs):
        _lw_data = kwargs.pop("linewidth", 1)
        _dashes_data = kwargs.pop("dashes", (1,))
        super().__init__(*args, **kwargs)
        self._lw_data = _lw_data
        self._dashes_data = _dashes_data
        self._dashOffset = 0

    def _get_lw(self):
        if self.axes is not None:
            ppd = 72./self.axes.figure.dpi
            trans = self.axes.transData.transform
            return ((trans((1, self._lw_data))-trans((0, 0)))*ppd)[1]
        else:
            return 1

    def _set_lw(self, lw):
        self._lw_data = lw

    def _get_dashes(self):
        if self.axes is not None:
            ppd = 72./self.axes.figure.dpi
            trans = self.axes.transData.transform
            return tuple([((trans((1, dash_data))-trans((0, 0)))*ppd)[1] for dash_data in self._dashes_data])
        else:
            return tuple((1, 0))

    def _set_dashes(self, dashes):
        self._dashes_data = dashes

    _linewidth = property(_get_lw, _set_lw)
    _dashSeq = property(_get_dashes, _set_dashes)


if __name__ == "__main__":
    fig, ax = plt.subplots()
    line1 = Line2D([0, 10], [3, 3], linewidth=10, color="blue", dashes=(3, 3), dash_capstyle="round")
    line2 = LineDataUnits([0, 10], [6, 6], linewidth=0.5, color="red", dashes=(1, 1), dash_capstyle="round")
    print("dash_capstyle:", line2.get_dash_capstyle())
    ax.add_line(line1)
    ax.add_line(line2)
    ax.set_xticks(range(10))
    ax.grid()
    ax.set_xlim(0, 10)
    ax.set_ylim(0, 10)
    plt.show()

Which yields the following graph: graph of dashed Line2D and LineDataUnits

When I zoom in and out, the dash spacings do seem to remain constant in the data units, however as you can see in the graph, their values are not calculated correctly. Furthermore, it appears setting the dash_capstyle parameter in this class seems to have no effect.

Any help would be greatly appreciated.

Upvotes: 3

Views: 259

Answers (1)

patmalcolm91
patmalcolm91

Reputation: 51

After much more experimentation, I managed to get it working properly. Firstly, even though the line was being plotted with dashes, the Line2D still had its _linestyle parameter set to "-" (solid), which caused the cap style argument to be ignored. Secondly, there was an error in my conversion from data units to points. With the changes below, it now works perfectly.

# in __init__()
    super().__init__(*args, **kwargs):
+   self.set_linestyle("--")
    self._lw_data = _lw_data

# in _get_dashes()
-   return tuple([((trans((1, dash_data))-trans((0, 0)))*ppd)[1] for dash_data in self._dashes_data])
+   dpu = (trans((1, 1)) - trans((0, 0)))[0]
+   return tuple([u*dpu*ppd for u in self._dashes_data])

Upvotes: 2

Related Questions