0

I coming from java world, and currently trying to learn python for the sake of learning matplotlib. As a java programmer, the following snippet looked very strange.

x = np.linspace(0, 2*np.pi, 400)
y = np.sin(x**2)

f, (ax1, ax2) = plt.subplots(1, 2, sharex='all', sharey='all')
ax1.plot(x, y)
ax1.set_title('Sharing Y axis')
ax2.scatter(x, y)

plt.show()

I was wondering how the plt.show() can read the configurations set on the axes since we doesn't pass any arguments to it. Can someone explain the rough idea how this behavior might be achieved?

EDIT

Since being asked by @ImportanceOfBeingErnest, I might be well clarify my intention. My question is not about how the mechanics that pyplot creating an image, my question is more to how the axes configuration is visible to the pyplot. John Zwinck has attemped in the right direction on answering my question, I myself had come into conclusion that it must be something related to global state. But glancing on the internal implementation of subplots, it gives me confusion.

Let's take some part of the internal implementation of pyplot :

def subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True,
             subplot_kw=None, gridspec_kw=None, **fig_kw):
    fig = figure(**fig_kw)
    axs = fig.subplots(nrows=nrows, ncols=ncols, sharex=sharex, sharey=sharey,
                       squeeze=squeeze, subplot_kw=subplot_kw,
                       gridspec_kw=gridspec_kw)
    return fig, axs

def subplots(self, nrows=1, ncols=1, sharex=False, sharey=False,
                 squeeze=True, subplot_kw=None, gridspec_kw=None):
            if isinstance(sharex, bool):
            sharex = "all" if sharex else "none"
        if isinstance(sharey, bool):
            sharey = "all" if sharey else "none"
        share_values = ["all", "row", "col", "none"]
        if sharex not in share_values:
            if isinstance(sharex, int):
                warnings.warn(
                    "sharex argument to subplots() was an integer. "
                    "Did you intend to use subplot() (without 's')?")

            raise ValueError("sharex [%s] must be one of %s" %
                             (sharex, share_values))
        if sharey not in share_values:
            raise ValueError("sharey [%s] must be one of %s" %
                             (sharey, share_values))
        if subplot_kw is None:
            subplot_kw = {}
        if gridspec_kw is None:
            gridspec_kw = {}

        if self.get_constrained_layout():
            gs = GridSpec(nrows, ncols, figure=self, **gridspec_kw)
        else:
            # this should turn constrained_layout off if we don't want it
            gs = GridSpec(nrows, ncols, figure=None, **gridspec_kw)

        # Create array to hold all axes.
        axarr = np.empty((nrows, ncols), dtype=object)
        for row in range(nrows):
            for col in range(ncols):
                shared_with = {"none": None, "all": axarr[0, 0],
                               "row": axarr[row, 0], "col": axarr[0, col]}
                subplot_kw["sharex"] = shared_with[sharex]
                subplot_kw["sharey"] = shared_with[sharey]
                axarr[row, col] = self.add_subplot(gs[row, col], **subplot_kw)

        # turn off redundant tick labeling
        if sharex in ["col", "all"]:
            # turn off all but the bottom row
            for ax in axarr[:-1, :].flat:
                ax.xaxis.set_tick_params(which='both',
                                         labelbottom=False, labeltop=False)
                ax.xaxis.offsetText.set_visible(False)
        if sharey in ["row", "all"]:
            # turn off all but the first column
            for ax in axarr[:, 1:].flat:
                ax.yaxis.set_tick_params(which='both',
                                         labelleft=False, labelright=False)
                ax.yaxis.offsetText.set_visible(False)

        if squeeze:
            # Discarding unneeded dimensions that equal 1.  If we only have one
            # subplot, just return it instead of a 1-element array.
            return axarr.item() if axarr.size == 1 else axarr.squeeze()
        else:
            # Returned axis array will be always 2-d, even if nrows=ncols=1.
            return axarr

For all that I aware of, the axes is created as a numpy array in this line

axarr = np.empty((nrows, ncols), dtype=object)

But this is a local variable and doesn't indicate how the state of the axes that had been changed on the main program can be visible to the pyplot. This is my actual question.

Mycotina
  • 369
  • 3
  • 11
  • This is pretty similar to [How does plt.show() know what to show?](https://stackoverflow.com/questions/48398923/how-does-plt-show-know-what-to-show). Maybe you can update the question with a more specific details of what you want to know. – ImportanceOfBeingErnest Sep 09 '18 at 11:42

2 Answers2

2

Matplotlib's pyplot (which is what you've imported as plt) is a weird, stateful thing which is meant to emulate Matlab. It is effectively a global variable (or a wrapper for a collection of global variables, e.g. plt.gca() to "get current axes").

A lot of people find it easier to use, but it is quite opposite to what you'd expect coming from Java. If you prefer, it is perfectly possible to use Matplotlib without pyplot/plt, in which case the code will probably look more conventional to those coming from object oriented programming backgrounds.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
1

First of all the line fig = figure(**fig_kw) calls the pyplot.figure function. This registers the figure inside the pyplot state machine.

fig is a matplotlib.figure.Figure instance. Next, it's subplots method is called. This will essentially create an array and fill it will matplotlib.axes.Axes instances. Those are created with self.add_subplot.

add_subplot will initialize the axes and store it as part of the figure's fig.axes array.

So in total you have pyplot which stores the figures and each figure stores the axes in it. When calling plt.show() it will basically loop through all figures and show them. For each figure, all axes inside fig.axes will be drawn. If you have previously manipulated any of the axes by calling any of their methods, those will of course be taken into account because you manipulated exactly the axes object that is later also drawn.

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712