This is a restatement of a feature request I posted at github, which was closed with a suggestion that it be discussed here (https://github.com/matplotlib/matplotlib/issues/18267) instead, so that a specific API suggestion can be made.
Circle, CirclePolygon, regular polygon etc patches have, for example, a radius parameter which may not properly apply to both Axes simultaneously (i.e. which does not have a well defined unit). The frequent example would be Axes on a datetime plot. On such a plot the X axis data may vary by <<1 as the Y axis data may vary by >> 1.
Further, the necessary transforms are hard to come by and not well documented.
I did not feel qualified to suggest specific API improvements to better support putting certain kinds of patches on non-uniform (aspect ratio != 1) Axes, to include:
- Allowing parameterization of patches such that the parameter’s transform can be inferred or explicitly specified. E.g.,
CirclePolygon(..., radius_xdata=None, ...)could specify a radius in x-axis units.
- Providing easy access to transforms so that positioning and sizing a CirclePolygon using data units, into Axes space is more easily achieved. E.g.,
data_to_axisin below source).
- Is the ability to add transforms to each other (per that code) actually documented anywhere? I found it in a stackoverflow answer.
Meanwhile, other patches (such as Rectangle) operate in data-space just fine, creating the impression patches should generally function well in data space.
In the code sample below, only the c1 circle displays properly, and it must be defined in axes space (0…1,0…1), which requires transforming the data coordinate to axes space, which requires knowing about the + operator on transform objects, and inverted() … PITA.
c3_... failed attempts which show what people like me may try. The result, given the datetime X axis and much larger Y range, is a point, a line, or nothing at all, which can be very confusing and a pain to debug.
# Create new plot fig = plt.figure() ax = fig.add_subplot(111) # Create rectangle x coordinates startTime = datetime(year=2020,month=7,day=6,hour=6) endTime = startTime + timedelta(seconds = 600) # convert to matplotlib date representation start = mdates.date2num(startTime) end = mdates.date2num(endTime) middle = (start+end)/2 width = end - start # assign date locator / formatter to the x-axis to get proper labels locator = mdates.AutoDateLocator(minticks=3) formatter = mdates.AutoDateFormatter(locator) ax.xaxis.set_major_locator(locator) ax.xaxis.set_major_formatter(formatter) # set the limits plt.xlim([start-width, end+width]) plt.ylim([8000, 10000]) xlim = ax.get_xlim() ylim = ax.get_ylim() xspan = xlim-xlim yspan = ylim-ylim axis_to_data = ax.transAxes + ax.transData.inverted() data_to_axis = axis_to_data.inverted() trans = data_to_axis.transform rect = mpatches.Rectangle((start, 9000), width, 500, color='yellow') circle_center = (middle,9250) circle_radius_datax = xspan/10 circle_radius_datay = yspan/10 circle_radius_ax = trans((xlim+circle_radius_datax,0)) # will "by definition" be 0.1 bc datax=xspan/10 assert round(circle_radius_ax/0.1)==1 # Plot rectangle c1 = mpatches.CirclePolygon(trans(circle_center),radius=circle_radius_ax,resolution=6,color='red',transform=ax.transAxes) c2_d = mpatches.CirclePolygon(circle_center,radius=circle_radius_datax,resolution=6,color='green',transform=ax.transData) c3_d = mpatches.CirclePolygon(circle_center,radius=circle_radius_datay,resolution=6,color='blue',transform=ax.transData) c2_dx = mpatches.CirclePolygon(circle_center,radius=circle_radius_datax,resolution=6,color='green',transform=data_to_axis) c3_dx = mpatches.CirclePolygon(circle_center,radius=circle_radius_datay,resolution=6,color='blue',transform=data_to_axis) ax.add_patch(rect) ax.add_patch(c2_d) ax.add_patch(c3_d) ax.add_patch(c2_dx) ax.add_patch(c3_dx) ax.add_patch(c1)