Fixed database typo and removed unnecessary class identifier.
This commit is contained in:
parent
00ad49a143
commit
45fb349a7d
5098 changed files with 952558 additions and 85 deletions
1474
venv/Lib/site-packages/matplotlib/__init__.py
Normal file
1474
venv/Lib/site-packages/matplotlib/__init__.py
Normal file
File diff suppressed because it is too large
Load diff
Binary file not shown.
Binary file not shown.
BIN
venv/Lib/site-packages/matplotlib/__pycache__/_cm.cpython-36.pyc
Normal file
BIN
venv/Lib/site-packages/matplotlib/__pycache__/_cm.cpython-36.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
venv/Lib/site-packages/matplotlib/__pycache__/afm.cpython-36.pyc
Normal file
BIN
venv/Lib/site-packages/matplotlib/__pycache__/afm.cpython-36.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
venv/Lib/site-packages/matplotlib/__pycache__/cm.cpython-36.pyc
Normal file
BIN
venv/Lib/site-packages/matplotlib/__pycache__/cm.cpython-36.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
262
venv/Lib/site-packages/matplotlib/_animation_data.py
Normal file
262
venv/Lib/site-packages/matplotlib/_animation_data.py
Normal file
|
@ -0,0 +1,262 @@
|
|||
# Javascript template for HTMLWriter
|
||||
JS_INCLUDE = """
|
||||
<link rel="stylesheet"
|
||||
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
|
||||
<script language="javascript">
|
||||
function isInternetExplorer() {
|
||||
ua = navigator.userAgent;
|
||||
/* MSIE used to detect old browsers and Trident used to newer ones*/
|
||||
return ua.indexOf("MSIE ") > -1 || ua.indexOf("Trident/") > -1;
|
||||
}
|
||||
|
||||
/* Define the Animation class */
|
||||
function Animation(frames, img_id, slider_id, interval, loop_select_id){
|
||||
this.img_id = img_id;
|
||||
this.slider_id = slider_id;
|
||||
this.loop_select_id = loop_select_id;
|
||||
this.interval = interval;
|
||||
this.current_frame = 0;
|
||||
this.direction = 0;
|
||||
this.timer = null;
|
||||
this.frames = new Array(frames.length);
|
||||
|
||||
for (var i=0; i<frames.length; i++)
|
||||
{
|
||||
this.frames[i] = new Image();
|
||||
this.frames[i].src = frames[i];
|
||||
}
|
||||
var slider = document.getElementById(this.slider_id);
|
||||
slider.max = this.frames.length - 1;
|
||||
if (isInternetExplorer()) {
|
||||
// switch from oninput to onchange because IE <= 11 does not conform
|
||||
// with W3C specification. It ignores oninput and onchange behaves
|
||||
// like oninput. In contrast, Mircosoft Edge behaves correctly.
|
||||
slider.setAttribute('onchange', slider.getAttribute('oninput'));
|
||||
slider.setAttribute('oninput', null);
|
||||
}
|
||||
this.set_frame(this.current_frame);
|
||||
}
|
||||
|
||||
Animation.prototype.get_loop_state = function(){
|
||||
var button_group = document[this.loop_select_id].state;
|
||||
for (var i = 0; i < button_group.length; i++) {
|
||||
var button = button_group[i];
|
||||
if (button.checked) {
|
||||
return button.value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Animation.prototype.set_frame = function(frame){
|
||||
this.current_frame = frame;
|
||||
document.getElementById(this.img_id).src =
|
||||
this.frames[this.current_frame].src;
|
||||
document.getElementById(this.slider_id).value = this.current_frame;
|
||||
}
|
||||
|
||||
Animation.prototype.next_frame = function()
|
||||
{
|
||||
this.set_frame(Math.min(this.frames.length - 1, this.current_frame + 1));
|
||||
}
|
||||
|
||||
Animation.prototype.previous_frame = function()
|
||||
{
|
||||
this.set_frame(Math.max(0, this.current_frame - 1));
|
||||
}
|
||||
|
||||
Animation.prototype.first_frame = function()
|
||||
{
|
||||
this.set_frame(0);
|
||||
}
|
||||
|
||||
Animation.prototype.last_frame = function()
|
||||
{
|
||||
this.set_frame(this.frames.length - 1);
|
||||
}
|
||||
|
||||
Animation.prototype.slower = function()
|
||||
{
|
||||
this.interval /= 0.7;
|
||||
if(this.direction > 0){this.play_animation();}
|
||||
else if(this.direction < 0){this.reverse_animation();}
|
||||
}
|
||||
|
||||
Animation.prototype.faster = function()
|
||||
{
|
||||
this.interval *= 0.7;
|
||||
if(this.direction > 0){this.play_animation();}
|
||||
else if(this.direction < 0){this.reverse_animation();}
|
||||
}
|
||||
|
||||
Animation.prototype.anim_step_forward = function()
|
||||
{
|
||||
this.current_frame += 1;
|
||||
if(this.current_frame < this.frames.length){
|
||||
this.set_frame(this.current_frame);
|
||||
}else{
|
||||
var loop_state = this.get_loop_state();
|
||||
if(loop_state == "loop"){
|
||||
this.first_frame();
|
||||
}else if(loop_state == "reflect"){
|
||||
this.last_frame();
|
||||
this.reverse_animation();
|
||||
}else{
|
||||
this.pause_animation();
|
||||
this.last_frame();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Animation.prototype.anim_step_reverse = function()
|
||||
{
|
||||
this.current_frame -= 1;
|
||||
if(this.current_frame >= 0){
|
||||
this.set_frame(this.current_frame);
|
||||
}else{
|
||||
var loop_state = this.get_loop_state();
|
||||
if(loop_state == "loop"){
|
||||
this.last_frame();
|
||||
}else if(loop_state == "reflect"){
|
||||
this.first_frame();
|
||||
this.play_animation();
|
||||
}else{
|
||||
this.pause_animation();
|
||||
this.first_frame();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Animation.prototype.pause_animation = function()
|
||||
{
|
||||
this.direction = 0;
|
||||
if (this.timer){
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
Animation.prototype.play_animation = function()
|
||||
{
|
||||
this.pause_animation();
|
||||
this.direction = 1;
|
||||
var t = this;
|
||||
if (!this.timer) this.timer = setInterval(function() {
|
||||
t.anim_step_forward();
|
||||
}, this.interval);
|
||||
}
|
||||
|
||||
Animation.prototype.reverse_animation = function()
|
||||
{
|
||||
this.pause_animation();
|
||||
this.direction = -1;
|
||||
var t = this;
|
||||
if (!this.timer) this.timer = setInterval(function() {
|
||||
t.anim_step_reverse();
|
||||
}, this.interval);
|
||||
}
|
||||
</script>
|
||||
"""
|
||||
|
||||
|
||||
# Style definitions for the HTML template
|
||||
STYLE_INCLUDE = """
|
||||
<style>
|
||||
.animation {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
input[type=range].anim-slider {
|
||||
width: 374px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.anim-buttons {
|
||||
margin: 8px 0px;
|
||||
}
|
||||
.anim-buttons button {
|
||||
padding: 0;
|
||||
width: 36px;
|
||||
}
|
||||
.anim-state label {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.anim-state input {
|
||||
margin: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
"""
|
||||
|
||||
|
||||
# HTML template for HTMLWriter
|
||||
DISPLAY_TEMPLATE = """
|
||||
<div class="animation">
|
||||
<img id="_anim_img{id}">
|
||||
<div class="anim-controls">
|
||||
<input id="_anim_slider{id}" type="range" class="anim-slider"
|
||||
name="points" min="0" max="1" step="1" value="0"
|
||||
oninput="anim{id}.set_frame(parseInt(this.value));"></input>
|
||||
<div class="anim-buttons">
|
||||
<button title="Decrease speed" onclick="anim{id}.slower()">
|
||||
<i class="fa fa-minus"></i></button>
|
||||
<button title="First frame" onclick="anim{id}.first_frame()">
|
||||
<i class="fa fa-fast-backward"></i></button>
|
||||
<button title="Previous frame" onclick="anim{id}.previous_frame()">
|
||||
<i class="fa fa-step-backward"></i></button>
|
||||
<button title="Play backwards" onclick="anim{id}.reverse_animation()">
|
||||
<i class="fa fa-play fa-flip-horizontal"></i></button>
|
||||
<button title="Pause" onclick="anim{id}.pause_animation()">
|
||||
<i class="fa fa-pause"></i></button>
|
||||
<button title="Play" onclick="anim{id}.play_animation()">
|
||||
<i class="fa fa-play"></i></button>
|
||||
<button title="Next frame" onclick="anim{id}.next_frame()">
|
||||
<i class="fa fa-step-forward"></i></button>
|
||||
<button title="Last frame" onclick="anim{id}.last_frame()">
|
||||
<i class="fa fa-fast-forward"></i></button>
|
||||
<button title="Increase speed" onclick="anim{id}.faster()">
|
||||
<i class="fa fa-plus"></i></button>
|
||||
</div>
|
||||
<form title="Repetition mode" action="#n" name="_anim_loop_select{id}"
|
||||
class="anim-state">
|
||||
<input type="radio" name="state" value="once" id="_anim_radio1_{id}"
|
||||
{once_checked}>
|
||||
<label for="_anim_radio1_{id}">Once</label>
|
||||
<input type="radio" name="state" value="loop" id="_anim_radio2_{id}"
|
||||
{loop_checked}>
|
||||
<label for="_anim_radio2_{id}">Loop</label>
|
||||
<input type="radio" name="state" value="reflect" id="_anim_radio3_{id}"
|
||||
{reflect_checked}>
|
||||
<label for="_anim_radio3_{id}">Reflect</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script language="javascript">
|
||||
/* Instantiate the Animation class. */
|
||||
/* The IDs given should match those used in the template above. */
|
||||
(function() {{
|
||||
var img_id = "_anim_img{id}";
|
||||
var slider_id = "_anim_slider{id}";
|
||||
var loop_select_id = "_anim_loop_select{id}";
|
||||
var frames = new Array({Nframes});
|
||||
{fill_frames}
|
||||
|
||||
/* set a timeout to make sure all the above elements are created before
|
||||
the object is initialized. */
|
||||
setTimeout(function() {{
|
||||
anim{id} = new Animation(frames, img_id, slider_id, {interval},
|
||||
loop_select_id);
|
||||
}}, 0);
|
||||
}})()
|
||||
</script>
|
||||
"""
|
||||
|
||||
|
||||
INCLUDED_FRAMES = """
|
||||
for (var i=0; i<{Nframes}; i++){{
|
||||
frames[i] = "{frame_dir}/frame" + ("0000000" + i).slice(-7) +
|
||||
".{frame_format}";
|
||||
}}
|
||||
"""
|
1434
venv/Lib/site-packages/matplotlib/_cm.py
Normal file
1434
venv/Lib/site-packages/matplotlib/_cm.py
Normal file
File diff suppressed because it is too large
Load diff
2071
venv/Lib/site-packages/matplotlib/_cm_listed.py
Normal file
2071
venv/Lib/site-packages/matplotlib/_cm_listed.py
Normal file
File diff suppressed because it is too large
Load diff
1147
venv/Lib/site-packages/matplotlib/_color_data.py
Normal file
1147
venv/Lib/site-packages/matplotlib/_color_data.py
Normal file
File diff suppressed because it is too large
Load diff
662
venv/Lib/site-packages/matplotlib/_constrained_layout.py
Normal file
662
venv/Lib/site-packages/matplotlib/_constrained_layout.py
Normal file
|
@ -0,0 +1,662 @@
|
|||
"""
|
||||
Adjust subplot layouts so that there are no overlapping axes or axes
|
||||
decorations. All axes decorations are dealt with (labels, ticks, titles,
|
||||
ticklabels) and some dependent artists are also dealt with (colorbar, suptitle,
|
||||
legend).
|
||||
|
||||
Layout is done via `~matplotlib.gridspec`, with one constraint per gridspec,
|
||||
so it is possible to have overlapping axes if the gridspecs overlap (i.e.
|
||||
using `~matplotlib.gridspec.GridSpecFromSubplotSpec`). Axes placed using
|
||||
``figure.subplots()`` or ``figure.add_subplots()`` will participate in the
|
||||
layout. Axes manually placed via ``figure.add_axes()`` will not.
|
||||
|
||||
See Tutorial: :doc:`/tutorials/intermediate/constrainedlayout_guide`
|
||||
"""
|
||||
|
||||
# Development Notes:
|
||||
|
||||
# What gets a layoutbox:
|
||||
# - figure
|
||||
# - gridspec
|
||||
# - subplotspec
|
||||
# EITHER:
|
||||
# - axes + pos for the axes (i.e. the total area taken by axis and
|
||||
# the actual "position" argument that needs to be sent to
|
||||
# ax.set_position.)
|
||||
# - The axes layout box will also encompass the legend, and that is
|
||||
# how legends get included (axes legends, not figure legends)
|
||||
# - colorbars are siblings of the axes if they are single-axes
|
||||
# colorbars
|
||||
# OR:
|
||||
# - a gridspec can be inside a subplotspec.
|
||||
# - subplotspec
|
||||
# EITHER:
|
||||
# - axes...
|
||||
# OR:
|
||||
# - gridspec... with arbitrary nesting...
|
||||
# - colorbars are siblings of the subplotspecs if they are multi-axes
|
||||
# colorbars.
|
||||
# - suptitle:
|
||||
# - right now suptitles are just stacked atop everything else in figure.
|
||||
# Could imagine suptitles being gridspec suptitles, but not implemented
|
||||
#
|
||||
# Todo: AnchoredOffsetbox connected to gridspecs or axes. This would
|
||||
# be more general way to add extra-axes annotations.
|
||||
|
||||
import logging
|
||||
|
||||
import numpy as np
|
||||
|
||||
import matplotlib.cbook as cbook
|
||||
import matplotlib._layoutbox as layoutbox
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _spans_overlap(span0, span1):
|
||||
return span0.start in span1 or span1.start in span0
|
||||
|
||||
|
||||
def _axes_all_finite_sized(fig):
|
||||
"""Return whether all axes in the figure have a finite width and height."""
|
||||
for ax in fig.axes:
|
||||
if ax._layoutbox is not None:
|
||||
newpos = ax._poslayoutbox.get_rect()
|
||||
if newpos[2] <= 0 or newpos[3] <= 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
######################################################
|
||||
def do_constrained_layout(fig, renderer, h_pad, w_pad,
|
||||
hspace=None, wspace=None):
|
||||
"""
|
||||
Do the constrained_layout. Called at draw time in
|
||||
``figure.constrained_layout()``
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fig : Figure
|
||||
is the ``figure`` instance to do the layout in.
|
||||
|
||||
renderer : Renderer
|
||||
the renderer to use.
|
||||
|
||||
h_pad, w_pad : float
|
||||
are in figure-normalized units, and are a padding around the axes
|
||||
elements.
|
||||
|
||||
hspace, wspace : float
|
||||
are in fractions of the subplot sizes.
|
||||
|
||||
"""
|
||||
|
||||
# Steps:
|
||||
#
|
||||
# 1. get a list of unique gridspecs in this figure. Each gridspec will be
|
||||
# constrained separately.
|
||||
# 2. Check for gaps in the gridspecs. i.e. if not every axes slot in the
|
||||
# gridspec has been filled. If empty, add a ghost axis that is made so
|
||||
# that it cannot be seen (though visible=True). This is needed to make
|
||||
# a blank spot in the layout.
|
||||
# 3. Compare the tight_bbox of each axes to its `position`, and assume that
|
||||
# the difference is the space needed by the elements around the edge of
|
||||
# the axes (decorations) like the title, ticklabels, x-labels, etc. This
|
||||
# can include legends who overspill the axes boundaries.
|
||||
# 4. Constrain gridspec elements to line up:
|
||||
# a) if colnum0 != colnumC, the two subplotspecs are stacked next to
|
||||
# each other, with the appropriate order.
|
||||
# b) if colnum0 == colnumC, line up the left or right side of the
|
||||
# _poslayoutbox (depending if it is the min or max num that is equal).
|
||||
# c) do the same for rows...
|
||||
# 5. The above doesn't constrain relative sizes of the _poslayoutboxes
|
||||
# at all, and indeed zero-size is a solution that the solver often finds
|
||||
# more convenient than expanding the sizes. Right now the solution is to
|
||||
# compare subplotspec sizes (i.e. drowsC and drows0) and constrain the
|
||||
# larger _poslayoutbox to be larger than the ratio of the sizes. i.e. if
|
||||
# drows0 > drowsC, then ax._poslayoutbox > axc._poslayoutbox*drowsC/drows0.
|
||||
# This works fine *if* the decorations are similar between the axes.
|
||||
# If the larger subplotspec has much larger axes decorations, then the
|
||||
# constraint above is incorrect.
|
||||
#
|
||||
# We need the greater than in the above, in general, rather than an equals
|
||||
# sign. Consider the case of the left column having 2 rows, and the right
|
||||
# column having 1 row. We want the top and bottom of the _poslayoutboxes
|
||||
# to line up. So that means if there are decorations on the left column
|
||||
# axes they will be smaller than half as large as the right hand axis.
|
||||
#
|
||||
# This can break down if the decoration size for the right hand axis (the
|
||||
# margins) is very large. There must be a math way to check for this case.
|
||||
|
||||
invTransFig = fig.transFigure.inverted().transform_bbox
|
||||
|
||||
# list of unique gridspecs that contain child axes:
|
||||
gss = set()
|
||||
for ax in fig.axes:
|
||||
if hasattr(ax, 'get_subplotspec'):
|
||||
gs = ax.get_subplotspec().get_gridspec()
|
||||
if gs._layoutbox is not None:
|
||||
gss.add(gs)
|
||||
if len(gss) == 0:
|
||||
cbook._warn_external('There are no gridspecs with layoutboxes. '
|
||||
'Possibly did not call parent GridSpec with the'
|
||||
' figure= keyword')
|
||||
|
||||
if fig._layoutbox.constrained_layout_called < 1:
|
||||
for gs in gss:
|
||||
# fill in any empty gridspec slots w/ ghost axes...
|
||||
_make_ghost_gridspec_slots(fig, gs)
|
||||
|
||||
for _ in range(2):
|
||||
# do the algorithm twice. This has to be done because decorators
|
||||
# change size after the first re-position (i.e. x/yticklabels get
|
||||
# larger/smaller). This second reposition tends to be much milder,
|
||||
# so doing twice makes things work OK.
|
||||
for ax in fig.axes:
|
||||
_log.debug(ax._layoutbox)
|
||||
if ax._layoutbox is not None:
|
||||
# make margins for each layout box based on the size of
|
||||
# the decorators.
|
||||
_make_layout_margins(ax, renderer, h_pad, w_pad)
|
||||
|
||||
# do layout for suptitle.
|
||||
suptitle = fig._suptitle
|
||||
do_suptitle = (suptitle is not None and
|
||||
suptitle._layoutbox is not None and
|
||||
suptitle.get_in_layout())
|
||||
if do_suptitle:
|
||||
bbox = invTransFig(
|
||||
suptitle.get_window_extent(renderer=renderer))
|
||||
height = bbox.height
|
||||
if np.isfinite(height):
|
||||
# reserve at top of figure include an h_pad above and below
|
||||
suptitle._layoutbox.edit_height(height + h_pad * 2)
|
||||
|
||||
# OK, the above lines up ax._poslayoutbox with ax._layoutbox
|
||||
# now we need to
|
||||
# 1) arrange the subplotspecs. We do it at this level because
|
||||
# the subplotspecs are meant to contain other dependent axes
|
||||
# like colorbars or legends.
|
||||
# 2) line up the right and left side of the ax._poslayoutbox
|
||||
# that have the same subplotspec maxes.
|
||||
|
||||
if fig._layoutbox.constrained_layout_called < 1:
|
||||
# arrange the subplotspecs... This is all done relative to each
|
||||
# other. Some subplotspecs contain axes, and others contain
|
||||
# gridspecs the ones that contain gridspecs are a set proportion
|
||||
# of their parent gridspec. The ones that contain axes are
|
||||
# not so constrained.
|
||||
figlb = fig._layoutbox
|
||||
for child in figlb.children:
|
||||
if child._is_gridspec_layoutbox():
|
||||
# This routine makes all the subplot spec containers
|
||||
# have the correct arrangement. It just stacks the
|
||||
# subplot layoutboxes in the correct order...
|
||||
_arrange_subplotspecs(child, hspace=hspace, wspace=wspace)
|
||||
|
||||
for gs in gss:
|
||||
_align_spines(fig, gs)
|
||||
|
||||
fig._layoutbox.constrained_layout_called += 1
|
||||
fig._layoutbox.update_variables()
|
||||
|
||||
# check if any axes collapsed to zero. If not, don't change positions:
|
||||
if _axes_all_finite_sized(fig):
|
||||
# Now set the position of the axes...
|
||||
for ax in fig.axes:
|
||||
if ax._layoutbox is not None:
|
||||
newpos = ax._poslayoutbox.get_rect()
|
||||
# Now set the new position.
|
||||
# ax.set_position will zero out the layout for
|
||||
# this axis, allowing users to hard-code the position,
|
||||
# so this does the same w/o zeroing layout.
|
||||
ax._set_position(newpos, which='original')
|
||||
if do_suptitle:
|
||||
newpos = suptitle._layoutbox.get_rect()
|
||||
suptitle.set_y(1.0 - h_pad)
|
||||
else:
|
||||
if suptitle is not None and suptitle._layoutbox is not None:
|
||||
suptitle._layoutbox.edit_height(0)
|
||||
else:
|
||||
cbook._warn_external('constrained_layout not applied. At least '
|
||||
'one axes collapsed to zero width or height.')
|
||||
|
||||
|
||||
def _make_ghost_gridspec_slots(fig, gs):
|
||||
"""
|
||||
Check for unoccupied gridspec slots and make ghost axes for these
|
||||
slots... Do for each gs separately. This is a pretty big kludge
|
||||
but shouldn't have too much ill effect. The worst is that
|
||||
someone querying the figure will wonder why there are more
|
||||
axes than they thought.
|
||||
"""
|
||||
nrows, ncols = gs.get_geometry()
|
||||
hassubplotspec = np.zeros(nrows * ncols, dtype=bool)
|
||||
axs = []
|
||||
for ax in fig.axes:
|
||||
if (hasattr(ax, 'get_subplotspec')
|
||||
and ax._layoutbox is not None
|
||||
and ax.get_subplotspec().get_gridspec() == gs):
|
||||
axs += [ax]
|
||||
for ax in axs:
|
||||
ss0 = ax.get_subplotspec()
|
||||
hassubplotspec[ss0.num1:(ss0.num2 + 1)] = True
|
||||
for nn, hss in enumerate(hassubplotspec):
|
||||
if not hss:
|
||||
# this gridspec slot doesn't have an axis so we
|
||||
# make a "ghost".
|
||||
ax = fig.add_subplot(gs[nn])
|
||||
ax.set_visible(False)
|
||||
|
||||
|
||||
def _make_layout_margins(ax, renderer, h_pad, w_pad):
|
||||
"""
|
||||
For each axes, make a margin between the *pos* layoutbox and the
|
||||
*axes* layoutbox be a minimum size that can accommodate the
|
||||
decorations on the axis.
|
||||
"""
|
||||
fig = ax.figure
|
||||
invTransFig = fig.transFigure.inverted().transform_bbox
|
||||
pos = ax.get_position(original=True)
|
||||
try:
|
||||
tightbbox = ax.get_tightbbox(renderer=renderer, for_layout_only=True)
|
||||
except TypeError:
|
||||
tightbbox = ax.get_tightbbox(renderer=renderer)
|
||||
|
||||
if tightbbox is None:
|
||||
bbox = pos
|
||||
else:
|
||||
bbox = invTransFig(tightbbox)
|
||||
|
||||
# this can go wrong:
|
||||
if not (np.isfinite(bbox.width) and np.isfinite(bbox.height)):
|
||||
# just abort, this is likely a bad set of coordinates that
|
||||
# is transitory...
|
||||
return
|
||||
# use stored h_pad if it exists
|
||||
h_padt = ax._poslayoutbox.h_pad
|
||||
if h_padt is None:
|
||||
h_padt = h_pad
|
||||
w_padt = ax._poslayoutbox.w_pad
|
||||
if w_padt is None:
|
||||
w_padt = w_pad
|
||||
ax._poslayoutbox.edit_left_margin_min(-bbox.x0 + pos.x0 + w_padt)
|
||||
ax._poslayoutbox.edit_right_margin_min(bbox.x1 - pos.x1 + w_padt)
|
||||
ax._poslayoutbox.edit_bottom_margin_min(-bbox.y0 + pos.y0 + h_padt)
|
||||
ax._poslayoutbox.edit_top_margin_min(bbox.y1-pos.y1+h_padt)
|
||||
_log.debug('left %f', (-bbox.x0 + pos.x0 + w_pad))
|
||||
_log.debug('right %f', (bbox.x1 - pos.x1 + w_pad))
|
||||
_log.debug('bottom %f', (-bbox.y0 + pos.y0 + h_padt))
|
||||
_log.debug('bbox.y0 %f', bbox.y0)
|
||||
_log.debug('pos.y0 %f', pos.y0)
|
||||
# Sometimes its possible for the solver to collapse
|
||||
# rather than expand axes, so they all have zero height
|
||||
# or width. This stops that... It *should* have been
|
||||
# taken into account w/ pref_width...
|
||||
if fig._layoutbox.constrained_layout_called < 1:
|
||||
ax._poslayoutbox.constrain_height_min(20, strength='weak')
|
||||
ax._poslayoutbox.constrain_width_min(20, strength='weak')
|
||||
ax._layoutbox.constrain_height_min(20, strength='weak')
|
||||
ax._layoutbox.constrain_width_min(20, strength='weak')
|
||||
ax._poslayoutbox.constrain_top_margin(0, strength='weak')
|
||||
ax._poslayoutbox.constrain_bottom_margin(0, strength='weak')
|
||||
ax._poslayoutbox.constrain_right_margin(0, strength='weak')
|
||||
ax._poslayoutbox.constrain_left_margin(0, strength='weak')
|
||||
|
||||
|
||||
def _align_spines(fig, gs):
|
||||
"""
|
||||
- Align right/left and bottom/top spines of appropriate subplots.
|
||||
- Compare size of subplotspec including height and width ratios
|
||||
and make sure that the axes spines are at least as large
|
||||
as they should be.
|
||||
"""
|
||||
# for each gridspec...
|
||||
nrows, ncols = gs.get_geometry()
|
||||
width_ratios = gs.get_width_ratios()
|
||||
height_ratios = gs.get_height_ratios()
|
||||
if width_ratios is None:
|
||||
width_ratios = np.ones(ncols)
|
||||
if height_ratios is None:
|
||||
height_ratios = np.ones(nrows)
|
||||
|
||||
# get axes in this gridspec....
|
||||
axs = [ax for ax in fig.axes
|
||||
if (hasattr(ax, 'get_subplotspec')
|
||||
and ax._layoutbox is not None
|
||||
and ax.get_subplotspec().get_gridspec() == gs)]
|
||||
rowspans = []
|
||||
colspans = []
|
||||
heights = []
|
||||
widths = []
|
||||
|
||||
for ax in axs:
|
||||
ss0 = ax.get_subplotspec()
|
||||
rowspan = ss0.rowspan
|
||||
colspan = ss0.colspan
|
||||
rowspans.append(rowspan)
|
||||
colspans.append(colspan)
|
||||
heights.append(sum(height_ratios[rowspan.start:rowspan.stop]))
|
||||
widths.append(sum(width_ratios[colspan.start:colspan.stop]))
|
||||
|
||||
for idx0, ax0 in enumerate(axs):
|
||||
# Compare ax to all other axs: If the subplotspecs start (/stop) at
|
||||
# the same column, then line up their left (/right) sides; likewise
|
||||
# for rows/top/bottom.
|
||||
rowspan0 = rowspans[idx0]
|
||||
colspan0 = colspans[idx0]
|
||||
height0 = heights[idx0]
|
||||
width0 = widths[idx0]
|
||||
alignleft = False
|
||||
alignright = False
|
||||
alignbot = False
|
||||
aligntop = False
|
||||
alignheight = False
|
||||
alignwidth = False
|
||||
for idx1 in range(idx0 + 1, len(axs)):
|
||||
ax1 = axs[idx1]
|
||||
rowspan1 = rowspans[idx1]
|
||||
colspan1 = colspans[idx1]
|
||||
width1 = widths[idx1]
|
||||
height1 = heights[idx1]
|
||||
# Horizontally align axes spines if they have the same min or max:
|
||||
if not alignleft and colspan0.start == colspan1.start:
|
||||
_log.debug('same start columns; line up layoutbox lefts')
|
||||
layoutbox.align([ax0._poslayoutbox, ax1._poslayoutbox],
|
||||
'left')
|
||||
alignleft = True
|
||||
if not alignright and colspan0.stop == colspan1.stop:
|
||||
_log.debug('same stop columns; line up layoutbox rights')
|
||||
layoutbox.align([ax0._poslayoutbox, ax1._poslayoutbox],
|
||||
'right')
|
||||
alignright = True
|
||||
# Vertically align axes spines if they have the same min or max:
|
||||
if not aligntop and rowspan0.start == rowspan1.start:
|
||||
_log.debug('same start rows; line up layoutbox tops')
|
||||
layoutbox.align([ax0._poslayoutbox, ax1._poslayoutbox],
|
||||
'top')
|
||||
aligntop = True
|
||||
if not alignbot and rowspan0.stop == rowspan1.stop:
|
||||
_log.debug('same stop rows; line up layoutbox bottoms')
|
||||
layoutbox.align([ax0._poslayoutbox, ax1._poslayoutbox],
|
||||
'bottom')
|
||||
alignbot = True
|
||||
|
||||
# Now we make the widths and heights of position boxes
|
||||
# similar. (i.e the spine locations)
|
||||
# This allows vertically stacked subplots to have different sizes
|
||||
# if they occupy different amounts of the gridspec, e.g. if
|
||||
# gs = gridspec.GridSpec(3, 1)
|
||||
# ax0 = gs[0, :]
|
||||
# ax1 = gs[1:, :]
|
||||
# then len(rowspan0) = 1, and len(rowspan1) = 2,
|
||||
# and ax1 should be at least twice as large as ax0.
|
||||
# But it can be more than twice as large because
|
||||
# it needs less room for the labeling.
|
||||
|
||||
# For heights, do it if the subplots share a column.
|
||||
if not alignheight and len(rowspan0) == len(rowspan1):
|
||||
ax0._poslayoutbox.constrain_height(
|
||||
ax1._poslayoutbox.height * height0 / height1)
|
||||
alignheight = True
|
||||
elif _spans_overlap(colspan0, colspan1):
|
||||
if height0 > height1:
|
||||
ax0._poslayoutbox.constrain_height_min(
|
||||
ax1._poslayoutbox.height * height0 / height1)
|
||||
elif height0 < height1:
|
||||
ax1._poslayoutbox.constrain_height_min(
|
||||
ax0._poslayoutbox.height * height1 / height0)
|
||||
# For widths, do it if the subplots share a row.
|
||||
if not alignwidth and len(colspan0) == len(colspan1):
|
||||
ax0._poslayoutbox.constrain_width(
|
||||
ax1._poslayoutbox.width * width0 / width1)
|
||||
alignwidth = True
|
||||
elif _spans_overlap(rowspan0, rowspan1):
|
||||
if width0 > width1:
|
||||
ax0._poslayoutbox.constrain_width_min(
|
||||
ax1._poslayoutbox.width * width0 / width1)
|
||||
elif width0 < width1:
|
||||
ax1._poslayoutbox.constrain_width_min(
|
||||
ax0._poslayoutbox.width * width1 / width0)
|
||||
|
||||
|
||||
def _arrange_subplotspecs(gs, hspace=0, wspace=0):
|
||||
"""Recursively arrange the subplotspec children of the given gridspec."""
|
||||
sschildren = []
|
||||
for child in gs.children:
|
||||
if child._is_subplotspec_layoutbox():
|
||||
for child2 in child.children:
|
||||
# check for gridspec children...
|
||||
if child2._is_gridspec_layoutbox():
|
||||
_arrange_subplotspecs(child2, hspace=hspace, wspace=wspace)
|
||||
sschildren += [child]
|
||||
# now arrange the subplots...
|
||||
for child0 in sschildren:
|
||||
ss0 = child0.artist
|
||||
nrows, ncols = ss0.get_gridspec().get_geometry()
|
||||
rowspan0 = ss0.rowspan
|
||||
colspan0 = ss0.colspan
|
||||
sschildren = sschildren[1:]
|
||||
for child1 in sschildren:
|
||||
ss1 = child1.artist
|
||||
rowspan1 = ss1.rowspan
|
||||
colspan1 = ss1.colspan
|
||||
# OK, this tells us the relative layout of child0 with child1.
|
||||
pad = wspace / ncols
|
||||
if colspan0.stop <= colspan1.start:
|
||||
layoutbox.hstack([ss0._layoutbox, ss1._layoutbox], padding=pad)
|
||||
if colspan1.stop <= colspan0.start:
|
||||
layoutbox.hstack([ss1._layoutbox, ss0._layoutbox], padding=pad)
|
||||
# vertical alignment
|
||||
pad = hspace / nrows
|
||||
if rowspan0.stop <= rowspan1.start:
|
||||
layoutbox.vstack([ss0._layoutbox, ss1._layoutbox], padding=pad)
|
||||
if rowspan1.stop <= rowspan0.start:
|
||||
layoutbox.vstack([ss1._layoutbox, ss0._layoutbox], padding=pad)
|
||||
|
||||
|
||||
def layoutcolorbarsingle(ax, cax, shrink, aspect, location, pad=0.05):
|
||||
"""
|
||||
Do the layout for a colorbar, to not overly pollute colorbar.py
|
||||
|
||||
*pad* is in fraction of the original axis size.
|
||||
"""
|
||||
axlb = ax._layoutbox
|
||||
axpos = ax._poslayoutbox
|
||||
axsslb = ax.get_subplotspec()._layoutbox
|
||||
lb = layoutbox.LayoutBox(
|
||||
parent=axsslb,
|
||||
name=axsslb.name + '.cbar',
|
||||
artist=cax)
|
||||
|
||||
if location in ('left', 'right'):
|
||||
lbpos = layoutbox.LayoutBox(
|
||||
parent=lb,
|
||||
name=lb.name + '.pos',
|
||||
tightwidth=False,
|
||||
pos=True,
|
||||
subplot=False,
|
||||
artist=cax)
|
||||
|
||||
if location == 'right':
|
||||
# arrange to right of parent axis
|
||||
layoutbox.hstack([axlb, lb], padding=pad * axlb.width,
|
||||
strength='strong')
|
||||
else:
|
||||
layoutbox.hstack([lb, axlb], padding=pad * axlb.width)
|
||||
# constrain the height and center...
|
||||
layoutbox.match_heights([axpos, lbpos], [1, shrink])
|
||||
layoutbox.align([axpos, lbpos], 'v_center')
|
||||
# set the width of the pos box
|
||||
lbpos.constrain_width(shrink * axpos.height * (1/aspect),
|
||||
strength='strong')
|
||||
elif location in ('bottom', 'top'):
|
||||
lbpos = layoutbox.LayoutBox(
|
||||
parent=lb,
|
||||
name=lb.name + '.pos',
|
||||
tightheight=True,
|
||||
pos=True,
|
||||
subplot=False,
|
||||
artist=cax)
|
||||
|
||||
if location == 'bottom':
|
||||
layoutbox.vstack([axlb, lb], padding=pad * axlb.height)
|
||||
else:
|
||||
layoutbox.vstack([lb, axlb], padding=pad * axlb.height)
|
||||
# constrain the height and center...
|
||||
layoutbox.match_widths([axpos, lbpos],
|
||||
[1, shrink], strength='strong')
|
||||
layoutbox.align([axpos, lbpos], 'h_center')
|
||||
# set the height of the pos box
|
||||
lbpos.constrain_height(axpos.width * aspect * shrink,
|
||||
strength='medium')
|
||||
|
||||
return lb, lbpos
|
||||
|
||||
|
||||
def _getmaxminrowcolumn(axs):
|
||||
"""
|
||||
Find axes covering the first and last rows and columns of a list of axes.
|
||||
"""
|
||||
startrow = startcol = np.inf
|
||||
stoprow = stopcol = -np.inf
|
||||
startax_row = startax_col = stopax_row = stopax_col = None
|
||||
for ax in axs:
|
||||
subspec = ax.get_subplotspec()
|
||||
if subspec.rowspan.start < startrow:
|
||||
startrow = subspec.rowspan.start
|
||||
startax_row = ax
|
||||
if subspec.rowspan.stop > stoprow:
|
||||
stoprow = subspec.rowspan.stop
|
||||
stopax_row = ax
|
||||
if subspec.colspan.start < startcol:
|
||||
startcol = subspec.colspan.start
|
||||
startax_col = ax
|
||||
if subspec.colspan.stop > stopcol:
|
||||
stopcol = subspec.colspan.stop
|
||||
stopax_col = ax
|
||||
return (startrow, stoprow - 1, startax_row, stopax_row,
|
||||
startcol, stopcol - 1, startax_col, stopax_col)
|
||||
|
||||
|
||||
def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
|
||||
"""
|
||||
Do the layout for a colorbar, to not overly pollute colorbar.py
|
||||
|
||||
*pad* is in fraction of the original axis size.
|
||||
"""
|
||||
|
||||
gs = parents[0].get_subplotspec().get_gridspec()
|
||||
# parent layout box....
|
||||
gslb = gs._layoutbox
|
||||
|
||||
lb = layoutbox.LayoutBox(parent=gslb.parent,
|
||||
name=gslb.parent.name + '.cbar',
|
||||
artist=cax)
|
||||
# figure out the row and column extent of the parents.
|
||||
(minrow, maxrow, minax_row, maxax_row,
|
||||
mincol, maxcol, minax_col, maxax_col) = _getmaxminrowcolumn(parents)
|
||||
|
||||
if location in ('left', 'right'):
|
||||
lbpos = layoutbox.LayoutBox(
|
||||
parent=lb,
|
||||
name=lb.name + '.pos',
|
||||
tightwidth=False,
|
||||
pos=True,
|
||||
subplot=False,
|
||||
artist=cax)
|
||||
for ax in parents:
|
||||
if location == 'right':
|
||||
order = [ax._layoutbox, lb]
|
||||
else:
|
||||
order = [lb, ax._layoutbox]
|
||||
layoutbox.hstack(order, padding=pad * gslb.width,
|
||||
strength='strong')
|
||||
# constrain the height and center...
|
||||
# This isn't quite right. We'd like the colorbar
|
||||
# pos to line up w/ the axes poss, not the size of the
|
||||
# gs.
|
||||
|
||||
# Horizontal Layout: need to check all the axes in this gridspec
|
||||
for ch in gslb.children:
|
||||
subspec = ch.artist
|
||||
if location == 'right':
|
||||
if subspec.colspan.stop - 1 <= maxcol:
|
||||
order = [subspec._layoutbox, lb]
|
||||
# arrange to right of the parents
|
||||
elif subspec.colspan.start > maxcol:
|
||||
order = [lb, subspec._layoutbox]
|
||||
elif location == 'left':
|
||||
if subspec.colspan.start >= mincol:
|
||||
order = [lb, subspec._layoutbox]
|
||||
elif subspec.colspan.stop - 1 < mincol:
|
||||
order = [subspec._layoutbox, lb]
|
||||
layoutbox.hstack(order, padding=pad * gslb.width,
|
||||
strength='strong')
|
||||
|
||||
# Vertical layout:
|
||||
maxposlb = minax_row._poslayoutbox
|
||||
minposlb = maxax_row._poslayoutbox
|
||||
# now we want the height of the colorbar pos to be
|
||||
# set by the top and bottom of the min/max axes...
|
||||
# bottom top
|
||||
# b t
|
||||
# h = (top-bottom)*shrink
|
||||
# b = bottom + (top-bottom - h) / 2.
|
||||
lbpos.constrain_height(
|
||||
(maxposlb.top - minposlb.bottom) *
|
||||
shrink, strength='strong')
|
||||
lbpos.constrain_bottom(
|
||||
(maxposlb.top - minposlb.bottom) *
|
||||
(1 - shrink)/2 + minposlb.bottom,
|
||||
strength='strong')
|
||||
|
||||
# set the width of the pos box
|
||||
lbpos.constrain_width(lbpos.height * (shrink / aspect),
|
||||
strength='strong')
|
||||
elif location in ('bottom', 'top'):
|
||||
lbpos = layoutbox.LayoutBox(
|
||||
parent=lb,
|
||||
name=lb.name + '.pos',
|
||||
tightheight=True,
|
||||
pos=True,
|
||||
subplot=False,
|
||||
artist=cax)
|
||||
|
||||
for ax in parents:
|
||||
if location == 'bottom':
|
||||
order = [ax._layoutbox, lb]
|
||||
else:
|
||||
order = [lb, ax._layoutbox]
|
||||
layoutbox.vstack(order, padding=pad * gslb.width,
|
||||
strength='strong')
|
||||
|
||||
# Vertical Layout: need to check all the axes in this gridspec
|
||||
for ch in gslb.children:
|
||||
subspec = ch.artist
|
||||
if location == 'bottom':
|
||||
if subspec.rowspan.stop - 1 <= minrow:
|
||||
order = [subspec._layoutbox, lb]
|
||||
elif subspec.rowspan.start > maxrow:
|
||||
order = [lb, subspec._layoutbox]
|
||||
elif location == 'top':
|
||||
if subspec.rowspan.stop - 1 < minrow:
|
||||
order = [subspec._layoutbox, lb]
|
||||
elif subspec.rowspan.start >= maxrow:
|
||||
order = [lb, subspec._layoutbox]
|
||||
layoutbox.vstack(order, padding=pad * gslb.width,
|
||||
strength='strong')
|
||||
|
||||
# Do horizontal layout...
|
||||
maxposlb = maxax_col._poslayoutbox
|
||||
minposlb = minax_col._poslayoutbox
|
||||
lbpos.constrain_width((maxposlb.right - minposlb.left) *
|
||||
shrink)
|
||||
lbpos.constrain_left(
|
||||
(maxposlb.right - minposlb.left) *
|
||||
(1-shrink)/2 + minposlb.left)
|
||||
# set the height of the pos box
|
||||
lbpos.constrain_height(lbpos.width * shrink * aspect,
|
||||
strength='medium')
|
||||
|
||||
return lb, lbpos
|
BIN
venv/Lib/site-packages/matplotlib/_contour.cp36-win32.pyd
Normal file
BIN
venv/Lib/site-packages/matplotlib/_contour.cp36-win32.pyd
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/matplotlib/_image.cp36-win32.pyd
Normal file
BIN
venv/Lib/site-packages/matplotlib/_image.cp36-win32.pyd
Normal file
Binary file not shown.
64
venv/Lib/site-packages/matplotlib/_internal_utils.py
Normal file
64
venv/Lib/site-packages/matplotlib/_internal_utils.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
"""
|
||||
Internal debugging utilities, that are not expected to be used in the rest of
|
||||
the codebase.
|
||||
|
||||
WARNING: Code in this module may change without prior notice!
|
||||
"""
|
||||
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
from matplotlib.transforms import TransformNode
|
||||
|
||||
|
||||
def graphviz_dump_transform(transform, dest, *, highlight=None):
|
||||
"""
|
||||
Generate a graphical representation of the transform tree for *transform*
|
||||
using the :program:`dot` program (which this function depends on). The
|
||||
output format (png, dot, etc.) is determined from the suffix of *dest*.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
transform : `~matplotlib.transform.Transform`
|
||||
The represented transform.
|
||||
dest : str
|
||||
Output filename. The extension must be one of the formats supported
|
||||
by :program:`dot`, e.g. png, svg, dot, ...
|
||||
(see https://www.graphviz.org/doc/info/output.html).
|
||||
highlight : list of `~matplotlib.transform.Transform` or None
|
||||
The transforms in the tree to be drawn in bold.
|
||||
If *None*, *transform* is highlighted.
|
||||
"""
|
||||
|
||||
if highlight is None:
|
||||
highlight = [transform]
|
||||
seen = set()
|
||||
|
||||
def recurse(root, buf):
|
||||
if id(root) in seen:
|
||||
return
|
||||
seen.add(id(root))
|
||||
props = {}
|
||||
label = type(root).__name__
|
||||
if root._invalid:
|
||||
label = f'[{label}]'
|
||||
if root in highlight:
|
||||
props['style'] = 'bold'
|
||||
props['shape'] = 'box'
|
||||
props['label'] = '"%s"' % label
|
||||
props = ' '.join(map('{0[0]}={0[1]}'.format, props.items()))
|
||||
buf.write(f'{id(root)} [{props}];\n')
|
||||
for key, val in vars(root).items():
|
||||
if isinstance(val, TransformNode) and id(root) in val._parents:
|
||||
buf.write(f'"{id(root)}" -> "{id(val)}" '
|
||||
f'[label="{key}", fontsize=10];\n')
|
||||
recurse(val, buf)
|
||||
|
||||
buf = StringIO()
|
||||
buf.write('digraph G {\n')
|
||||
recurse(transform, buf)
|
||||
buf.write('}\n')
|
||||
subprocess.run(
|
||||
['dot', '-T', Path(dest).suffix[1:], '-o', dest],
|
||||
input=buf.getvalue().encode('utf-8'), check=True)
|
695
venv/Lib/site-packages/matplotlib/_layoutbox.py
Normal file
695
venv/Lib/site-packages/matplotlib/_layoutbox.py
Normal file
|
@ -0,0 +1,695 @@
|
|||
"""
|
||||
|
||||
Conventions:
|
||||
|
||||
"constrain_x" means to constrain the variable with either
|
||||
another kiwisolver variable, or a float. i.e. `constrain_width(0.2)`
|
||||
will set a constraint that the width has to be 0.2 and this constraint is
|
||||
permanent - i.e. it will not be removed if it becomes obsolete.
|
||||
|
||||
"edit_x" means to set x to a value (just a float), and that this value can
|
||||
change. So `edit_width(0.2)` will set width to be 0.2, but `edit_width(0.3)`
|
||||
will allow it to change to 0.3 later. Note that these values are still just
|
||||
"suggestions" in `kiwisolver` parlance, and could be over-ridden by
|
||||
other constrains.
|
||||
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import kiwisolver as kiwi
|
||||
import logging
|
||||
import numpy as np
|
||||
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# renderers can be complicated
|
||||
def get_renderer(fig):
|
||||
if fig._cachedRenderer:
|
||||
renderer = fig._cachedRenderer
|
||||
else:
|
||||
canvas = fig.canvas
|
||||
if canvas and hasattr(canvas, "get_renderer"):
|
||||
renderer = canvas.get_renderer()
|
||||
else:
|
||||
# not sure if this can happen
|
||||
# seems to with PDF...
|
||||
_log.info("constrained_layout : falling back to Agg renderer")
|
||||
from matplotlib.backends.backend_agg import FigureCanvasAgg
|
||||
canvas = FigureCanvasAgg(fig)
|
||||
renderer = canvas.get_renderer()
|
||||
|
||||
return renderer
|
||||
|
||||
|
||||
class LayoutBox:
|
||||
"""
|
||||
Basic rectangle representation using kiwi solver variables
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None, name='', tightwidth=False,
|
||||
tightheight=False, artist=None,
|
||||
lower_left=(0, 0), upper_right=(1, 1), pos=False,
|
||||
subplot=False, h_pad=None, w_pad=None):
|
||||
Variable = kiwi.Variable
|
||||
self.parent = parent
|
||||
self.name = name
|
||||
sn = self.name + '_'
|
||||
if parent is None:
|
||||
self.solver = kiwi.Solver()
|
||||
self.constrained_layout_called = 0
|
||||
else:
|
||||
self.solver = parent.solver
|
||||
self.constrained_layout_called = None
|
||||
# parent wants to know about this child!
|
||||
parent.add_child(self)
|
||||
# keep track of artist associated w/ this layout. Can be none
|
||||
self.artist = artist
|
||||
# keep track if this box is supposed to be a pos that is constrained
|
||||
# by the parent.
|
||||
self.pos = pos
|
||||
# keep track of whether we need to match this subplot up with others.
|
||||
self.subplot = subplot
|
||||
|
||||
self.top = Variable(sn + 'top')
|
||||
self.bottom = Variable(sn + 'bottom')
|
||||
self.left = Variable(sn + 'left')
|
||||
self.right = Variable(sn + 'right')
|
||||
|
||||
self.width = Variable(sn + 'width')
|
||||
self.height = Variable(sn + 'height')
|
||||
self.h_center = Variable(sn + 'h_center')
|
||||
self.v_center = Variable(sn + 'v_center')
|
||||
|
||||
self.min_width = Variable(sn + 'min_width')
|
||||
self.min_height = Variable(sn + 'min_height')
|
||||
self.pref_width = Variable(sn + 'pref_width')
|
||||
self.pref_height = Variable(sn + 'pref_height')
|
||||
# margins are only used for axes-position layout boxes. maybe should
|
||||
# be a separate subclass:
|
||||
self.left_margin = Variable(sn + 'left_margin')
|
||||
self.right_margin = Variable(sn + 'right_margin')
|
||||
self.bottom_margin = Variable(sn + 'bottom_margin')
|
||||
self.top_margin = Variable(sn + 'top_margin')
|
||||
# mins
|
||||
self.left_margin_min = Variable(sn + 'left_margin_min')
|
||||
self.right_margin_min = Variable(sn + 'right_margin_min')
|
||||
self.bottom_margin_min = Variable(sn + 'bottom_margin_min')
|
||||
self.top_margin_min = Variable(sn + 'top_margin_min')
|
||||
|
||||
right, top = upper_right
|
||||
left, bottom = lower_left
|
||||
self.tightheight = tightheight
|
||||
self.tightwidth = tightwidth
|
||||
self.add_constraints()
|
||||
self.children = []
|
||||
self.subplotspec = None
|
||||
if self.pos:
|
||||
self.constrain_margins()
|
||||
self.h_pad = h_pad
|
||||
self.w_pad = w_pad
|
||||
|
||||
def constrain_margins(self):
|
||||
"""
|
||||
Only do this for pos. This sets a variable distance
|
||||
margin between the position of the axes and the outer edge of
|
||||
the axes.
|
||||
|
||||
Margins are variable because they change with the figure size.
|
||||
|
||||
Margin minimums are set to make room for axes decorations. However,
|
||||
the margins can be larger if we are mathicng the position size to
|
||||
other axes.
|
||||
"""
|
||||
sol = self.solver
|
||||
|
||||
# left
|
||||
if not sol.hasEditVariable(self.left_margin_min):
|
||||
sol.addEditVariable(self.left_margin_min, 'strong')
|
||||
sol.suggestValue(self.left_margin_min, 0.0001)
|
||||
c = (self.left_margin == self.left - self.parent.left)
|
||||
self.solver.addConstraint(c | 'required')
|
||||
c = (self.left_margin >= self.left_margin_min)
|
||||
self.solver.addConstraint(c | 'strong')
|
||||
|
||||
# right
|
||||
if not sol.hasEditVariable(self.right_margin_min):
|
||||
sol.addEditVariable(self.right_margin_min, 'strong')
|
||||
sol.suggestValue(self.right_margin_min, 0.0001)
|
||||
c = (self.right_margin == self.parent.right - self.right)
|
||||
self.solver.addConstraint(c | 'required')
|
||||
c = (self.right_margin >= self.right_margin_min)
|
||||
self.solver.addConstraint(c | 'required')
|
||||
# bottom
|
||||
if not sol.hasEditVariable(self.bottom_margin_min):
|
||||
sol.addEditVariable(self.bottom_margin_min, 'strong')
|
||||
sol.suggestValue(self.bottom_margin_min, 0.0001)
|
||||
c = (self.bottom_margin == self.bottom - self.parent.bottom)
|
||||
self.solver.addConstraint(c | 'required')
|
||||
c = (self.bottom_margin >= self.bottom_margin_min)
|
||||
self.solver.addConstraint(c | 'required')
|
||||
# top
|
||||
if not sol.hasEditVariable(self.top_margin_min):
|
||||
sol.addEditVariable(self.top_margin_min, 'strong')
|
||||
sol.suggestValue(self.top_margin_min, 0.0001)
|
||||
c = (self.top_margin == self.parent.top - self.top)
|
||||
self.solver.addConstraint(c | 'required')
|
||||
c = (self.top_margin >= self.top_margin_min)
|
||||
self.solver.addConstraint(c | 'required')
|
||||
|
||||
def add_child(self, child):
|
||||
self.children += [child]
|
||||
|
||||
def remove_child(self, child):
|
||||
try:
|
||||
self.children.remove(child)
|
||||
except ValueError:
|
||||
_log.info("Tried to remove child that doesn't belong to parent")
|
||||
|
||||
def add_constraints(self):
|
||||
sol = self.solver
|
||||
# never let width and height go negative.
|
||||
for i in [self.min_width, self.min_height]:
|
||||
sol.addEditVariable(i, 1e9)
|
||||
sol.suggestValue(i, 0.0)
|
||||
# define relation ships between things thing width and right and left
|
||||
self.hard_constraints()
|
||||
# self.soft_constraints()
|
||||
if self.parent:
|
||||
self.parent_constrain()
|
||||
# sol.updateVariables()
|
||||
|
||||
def parent_constrain(self):
|
||||
parent = self.parent
|
||||
hc = [self.left >= parent.left,
|
||||
self.bottom >= parent.bottom,
|
||||
self.top <= parent.top,
|
||||
self.right <= parent.right]
|
||||
for c in hc:
|
||||
self.solver.addConstraint(c | 'required')
|
||||
|
||||
def hard_constraints(self):
|
||||
hc = [self.width == self.right - self.left,
|
||||
self.height == self.top - self.bottom,
|
||||
self.h_center == (self.left + self.right) * 0.5,
|
||||
self.v_center == (self.top + self.bottom) * 0.5,
|
||||
self.width >= self.min_width,
|
||||
self.height >= self.min_height]
|
||||
for c in hc:
|
||||
self.solver.addConstraint(c | 'required')
|
||||
|
||||
def soft_constraints(self):
|
||||
sol = self.solver
|
||||
if self.tightwidth:
|
||||
suggest = 0.
|
||||
else:
|
||||
suggest = 20.
|
||||
c = (self.pref_width == suggest)
|
||||
for i in c:
|
||||
sol.addConstraint(i | 'required')
|
||||
if self.tightheight:
|
||||
suggest = 0.
|
||||
else:
|
||||
suggest = 20.
|
||||
c = (self.pref_height == suggest)
|
||||
for i in c:
|
||||
sol.addConstraint(i | 'required')
|
||||
|
||||
c = [(self.width >= suggest),
|
||||
(self.height >= suggest)]
|
||||
for i in c:
|
||||
sol.addConstraint(i | 150000)
|
||||
|
||||
def set_parent(self, parent):
|
||||
"""Replace the parent of this with the new parent."""
|
||||
self.parent = parent
|
||||
self.parent_constrain()
|
||||
|
||||
def constrain_geometry(self, left, bottom, right, top, strength='strong'):
|
||||
hc = [self.left == left,
|
||||
self.right == right,
|
||||
self.bottom == bottom,
|
||||
self.top == top]
|
||||
for c in hc:
|
||||
self.solver.addConstraint(c | strength)
|
||||
# self.solver.updateVariables()
|
||||
|
||||
def constrain_same(self, other, strength='strong'):
|
||||
"""
|
||||
Make the layoutbox have same position as other layoutbox
|
||||
"""
|
||||
hc = [self.left == other.left,
|
||||
self.right == other.right,
|
||||
self.bottom == other.bottom,
|
||||
self.top == other.top]
|
||||
for c in hc:
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def constrain_left_margin(self, margin, strength='strong'):
|
||||
c = (self.left == self.parent.left + margin)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def edit_left_margin_min(self, margin):
|
||||
self.solver.suggestValue(self.left_margin_min, margin)
|
||||
|
||||
def constrain_right_margin(self, margin, strength='strong'):
|
||||
c = (self.right == self.parent.right - margin)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def edit_right_margin_min(self, margin):
|
||||
self.solver.suggestValue(self.right_margin_min, margin)
|
||||
|
||||
def constrain_bottom_margin(self, margin, strength='strong'):
|
||||
c = (self.bottom == self.parent.bottom + margin)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def edit_bottom_margin_min(self, margin):
|
||||
self.solver.suggestValue(self.bottom_margin_min, margin)
|
||||
|
||||
def constrain_top_margin(self, margin, strength='strong'):
|
||||
c = (self.top == self.parent.top - margin)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def edit_top_margin_min(self, margin):
|
||||
self.solver.suggestValue(self.top_margin_min, margin)
|
||||
|
||||
def get_rect(self):
|
||||
return (self.left.value(), self.bottom.value(),
|
||||
self.width.value(), self.height.value())
|
||||
|
||||
def update_variables(self):
|
||||
"""
|
||||
Update *all* the variables that are part of the solver this LayoutBox
|
||||
is created with.
|
||||
"""
|
||||
self.solver.updateVariables()
|
||||
|
||||
def edit_height(self, height, strength='strong'):
|
||||
"""
|
||||
Set the height of the layout box.
|
||||
|
||||
This is done as an editable variable so that the value can change
|
||||
due to resizing.
|
||||
"""
|
||||
sol = self.solver
|
||||
for i in [self.height]:
|
||||
if not sol.hasEditVariable(i):
|
||||
sol.addEditVariable(i, strength)
|
||||
sol.suggestValue(self.height, height)
|
||||
|
||||
def constrain_height(self, height, strength='strong'):
|
||||
"""
|
||||
Constrain the height of the layout box. height is
|
||||
either a float or a layoutbox.height.
|
||||
"""
|
||||
c = (self.height == height)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def constrain_height_min(self, height, strength='strong'):
|
||||
c = (self.height >= height)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def edit_width(self, width, strength='strong'):
|
||||
sol = self.solver
|
||||
for i in [self.width]:
|
||||
if not sol.hasEditVariable(i):
|
||||
sol.addEditVariable(i, strength)
|
||||
sol.suggestValue(self.width, width)
|
||||
|
||||
def constrain_width(self, width, strength='strong'):
|
||||
"""
|
||||
Constrain the width of the layout box. *width* is
|
||||
either a float or a layoutbox.width.
|
||||
"""
|
||||
c = (self.width == width)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def constrain_width_min(self, width, strength='strong'):
|
||||
c = (self.width >= width)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def constrain_left(self, left, strength='strong'):
|
||||
c = (self.left == left)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def constrain_bottom(self, bottom, strength='strong'):
|
||||
c = (self.bottom == bottom)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def constrain_right(self, right, strength='strong'):
|
||||
c = (self.right == right)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def constrain_top(self, top, strength='strong'):
|
||||
c = (self.top == top)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def _is_subplotspec_layoutbox(self):
|
||||
"""
|
||||
Helper to check if this layoutbox is the layoutbox of a subplotspec.
|
||||
"""
|
||||
name = self.name.split('.')[-1]
|
||||
return name[:2] == 'ss'
|
||||
|
||||
def _is_gridspec_layoutbox(self):
|
||||
"""
|
||||
Helper to check if this layoutbox is the layoutbox of a gridspec.
|
||||
"""
|
||||
name = self.name.split('.')[-1]
|
||||
return name[:8] == 'gridspec'
|
||||
|
||||
def find_child_subplots(self):
|
||||
"""
|
||||
Find children of this layout box that are subplots. We want to line
|
||||
poss up, and this is an easy way to find them all.
|
||||
"""
|
||||
if self.subplot:
|
||||
subplots = [self]
|
||||
else:
|
||||
subplots = []
|
||||
for child in self.children:
|
||||
subplots += child.find_child_subplots()
|
||||
return subplots
|
||||
|
||||
def layout_from_subplotspec(self, subspec,
|
||||
name='', artist=None, pos=False):
|
||||
"""
|
||||
Make a layout box from a subplotspec. The layout box is
|
||||
constrained to be a fraction of the width/height of the parent,
|
||||
and be a fraction of the parent width/height from the left/bottom
|
||||
of the parent. Therefore the parent can move around and the
|
||||
layout for the subplot spec should move with it.
|
||||
|
||||
The parent is *usually* the gridspec that made the subplotspec.??
|
||||
"""
|
||||
lb = LayoutBox(parent=self, name=name, artist=artist, pos=pos)
|
||||
gs = subspec.get_gridspec()
|
||||
nrows, ncols = gs.get_geometry()
|
||||
parent = self.parent
|
||||
|
||||
# OK, now, we want to set the position of this subplotspec
|
||||
# based on its subplotspec parameters. The new gridspec will inherit
|
||||
# from gridspec. prob should be new method in gridspec
|
||||
left = 0.0
|
||||
right = 1.0
|
||||
bottom = 0.0
|
||||
top = 1.0
|
||||
totWidth = right-left
|
||||
totHeight = top-bottom
|
||||
hspace = 0.
|
||||
wspace = 0.
|
||||
|
||||
# calculate accumulated heights of columns
|
||||
cellH = totHeight / (nrows + hspace * (nrows - 1))
|
||||
sepH = hspace * cellH
|
||||
|
||||
if gs._row_height_ratios is not None:
|
||||
netHeight = cellH * nrows
|
||||
tr = sum(gs._row_height_ratios)
|
||||
cellHeights = [netHeight * r / tr for r in gs._row_height_ratios]
|
||||
else:
|
||||
cellHeights = [cellH] * nrows
|
||||
|
||||
sepHeights = [0] + ([sepH] * (nrows - 1))
|
||||
cellHs = np.cumsum(np.column_stack([sepHeights, cellHeights]).flat)
|
||||
|
||||
# calculate accumulated widths of rows
|
||||
cellW = totWidth / (ncols + wspace * (ncols - 1))
|
||||
sepW = wspace * cellW
|
||||
|
||||
if gs._col_width_ratios is not None:
|
||||
netWidth = cellW * ncols
|
||||
tr = sum(gs._col_width_ratios)
|
||||
cellWidths = [netWidth * r / tr for r in gs._col_width_ratios]
|
||||
else:
|
||||
cellWidths = [cellW] * ncols
|
||||
|
||||
sepWidths = [0] + ([sepW] * (ncols - 1))
|
||||
cellWs = np.cumsum(np.column_stack([sepWidths, cellWidths]).flat)
|
||||
|
||||
figTops = [top - cellHs[2 * rowNum] for rowNum in range(nrows)]
|
||||
figBottoms = [top - cellHs[2 * rowNum + 1] for rowNum in range(nrows)]
|
||||
figLefts = [left + cellWs[2 * colNum] for colNum in range(ncols)]
|
||||
figRights = [left + cellWs[2 * colNum + 1] for colNum in range(ncols)]
|
||||
|
||||
rowNum1, colNum1 = divmod(subspec.num1, ncols)
|
||||
rowNum2, colNum2 = divmod(subspec.num2, ncols)
|
||||
figBottom = min(figBottoms[rowNum1], figBottoms[rowNum2])
|
||||
figTop = max(figTops[rowNum1], figTops[rowNum2])
|
||||
figLeft = min(figLefts[colNum1], figLefts[colNum2])
|
||||
figRight = max(figRights[colNum1], figRights[colNum2])
|
||||
|
||||
# These are numbers relative to (0, 0, 1, 1). Need to constrain
|
||||
# relative to parent.
|
||||
|
||||
width = figRight - figLeft
|
||||
height = figTop - figBottom
|
||||
parent = self.parent
|
||||
cs = [self.left == parent.left + parent.width * figLeft,
|
||||
self.bottom == parent.bottom + parent.height * figBottom,
|
||||
self.width == parent.width * width,
|
||||
self.height == parent.height * height]
|
||||
for c in cs:
|
||||
self.solver.addConstraint(c | 'required')
|
||||
|
||||
return lb
|
||||
|
||||
def __repr__(self):
|
||||
return (f'LayoutBox: {self.name:25s}, '
|
||||
f'(left: {self.left.value():1.3f}) '
|
||||
f'(bot: {self.bottom.value():1.3f}) '
|
||||
f'(right: {self.right.value():1.3f}) '
|
||||
f'(top: {self.top.value():1.3f})')
|
||||
|
||||
|
||||
# Utility functions that act on layoutboxes...
|
||||
def hstack(boxes, padding=0, strength='strong'):
|
||||
"""
|
||||
Stack LayoutBox instances from left to right.
|
||||
*padding* is in figure-relative units.
|
||||
"""
|
||||
|
||||
for i in range(1, len(boxes)):
|
||||
c = (boxes[i-1].right + padding <= boxes[i].left)
|
||||
boxes[i].solver.addConstraint(c | strength)
|
||||
|
||||
|
||||
def hpack(boxes, padding=0, strength='strong'):
|
||||
"""Stack LayoutBox instances from left to right."""
|
||||
|
||||
for i in range(1, len(boxes)):
|
||||
c = (boxes[i-1].right + padding == boxes[i].left)
|
||||
boxes[i].solver.addConstraint(c | strength)
|
||||
|
||||
|
||||
def vstack(boxes, padding=0, strength='strong'):
|
||||
"""Stack LayoutBox instances from top to bottom."""
|
||||
|
||||
for i in range(1, len(boxes)):
|
||||
c = (boxes[i-1].bottom - padding >= boxes[i].top)
|
||||
boxes[i].solver.addConstraint(c | strength)
|
||||
|
||||
|
||||
def vpack(boxes, padding=0, strength='strong'):
|
||||
"""Stack LayoutBox instances from top to bottom."""
|
||||
|
||||
for i in range(1, len(boxes)):
|
||||
c = (boxes[i-1].bottom - padding >= boxes[i].top)
|
||||
boxes[i].solver.addConstraint(c | strength)
|
||||
|
||||
|
||||
def match_heights(boxes, height_ratios=None, strength='medium'):
|
||||
"""Stack LayoutBox instances from top to bottom."""
|
||||
|
||||
if height_ratios is None:
|
||||
height_ratios = np.ones(len(boxes))
|
||||
for i in range(1, len(boxes)):
|
||||
c = (boxes[i-1].height ==
|
||||
boxes[i].height*height_ratios[i-1]/height_ratios[i])
|
||||
boxes[i].solver.addConstraint(c | strength)
|
||||
|
||||
|
||||
def match_widths(boxes, width_ratios=None, strength='medium'):
|
||||
"""Stack LayoutBox instances from top to bottom."""
|
||||
|
||||
if width_ratios is None:
|
||||
width_ratios = np.ones(len(boxes))
|
||||
for i in range(1, len(boxes)):
|
||||
c = (boxes[i-1].width ==
|
||||
boxes[i].width*width_ratios[i-1]/width_ratios[i])
|
||||
boxes[i].solver.addConstraint(c | strength)
|
||||
|
||||
|
||||
def vstackeq(boxes, padding=0, height_ratios=None):
|
||||
vstack(boxes, padding=padding)
|
||||
match_heights(boxes, height_ratios=height_ratios)
|
||||
|
||||
|
||||
def hstackeq(boxes, padding=0, width_ratios=None):
|
||||
hstack(boxes, padding=padding)
|
||||
match_widths(boxes, width_ratios=width_ratios)
|
||||
|
||||
|
||||
def align(boxes, attr, strength='strong'):
|
||||
cons = []
|
||||
for box in boxes[1:]:
|
||||
cons = (getattr(boxes[0], attr) == getattr(box, attr))
|
||||
boxes[0].solver.addConstraint(cons | strength)
|
||||
|
||||
|
||||
def match_top_margins(boxes, levels=1):
|
||||
box0 = boxes[0]
|
||||
top0 = box0
|
||||
for n in range(levels):
|
||||
top0 = top0.parent
|
||||
for box in boxes[1:]:
|
||||
topb = box
|
||||
for n in range(levels):
|
||||
topb = topb.parent
|
||||
c = (box0.top-top0.top == box.top-topb.top)
|
||||
box0.solver.addConstraint(c | 'strong')
|
||||
|
||||
|
||||
def match_bottom_margins(boxes, levels=1):
|
||||
box0 = boxes[0]
|
||||
top0 = box0
|
||||
for n in range(levels):
|
||||
top0 = top0.parent
|
||||
for box in boxes[1:]:
|
||||
topb = box
|
||||
for n in range(levels):
|
||||
topb = topb.parent
|
||||
c = (box0.bottom-top0.bottom == box.bottom-topb.bottom)
|
||||
box0.solver.addConstraint(c | 'strong')
|
||||
|
||||
|
||||
def match_left_margins(boxes, levels=1):
|
||||
box0 = boxes[0]
|
||||
top0 = box0
|
||||
for n in range(levels):
|
||||
top0 = top0.parent
|
||||
for box in boxes[1:]:
|
||||
topb = box
|
||||
for n in range(levels):
|
||||
topb = topb.parent
|
||||
c = (box0.left-top0.left == box.left-topb.left)
|
||||
box0.solver.addConstraint(c | 'strong')
|
||||
|
||||
|
||||
def match_right_margins(boxes, levels=1):
|
||||
box0 = boxes[0]
|
||||
top0 = box0
|
||||
for n in range(levels):
|
||||
top0 = top0.parent
|
||||
for box in boxes[1:]:
|
||||
topb = box
|
||||
for n in range(levels):
|
||||
topb = topb.parent
|
||||
c = (box0.right-top0.right == box.right-topb.right)
|
||||
box0.solver.addConstraint(c | 'strong')
|
||||
|
||||
|
||||
def match_width_margins(boxes, levels=1):
|
||||
match_left_margins(boxes, levels=levels)
|
||||
match_right_margins(boxes, levels=levels)
|
||||
|
||||
|
||||
def match_height_margins(boxes, levels=1):
|
||||
match_top_margins(boxes, levels=levels)
|
||||
match_bottom_margins(boxes, levels=levels)
|
||||
|
||||
|
||||
def match_margins(boxes, levels=1):
|
||||
match_width_margins(boxes, levels=levels)
|
||||
match_height_margins(boxes, levels=levels)
|
||||
|
||||
|
||||
_layoutboxobjnum = itertools.count()
|
||||
|
||||
|
||||
def seq_id():
|
||||
"""Generate a short sequential id for layoutbox objects."""
|
||||
return '%06d' % next(_layoutboxobjnum)
|
||||
|
||||
|
||||
def print_children(lb):
|
||||
"""Print the children of the layoutbox."""
|
||||
print(lb)
|
||||
for child in lb.children:
|
||||
print_children(child)
|
||||
|
||||
|
||||
def nonetree(lb):
|
||||
"""
|
||||
Make all elements in this tree None, signalling not to do any more layout.
|
||||
"""
|
||||
if lb is not None:
|
||||
if lb.parent is None:
|
||||
# Clear the solver. Hopefully this garbage collects.
|
||||
lb.solver.reset()
|
||||
nonechildren(lb)
|
||||
else:
|
||||
nonetree(lb.parent)
|
||||
|
||||
|
||||
def nonechildren(lb):
|
||||
for child in lb.children:
|
||||
nonechildren(child)
|
||||
lb.artist._layoutbox = None
|
||||
lb = None
|
||||
|
||||
|
||||
def print_tree(lb):
|
||||
"""Print the tree of layoutboxes."""
|
||||
|
||||
if lb.parent is None:
|
||||
print('LayoutBox Tree\n')
|
||||
print('==============\n')
|
||||
print_children(lb)
|
||||
print('\n')
|
||||
else:
|
||||
print_tree(lb.parent)
|
||||
|
||||
|
||||
def plot_children(fig, box, level=0, printit=True):
|
||||
"""Simple plotting to show where boxes are."""
|
||||
import matplotlib
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
if isinstance(fig, matplotlib.figure.Figure):
|
||||
ax = fig.add_axes([0., 0., 1., 1.])
|
||||
ax.set_facecolor([1., 1., 1., 0.7])
|
||||
ax.set_alpha(0.3)
|
||||
fig.draw(fig.canvas.get_renderer())
|
||||
else:
|
||||
ax = fig
|
||||
|
||||
import matplotlib.patches as patches
|
||||
colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]
|
||||
if printit:
|
||||
print("Level:", level)
|
||||
for child in box.children:
|
||||
if printit:
|
||||
print(child)
|
||||
ax.add_patch(
|
||||
patches.Rectangle(
|
||||
(child.left.value(), child.bottom.value()), # (x, y)
|
||||
child.width.value(), # width
|
||||
child.height.value(), # height
|
||||
fc='none',
|
||||
alpha=0.8,
|
||||
ec=colors[level]
|
||||
)
|
||||
)
|
||||
if level > 0:
|
||||
name = child.name.split('.')[-1]
|
||||
if level % 2 == 0:
|
||||
ax.text(child.left.value(), child.bottom.value(), name,
|
||||
size=12-level, color=colors[level])
|
||||
else:
|
||||
ax.text(child.right.value(), child.top.value(), name,
|
||||
ha='right', va='top', size=12-level,
|
||||
color=colors[level])
|
||||
|
||||
plot_children(ax, child, level=level+1, printit=printit)
|
1397
venv/Lib/site-packages/matplotlib/_mathtext_data.py
Normal file
1397
venv/Lib/site-packages/matplotlib/_mathtext_data.py
Normal file
File diff suppressed because it is too large
Load diff
BIN
venv/Lib/site-packages/matplotlib/_path.cp36-win32.pyd
Normal file
BIN
venv/Lib/site-packages/matplotlib/_path.cp36-win32.pyd
Normal file
Binary file not shown.
141
venv/Lib/site-packages/matplotlib/_pylab_helpers.py
Normal file
141
venv/Lib/site-packages/matplotlib/_pylab_helpers.py
Normal file
|
@ -0,0 +1,141 @@
|
|||
"""
|
||||
Manage figures for the pyplot interface.
|
||||
"""
|
||||
|
||||
import atexit
|
||||
from collections import OrderedDict
|
||||
import gc
|
||||
|
||||
|
||||
class Gcf:
|
||||
"""
|
||||
Singleton to maintain the relation between figures and their managers, and
|
||||
keep track of and "active" figure and manager.
|
||||
|
||||
The canvas of a figure created through pyplot is associated with a figure
|
||||
manager, which handles the interaction between the figure and the backend.
|
||||
pyplot keeps track of figure managers using an identifier, the "figure
|
||||
number" or "manager number" (which can actually be any hashable value);
|
||||
this number is available as the :attr:`number` attribute of the manager.
|
||||
|
||||
This class is never instantiated; it consists of an `OrderedDict` mapping
|
||||
figure/manager numbers to managers, and a set of class methods that
|
||||
manipulate this `OrderedDict`.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
figs : OrderedDict
|
||||
`OrderedDict` mapping numbers to managers; the active manager is at the
|
||||
end.
|
||||
"""
|
||||
|
||||
figs = OrderedDict()
|
||||
|
||||
@classmethod
|
||||
def get_fig_manager(cls, num):
|
||||
"""
|
||||
If manager number *num* exists, make it the active one and return it;
|
||||
otherwise return *None*.
|
||||
"""
|
||||
manager = cls.figs.get(num, None)
|
||||
if manager is not None:
|
||||
cls.set_active(manager)
|
||||
return manager
|
||||
|
||||
@classmethod
|
||||
def destroy(cls, num):
|
||||
"""
|
||||
Destroy manager *num* -- either a manager instance or a manager number.
|
||||
|
||||
In the interactive backends, this is bound to the window "destroy" and
|
||||
"delete" events.
|
||||
|
||||
It is recommended to pass a manager instance, to avoid confusion when
|
||||
two managers share the same number.
|
||||
"""
|
||||
if all(hasattr(num, attr) for attr in ["num", "_cidgcf", "destroy"]):
|
||||
manager = num
|
||||
if cls.figs.get(manager.num) is manager:
|
||||
cls.figs.pop(manager.num)
|
||||
else:
|
||||
return
|
||||
else:
|
||||
try:
|
||||
manager = cls.figs.pop(num)
|
||||
except KeyError:
|
||||
return
|
||||
manager.canvas.mpl_disconnect(manager._cidgcf)
|
||||
manager.destroy()
|
||||
gc.collect(1)
|
||||
|
||||
@classmethod
|
||||
def destroy_fig(cls, fig):
|
||||
"""Destroy figure *fig*."""
|
||||
num = next((manager.num for manager in cls.figs.values()
|
||||
if manager.canvas.figure == fig), None)
|
||||
if num is not None:
|
||||
cls.destroy(num)
|
||||
|
||||
@classmethod
|
||||
def destroy_all(cls):
|
||||
"""Destroy all figures."""
|
||||
# Reimport gc in case the module globals have already been removed
|
||||
# during interpreter shutdown.
|
||||
import gc
|
||||
for manager in list(cls.figs.values()):
|
||||
manager.canvas.mpl_disconnect(manager._cidgcf)
|
||||
manager.destroy()
|
||||
cls.figs.clear()
|
||||
gc.collect(1)
|
||||
|
||||
@classmethod
|
||||
def has_fignum(cls, num):
|
||||
"""Return whether figure number *num* exists."""
|
||||
return num in cls.figs
|
||||
|
||||
@classmethod
|
||||
def get_all_fig_managers(cls):
|
||||
"""Return a list of figure managers."""
|
||||
return list(cls.figs.values())
|
||||
|
||||
@classmethod
|
||||
def get_num_fig_managers(cls):
|
||||
"""Return the number of figures being managed."""
|
||||
return len(cls.figs)
|
||||
|
||||
@classmethod
|
||||
def get_active(cls):
|
||||
"""Return the active manager, or *None* if there is no manager."""
|
||||
return next(reversed(cls.figs.values())) if cls.figs else None
|
||||
|
||||
@classmethod
|
||||
def _set_new_active_manager(cls, manager):
|
||||
"""Adopt *manager* into pyplot and make it the active manager."""
|
||||
if not hasattr(manager, "_cidgcf"):
|
||||
manager._cidgcf = manager.canvas.mpl_connect(
|
||||
"button_press_event", lambda event: cls.set_active(manager))
|
||||
fig = manager.canvas.figure
|
||||
fig.number = manager.num
|
||||
label = fig.get_label()
|
||||
if label:
|
||||
manager.set_window_title(label)
|
||||
cls.set_active(manager)
|
||||
|
||||
@classmethod
|
||||
def set_active(cls, manager):
|
||||
"""Make *manager* the active manager."""
|
||||
cls.figs[manager.num] = manager
|
||||
cls.figs.move_to_end(manager.num)
|
||||
|
||||
@classmethod
|
||||
def draw_all(cls, force=False):
|
||||
"""
|
||||
Redraw all stale managed figures, or, if *force* is True, all managed
|
||||
figures.
|
||||
"""
|
||||
for manager in cls.get_all_fig_managers():
|
||||
if force or manager.canvas.figure.stale:
|
||||
manager.canvas.draw_idle()
|
||||
|
||||
|
||||
atexit.register(Gcf.destroy_all)
|
BIN
venv/Lib/site-packages/matplotlib/_qhull.cp36-win32.pyd
Normal file
BIN
venv/Lib/site-packages/matplotlib/_qhull.cp36-win32.pyd
Normal file
Binary file not shown.
38
venv/Lib/site-packages/matplotlib/_text_layout.py
Normal file
38
venv/Lib/site-packages/matplotlib/_text_layout.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
"""
|
||||
Text layouting utilities.
|
||||
"""
|
||||
|
||||
from .ft2font import KERNING_DEFAULT, LOAD_NO_HINTING
|
||||
|
||||
|
||||
def layout(string, font, *, kern_mode=KERNING_DEFAULT):
|
||||
"""
|
||||
Render *string* with *font*. For each character in *string*, yield a
|
||||
(glyph-index, x-position) pair. When such a pair is yielded, the font's
|
||||
glyph is set to the corresponding character.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
string : str
|
||||
The string to be rendered.
|
||||
font : FT2Font
|
||||
The font.
|
||||
kern_mode : int
|
||||
A FreeType kerning mode.
|
||||
|
||||
Yields
|
||||
------
|
||||
glyph_index : int
|
||||
x_position : float
|
||||
"""
|
||||
x = 0
|
||||
last_glyph_idx = None
|
||||
for char in string:
|
||||
glyph_idx = font.get_char_index(ord(char))
|
||||
kern = (font.get_kerning(last_glyph_idx, glyph_idx, kern_mode)
|
||||
if last_glyph_idx is not None else 0) / 64
|
||||
x += kern
|
||||
glyph = font.load_glyph(glyph_idx, flags=LOAD_NO_HINTING)
|
||||
yield glyph_idx, x
|
||||
x += glyph.linearHoriAdvance / 65536
|
||||
last_glyph_idx = glyph_idx
|
BIN
venv/Lib/site-packages/matplotlib/_tri.cp36-win32.pyd
Normal file
BIN
venv/Lib/site-packages/matplotlib/_tri.cp36-win32.pyd
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/matplotlib/_ttconv.cp36-win32.pyd
Normal file
BIN
venv/Lib/site-packages/matplotlib/_ttconv.cp36-win32.pyd
Normal file
Binary file not shown.
21
venv/Lib/site-packages/matplotlib/_version.py
Normal file
21
venv/Lib/site-packages/matplotlib/_version.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
|
||||
# This file was generated by 'versioneer.py' (0.15) from
|
||||
# revision-control system data, or from the parent directory name of an
|
||||
# unpacked source archive. Distribution tarballs contain a pre-generated copy
|
||||
# of this file.
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
version_json = '''
|
||||
{
|
||||
"dirty": false,
|
||||
"error": null,
|
||||
"full-revisionid": "6e4d72c663c9930115720ac469341ed56a9505ec",
|
||||
"version": "3.3.2"
|
||||
}
|
||||
''' # END VERSION_JSON
|
||||
|
||||
|
||||
def get_versions():
|
||||
return json.loads(version_json)
|
528
venv/Lib/site-packages/matplotlib/afm.py
Normal file
528
venv/Lib/site-packages/matplotlib/afm.py
Normal file
|
@ -0,0 +1,528 @@
|
|||
"""
|
||||
A python interface to Adobe Font Metrics Files.
|
||||
|
||||
Although a number of other python implementations exist, and may be more
|
||||
complete than this, it was decided not to go with them because they were
|
||||
either:
|
||||
|
||||
1) copyrighted or used a non-BSD compatible license
|
||||
2) had too many dependencies and a free standing lib was needed
|
||||
3) did more than needed and it was easier to write afresh rather than
|
||||
figure out how to get just what was needed.
|
||||
|
||||
It is pretty easy to use, and has no external dependencies:
|
||||
|
||||
>>> import matplotlib as mpl
|
||||
>>> from pathlib import Path
|
||||
>>> afm_path = Path(mpl.get_data_path(), 'fonts', 'afm', 'ptmr8a.afm')
|
||||
>>>
|
||||
>>> from matplotlib.afm import AFM
|
||||
>>> with afm_path.open('rb') as fh:
|
||||
... afm = AFM(fh)
|
||||
>>> afm.string_width_height('What the heck?')
|
||||
(6220.0, 694)
|
||||
>>> afm.get_fontname()
|
||||
'Times-Roman'
|
||||
>>> afm.get_kern_dist('A', 'f')
|
||||
0
|
||||
>>> afm.get_kern_dist('A', 'y')
|
||||
-92.0
|
||||
>>> afm.get_bbox_char('!')
|
||||
[130, -9, 238, 676]
|
||||
|
||||
As in the Adobe Font Metrics File Format Specification, all dimensions
|
||||
are given in units of 1/1000 of the scale factor (point size) of the font
|
||||
being used.
|
||||
"""
|
||||
|
||||
from collections import namedtuple
|
||||
import logging
|
||||
import re
|
||||
|
||||
from ._mathtext_data import uni2type1
|
||||
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _to_int(x):
|
||||
# Some AFM files have floats where we are expecting ints -- there is
|
||||
# probably a better way to handle this (support floats, round rather than
|
||||
# truncate). But I don't know what the best approach is now and this
|
||||
# change to _to_int should at least prevent Matplotlib from crashing on
|
||||
# these. JDH (2009-11-06)
|
||||
return int(float(x))
|
||||
|
||||
|
||||
def _to_float(x):
|
||||
# Some AFM files use "," instead of "." as decimal separator -- this
|
||||
# shouldn't be ambiguous (unless someone is wicked enough to use "," as
|
||||
# thousands separator...).
|
||||
if isinstance(x, bytes):
|
||||
# Encoding doesn't really matter -- if we have codepoints >127 the call
|
||||
# to float() will error anyways.
|
||||
x = x.decode('latin-1')
|
||||
return float(x.replace(',', '.'))
|
||||
|
||||
|
||||
def _to_str(x):
|
||||
return x.decode('utf8')
|
||||
|
||||
|
||||
def _to_list_of_ints(s):
|
||||
s = s.replace(b',', b' ')
|
||||
return [_to_int(val) for val in s.split()]
|
||||
|
||||
|
||||
def _to_list_of_floats(s):
|
||||
return [_to_float(val) for val in s.split()]
|
||||
|
||||
|
||||
def _to_bool(s):
|
||||
if s.lower().strip() in (b'false', b'0', b'no'):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def _parse_header(fh):
|
||||
"""
|
||||
Read the font metrics header (up to the char metrics) and returns
|
||||
a dictionary mapping *key* to *val*. *val* will be converted to the
|
||||
appropriate python type as necessary; e.g.:
|
||||
|
||||
* 'False'->False
|
||||
* '0'->0
|
||||
* '-168 -218 1000 898'-> [-168, -218, 1000, 898]
|
||||
|
||||
Dictionary keys are
|
||||
|
||||
StartFontMetrics, FontName, FullName, FamilyName, Weight,
|
||||
ItalicAngle, IsFixedPitch, FontBBox, UnderlinePosition,
|
||||
UnderlineThickness, Version, Notice, EncodingScheme, CapHeight,
|
||||
XHeight, Ascender, Descender, StartCharMetrics
|
||||
"""
|
||||
header_converters = {
|
||||
b'StartFontMetrics': _to_float,
|
||||
b'FontName': _to_str,
|
||||
b'FullName': _to_str,
|
||||
b'FamilyName': _to_str,
|
||||
b'Weight': _to_str,
|
||||
b'ItalicAngle': _to_float,
|
||||
b'IsFixedPitch': _to_bool,
|
||||
b'FontBBox': _to_list_of_ints,
|
||||
b'UnderlinePosition': _to_float,
|
||||
b'UnderlineThickness': _to_float,
|
||||
b'Version': _to_str,
|
||||
# Some AFM files have non-ASCII characters (which are not allowed by
|
||||
# the spec). Given that there is actually no public API to even access
|
||||
# this field, just return it as straight bytes.
|
||||
b'Notice': lambda x: x,
|
||||
b'EncodingScheme': _to_str,
|
||||
b'CapHeight': _to_float, # Is the second version a mistake, or
|
||||
b'Capheight': _to_float, # do some AFM files contain 'Capheight'? -JKS
|
||||
b'XHeight': _to_float,
|
||||
b'Ascender': _to_float,
|
||||
b'Descender': _to_float,
|
||||
b'StdHW': _to_float,
|
||||
b'StdVW': _to_float,
|
||||
b'StartCharMetrics': _to_int,
|
||||
b'CharacterSet': _to_str,
|
||||
b'Characters': _to_int,
|
||||
}
|
||||
d = {}
|
||||
first_line = True
|
||||
for line in fh:
|
||||
line = line.rstrip()
|
||||
if line.startswith(b'Comment'):
|
||||
continue
|
||||
lst = line.split(b' ', 1)
|
||||
key = lst[0]
|
||||
if first_line:
|
||||
# AFM spec, Section 4: The StartFontMetrics keyword
|
||||
# [followed by a version number] must be the first line in
|
||||
# the file, and the EndFontMetrics keyword must be the
|
||||
# last non-empty line in the file. We just check the
|
||||
# first header entry.
|
||||
if key != b'StartFontMetrics':
|
||||
raise RuntimeError('Not an AFM file')
|
||||
first_line = False
|
||||
if len(lst) == 2:
|
||||
val = lst[1]
|
||||
else:
|
||||
val = b''
|
||||
try:
|
||||
converter = header_converters[key]
|
||||
except KeyError:
|
||||
_log.error('Found an unknown keyword in AFM header (was %r)' % key)
|
||||
continue
|
||||
try:
|
||||
d[key] = converter(val)
|
||||
except ValueError:
|
||||
_log.error('Value error parsing header in AFM: %s, %s', key, val)
|
||||
continue
|
||||
if key == b'StartCharMetrics':
|
||||
break
|
||||
else:
|
||||
raise RuntimeError('Bad parse')
|
||||
return d
|
||||
|
||||
|
||||
CharMetrics = namedtuple('CharMetrics', 'width, name, bbox')
|
||||
CharMetrics.__doc__ = """
|
||||
Represents the character metrics of a single character.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The fields do currently only describe a subset of character metrics
|
||||
information defined in the AFM standard.
|
||||
"""
|
||||
CharMetrics.width.__doc__ = """The character width (WX)."""
|
||||
CharMetrics.name.__doc__ = """The character name (N)."""
|
||||
CharMetrics.bbox.__doc__ = """
|
||||
The bbox of the character (B) as a tuple (*llx*, *lly*, *urx*, *ury*)."""
|
||||
|
||||
|
||||
def _parse_char_metrics(fh):
|
||||
"""
|
||||
Parse the given filehandle for character metrics information and return
|
||||
the information as dicts.
|
||||
|
||||
It is assumed that the file cursor is on the line behind
|
||||
'StartCharMetrics'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
ascii_d : dict
|
||||
A mapping "ASCII num of the character" to `.CharMetrics`.
|
||||
name_d : dict
|
||||
A mapping "character name" to `.CharMetrics`.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function is incomplete per the standard, but thus far parses
|
||||
all the sample afm files tried.
|
||||
"""
|
||||
required_keys = {'C', 'WX', 'N', 'B'}
|
||||
|
||||
ascii_d = {}
|
||||
name_d = {}
|
||||
for line in fh:
|
||||
# We are defensively letting values be utf8. The spec requires
|
||||
# ascii, but there are non-compliant fonts in circulation
|
||||
line = _to_str(line.rstrip()) # Convert from byte-literal
|
||||
if line.startswith('EndCharMetrics'):
|
||||
return ascii_d, name_d
|
||||
# Split the metric line into a dictionary, keyed by metric identifiers
|
||||
vals = dict(s.strip().split(' ', 1) for s in line.split(';') if s)
|
||||
# There may be other metrics present, but only these are needed
|
||||
if not required_keys.issubset(vals):
|
||||
raise RuntimeError('Bad char metrics line: %s' % line)
|
||||
num = _to_int(vals['C'])
|
||||
wx = _to_float(vals['WX'])
|
||||
name = vals['N']
|
||||
bbox = _to_list_of_floats(vals['B'])
|
||||
bbox = list(map(int, bbox))
|
||||
metrics = CharMetrics(wx, name, bbox)
|
||||
# Workaround: If the character name is 'Euro', give it the
|
||||
# corresponding character code, according to WinAnsiEncoding (see PDF
|
||||
# Reference).
|
||||
if name == 'Euro':
|
||||
num = 128
|
||||
elif name == 'minus':
|
||||
num = ord("\N{MINUS SIGN}") # 0x2212
|
||||
if num != -1:
|
||||
ascii_d[num] = metrics
|
||||
name_d[name] = metrics
|
||||
raise RuntimeError('Bad parse')
|
||||
|
||||
|
||||
def _parse_kern_pairs(fh):
|
||||
"""
|
||||
Return a kern pairs dictionary; keys are (*char1*, *char2*) tuples and
|
||||
values are the kern pair value. For example, a kern pairs line like
|
||||
``KPX A y -50``
|
||||
|
||||
will be represented as::
|
||||
|
||||
d[ ('A', 'y') ] = -50
|
||||
|
||||
"""
|
||||
|
||||
line = next(fh)
|
||||
if not line.startswith(b'StartKernPairs'):
|
||||
raise RuntimeError('Bad start of kern pairs data: %s' % line)
|
||||
|
||||
d = {}
|
||||
for line in fh:
|
||||
line = line.rstrip()
|
||||
if not line:
|
||||
continue
|
||||
if line.startswith(b'EndKernPairs'):
|
||||
next(fh) # EndKernData
|
||||
return d
|
||||
vals = line.split()
|
||||
if len(vals) != 4 or vals[0] != b'KPX':
|
||||
raise RuntimeError('Bad kern pairs line: %s' % line)
|
||||
c1, c2, val = _to_str(vals[1]), _to_str(vals[2]), _to_float(vals[3])
|
||||
d[(c1, c2)] = val
|
||||
raise RuntimeError('Bad kern pairs parse')
|
||||
|
||||
|
||||
CompositePart = namedtuple('CompositePart', 'name, dx, dy')
|
||||
CompositePart.__doc__ = """
|
||||
Represents the information on a composite element of a composite char."""
|
||||
CompositePart.name.__doc__ = """Name of the part, e.g. 'acute'."""
|
||||
CompositePart.dx.__doc__ = """x-displacement of the part from the origin."""
|
||||
CompositePart.dy.__doc__ = """y-displacement of the part from the origin."""
|
||||
|
||||
|
||||
def _parse_composites(fh):
|
||||
"""
|
||||
Parse the given filehandle for composites information return them as a
|
||||
dict.
|
||||
|
||||
It is assumed that the file cursor is on the line behind 'StartComposites'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
A dict mapping composite character names to a parts list. The parts
|
||||
list is a list of `.CompositePart` entries describing the parts of
|
||||
the composite.
|
||||
|
||||
Examples
|
||||
--------
|
||||
A composite definition line::
|
||||
|
||||
CC Aacute 2 ; PCC A 0 0 ; PCC acute 160 170 ;
|
||||
|
||||
will be represented as::
|
||||
|
||||
composites['Aacute'] = [CompositePart(name='A', dx=0, dy=0),
|
||||
CompositePart(name='acute', dx=160, dy=170)]
|
||||
|
||||
"""
|
||||
composites = {}
|
||||
for line in fh:
|
||||
line = line.rstrip()
|
||||
if not line:
|
||||
continue
|
||||
if line.startswith(b'EndComposites'):
|
||||
return composites
|
||||
vals = line.split(b';')
|
||||
cc = vals[0].split()
|
||||
name, numParts = cc[1], _to_int(cc[2])
|
||||
pccParts = []
|
||||
for s in vals[1:-1]:
|
||||
pcc = s.split()
|
||||
part = CompositePart(pcc[1], _to_float(pcc[2]), _to_float(pcc[3]))
|
||||
pccParts.append(part)
|
||||
composites[name] = pccParts
|
||||
|
||||
raise RuntimeError('Bad composites parse')
|
||||
|
||||
|
||||
def _parse_optional(fh):
|
||||
"""
|
||||
Parse the optional fields for kern pair data and composites.
|
||||
|
||||
Returns
|
||||
-------
|
||||
kern_data : dict
|
||||
A dict containing kerning information. May be empty.
|
||||
See `._parse_kern_pairs`.
|
||||
composites : dict
|
||||
A dict containing composite information. May be empty.
|
||||
See `._parse_composites`.
|
||||
"""
|
||||
optional = {
|
||||
b'StartKernData': _parse_kern_pairs,
|
||||
b'StartComposites': _parse_composites,
|
||||
}
|
||||
|
||||
d = {b'StartKernData': {},
|
||||
b'StartComposites': {}}
|
||||
for line in fh:
|
||||
line = line.rstrip()
|
||||
if not line:
|
||||
continue
|
||||
key = line.split()[0]
|
||||
|
||||
if key in optional:
|
||||
d[key] = optional[key](fh)
|
||||
|
||||
return d[b'StartKernData'], d[b'StartComposites']
|
||||
|
||||
|
||||
class AFM:
|
||||
|
||||
def __init__(self, fh):
|
||||
"""Parse the AFM file in file object *fh*."""
|
||||
self._header = _parse_header(fh)
|
||||
self._metrics, self._metrics_by_name = _parse_char_metrics(fh)
|
||||
self._kern, self._composite = _parse_optional(fh)
|
||||
|
||||
def get_bbox_char(self, c, isord=False):
|
||||
if not isord:
|
||||
c = ord(c)
|
||||
return self._metrics[c].bbox
|
||||
|
||||
def string_width_height(self, s):
|
||||
"""
|
||||
Return the string width (including kerning) and string height
|
||||
as a (*w*, *h*) tuple.
|
||||
"""
|
||||
if not len(s):
|
||||
return 0, 0
|
||||
total_width = 0
|
||||
namelast = None
|
||||
miny = 1e9
|
||||
maxy = 0
|
||||
for c in s:
|
||||
if c == '\n':
|
||||
continue
|
||||
wx, name, bbox = self._metrics[ord(c)]
|
||||
|
||||
total_width += wx + self._kern.get((namelast, name), 0)
|
||||
l, b, w, h = bbox
|
||||
miny = min(miny, b)
|
||||
maxy = max(maxy, b + h)
|
||||
|
||||
namelast = name
|
||||
|
||||
return total_width, maxy - miny
|
||||
|
||||
def get_str_bbox_and_descent(self, s):
|
||||
"""Return the string bounding box and the maximal descent."""
|
||||
if not len(s):
|
||||
return 0, 0, 0, 0, 0
|
||||
total_width = 0
|
||||
namelast = None
|
||||
miny = 1e9
|
||||
maxy = 0
|
||||
left = 0
|
||||
if not isinstance(s, str):
|
||||
s = _to_str(s)
|
||||
for c in s:
|
||||
if c == '\n':
|
||||
continue
|
||||
name = uni2type1.get(ord(c), f"uni{ord(c):04X}")
|
||||
try:
|
||||
wx, _, bbox = self._metrics_by_name[name]
|
||||
except KeyError:
|
||||
name = 'question'
|
||||
wx, _, bbox = self._metrics_by_name[name]
|
||||
total_width += wx + self._kern.get((namelast, name), 0)
|
||||
l, b, w, h = bbox
|
||||
left = min(left, l)
|
||||
miny = min(miny, b)
|
||||
maxy = max(maxy, b + h)
|
||||
|
||||
namelast = name
|
||||
|
||||
return left, miny, total_width, maxy - miny, -miny
|
||||
|
||||
def get_str_bbox(self, s):
|
||||
"""Return the string bounding box."""
|
||||
return self.get_str_bbox_and_descent(s)[:4]
|
||||
|
||||
def get_name_char(self, c, isord=False):
|
||||
"""Get the name of the character, i.e., ';' is 'semicolon'."""
|
||||
if not isord:
|
||||
c = ord(c)
|
||||
return self._metrics[c].name
|
||||
|
||||
def get_width_char(self, c, isord=False):
|
||||
"""
|
||||
Get the width of the character from the character metric WX field.
|
||||
"""
|
||||
if not isord:
|
||||
c = ord(c)
|
||||
return self._metrics[c].width
|
||||
|
||||
def get_width_from_char_name(self, name):
|
||||
"""Get the width of the character from a type1 character name."""
|
||||
return self._metrics_by_name[name].width
|
||||
|
||||
def get_height_char(self, c, isord=False):
|
||||
"""Get the bounding box (ink) height of character *c* (space is 0)."""
|
||||
if not isord:
|
||||
c = ord(c)
|
||||
return self._metrics[c].bbox[-1]
|
||||
|
||||
def get_kern_dist(self, c1, c2):
|
||||
"""
|
||||
Return the kerning pair distance (possibly 0) for chars *c1* and *c2*.
|
||||
"""
|
||||
name1, name2 = self.get_name_char(c1), self.get_name_char(c2)
|
||||
return self.get_kern_dist_from_name(name1, name2)
|
||||
|
||||
def get_kern_dist_from_name(self, name1, name2):
|
||||
"""
|
||||
Return the kerning pair distance (possibly 0) for chars
|
||||
*name1* and *name2*.
|
||||
"""
|
||||
return self._kern.get((name1, name2), 0)
|
||||
|
||||
def get_fontname(self):
|
||||
"""Return the font name, e.g., 'Times-Roman'."""
|
||||
return self._header[b'FontName']
|
||||
|
||||
def get_fullname(self):
|
||||
"""Return the font full name, e.g., 'Times-Roman'."""
|
||||
name = self._header.get(b'FullName')
|
||||
if name is None: # use FontName as a substitute
|
||||
name = self._header[b'FontName']
|
||||
return name
|
||||
|
||||
def get_familyname(self):
|
||||
"""Return the font family name, e.g., 'Times'."""
|
||||
name = self._header.get(b'FamilyName')
|
||||
if name is not None:
|
||||
return name
|
||||
|
||||
# FamilyName not specified so we'll make a guess
|
||||
name = self.get_fullname()
|
||||
extras = (r'(?i)([ -](regular|plain|italic|oblique|bold|semibold|'
|
||||
r'light|ultralight|extra|condensed))+$')
|
||||
return re.sub(extras, '', name)
|
||||
|
||||
@property
|
||||
def family_name(self):
|
||||
"""The font family name, e.g., 'Times'."""
|
||||
return self.get_familyname()
|
||||
|
||||
def get_weight(self):
|
||||
"""Return the font weight, e.g., 'Bold' or 'Roman'."""
|
||||
return self._header[b'Weight']
|
||||
|
||||
def get_angle(self):
|
||||
"""Return the fontangle as float."""
|
||||
return self._header[b'ItalicAngle']
|
||||
|
||||
def get_capheight(self):
|
||||
"""Return the cap height as float."""
|
||||
return self._header[b'CapHeight']
|
||||
|
||||
def get_xheight(self):
|
||||
"""Return the xheight as float."""
|
||||
return self._header[b'XHeight']
|
||||
|
||||
def get_underline_thickness(self):
|
||||
"""Return the underline thickness as float."""
|
||||
return self._header[b'UnderlineThickness']
|
||||
|
||||
def get_horizontal_stem_width(self):
|
||||
"""
|
||||
Return the standard horizontal stem width as float, or *None* if
|
||||
not specified in AFM file.
|
||||
"""
|
||||
return self._header.get(b'StdHW', None)
|
||||
|
||||
def get_vertical_stem_width(self):
|
||||
"""
|
||||
Return the standard vertical stem width as float, or *None* if
|
||||
not specified in AFM file.
|
||||
"""
|
||||
return self._header.get(b'StdVW', None)
|
1735
venv/Lib/site-packages/matplotlib/animation.py
Normal file
1735
venv/Lib/site-packages/matplotlib/animation.py
Normal file
File diff suppressed because it is too large
Load diff
1635
venv/Lib/site-packages/matplotlib/artist.py
Normal file
1635
venv/Lib/site-packages/matplotlib/artist.py
Normal file
File diff suppressed because it is too large
Load diff
2
venv/Lib/site-packages/matplotlib/axes/__init__.py
Normal file
2
venv/Lib/site-packages/matplotlib/axes/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
from ._subplots import *
|
||||
from ._axes import *
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
8077
venv/Lib/site-packages/matplotlib/axes/_axes.py
Normal file
8077
venv/Lib/site-packages/matplotlib/axes/_axes.py
Normal file
File diff suppressed because it is too large
Load diff
4286
venv/Lib/site-packages/matplotlib/axes/_base.py
Normal file
4286
venv/Lib/site-packages/matplotlib/axes/_base.py
Normal file
File diff suppressed because it is too large
Load diff
386
venv/Lib/site-packages/matplotlib/axes/_secondary_axes.py
Normal file
386
venv/Lib/site-packages/matplotlib/axes/_secondary_axes.py
Normal file
|
@ -0,0 +1,386 @@
|
|||
import numpy as np
|
||||
|
||||
import matplotlib.cbook as cbook
|
||||
import matplotlib.docstring as docstring
|
||||
import matplotlib.ticker as mticker
|
||||
import matplotlib.transforms as mtransforms
|
||||
from matplotlib.axes._base import _AxesBase
|
||||
|
||||
|
||||
def _make_secondary_locator(rect, parent):
|
||||
"""
|
||||
Helper function to locate the secondary axes.
|
||||
|
||||
A locator gets used in `Axes.set_aspect` to override the default
|
||||
locations... It is a function that takes an axes object and
|
||||
a renderer and tells `set_aspect` where it is to be placed.
|
||||
|
||||
This locator make the transform be in axes-relative co-coordinates
|
||||
because that is how we specify the "location" of the secondary axes.
|
||||
|
||||
Here *rect* is a rectangle [l, b, w, h] that specifies the
|
||||
location for the axes in the transform given by *trans* on the
|
||||
*parent*.
|
||||
"""
|
||||
_rect = mtransforms.Bbox.from_bounds(*rect)
|
||||
def secondary_locator(ax, renderer):
|
||||
# delay evaluating transform until draw time because the
|
||||
# parent transform may have changed (i.e. if window reesized)
|
||||
bb = mtransforms.TransformedBbox(_rect, parent.transAxes)
|
||||
tr = parent.figure.transFigure.inverted()
|
||||
bb = mtransforms.TransformedBbox(bb, tr)
|
||||
return bb
|
||||
|
||||
return secondary_locator
|
||||
|
||||
|
||||
class SecondaryAxis(_AxesBase):
|
||||
"""
|
||||
General class to hold a Secondary_X/Yaxis.
|
||||
"""
|
||||
|
||||
def __init__(self, parent, orientation, location, functions, **kwargs):
|
||||
"""
|
||||
See `.secondary_xaxis` and `.secondary_yaxis` for the doc string.
|
||||
While there is no need for this to be private, it should really be
|
||||
called by those higher level functions.
|
||||
"""
|
||||
|
||||
self._functions = functions
|
||||
self._parent = parent
|
||||
self._orientation = orientation
|
||||
self._ticks_set = False
|
||||
|
||||
if self._orientation == 'x':
|
||||
super().__init__(self._parent.figure, [0, 1., 1, 0.0001], **kwargs)
|
||||
self._axis = self.xaxis
|
||||
self._locstrings = ['top', 'bottom']
|
||||
self._otherstrings = ['left', 'right']
|
||||
elif self._orientation == 'y':
|
||||
super().__init__(self._parent.figure, [0, 1., 0.0001, 1], **kwargs)
|
||||
self._axis = self.yaxis
|
||||
self._locstrings = ['right', 'left']
|
||||
self._otherstrings = ['top', 'bottom']
|
||||
self._parentscale = None
|
||||
# this gets positioned w/o constrained_layout so exclude:
|
||||
self._layoutbox = None
|
||||
self._poslayoutbox = None
|
||||
|
||||
self.set_location(location)
|
||||
self.set_functions(functions)
|
||||
|
||||
# styling:
|
||||
if self._orientation == 'x':
|
||||
otheraxis = self.yaxis
|
||||
else:
|
||||
otheraxis = self.xaxis
|
||||
|
||||
otheraxis.set_major_locator(mticker.NullLocator())
|
||||
otheraxis.set_ticks_position('none')
|
||||
|
||||
for st in self._otherstrings:
|
||||
self.spines[st].set_visible(False)
|
||||
for st in self._locstrings:
|
||||
self.spines[st].set_visible(True)
|
||||
|
||||
if self._pos < 0.5:
|
||||
# flip the location strings...
|
||||
self._locstrings = self._locstrings[::-1]
|
||||
self.set_alignment(self._locstrings[0])
|
||||
|
||||
def set_alignment(self, align):
|
||||
"""
|
||||
Set if axes spine and labels are drawn at top or bottom (or left/right)
|
||||
of the axes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
align : str
|
||||
either 'top' or 'bottom' for orientation='x' or
|
||||
'left' or 'right' for orientation='y' axis.
|
||||
"""
|
||||
cbook._check_in_list(self._locstrings, align=align)
|
||||
if align == self._locstrings[1]: # Need to change the orientation.
|
||||
self._locstrings = self._locstrings[::-1]
|
||||
self.spines[self._locstrings[0]].set_visible(True)
|
||||
self.spines[self._locstrings[1]].set_visible(False)
|
||||
self._axis.set_ticks_position(align)
|
||||
self._axis.set_label_position(align)
|
||||
|
||||
def set_location(self, location):
|
||||
"""
|
||||
Set the vertical or horizontal location of the axes in
|
||||
parent-normalized coordinates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
location : {'top', 'bottom', 'left', 'right'} or float
|
||||
The position to put the secondary axis. Strings can be 'top' or
|
||||
'bottom' for orientation='x' and 'right' or 'left' for
|
||||
orientation='y'. A float indicates the relative position on the
|
||||
parent axes to put the new axes, 0.0 being the bottom (or left)
|
||||
and 1.0 being the top (or right).
|
||||
"""
|
||||
|
||||
# This puts the rectangle into figure-relative coordinates.
|
||||
if isinstance(location, str):
|
||||
if location in ['top', 'right']:
|
||||
self._pos = 1.
|
||||
elif location in ['bottom', 'left']:
|
||||
self._pos = 0.
|
||||
else:
|
||||
raise ValueError(
|
||||
f"location must be {self._locstrings[0]!r}, "
|
||||
f"{self._locstrings[1]!r}, or a float, not {location!r}")
|
||||
else:
|
||||
self._pos = location
|
||||
self._loc = location
|
||||
|
||||
if self._orientation == 'x':
|
||||
bounds = [0, self._pos, 1., 1e-10]
|
||||
else:
|
||||
bounds = [self._pos, 0, 1e-10, 1]
|
||||
|
||||
secondary_locator = _make_secondary_locator(bounds, self._parent)
|
||||
|
||||
# this locator lets the axes move in the parent axes coordinates.
|
||||
# so it never needs to know where the parent is explicitly in
|
||||
# figure coordinates.
|
||||
# it gets called in `ax.apply_aspect() (of all places)
|
||||
self.set_axes_locator(secondary_locator)
|
||||
|
||||
def apply_aspect(self, position=None):
|
||||
# docstring inherited.
|
||||
self._set_lims()
|
||||
super().apply_aspect(position)
|
||||
|
||||
@cbook._make_keyword_only("3.2", "minor")
|
||||
def set_ticks(self, ticks, minor=False):
|
||||
"""
|
||||
Set the x ticks with list of *ticks*
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ticks : list
|
||||
List of x-axis tick locations.
|
||||
minor : bool, default: False
|
||||
If ``False`` sets major ticks, if ``True`` sets minor ticks.
|
||||
"""
|
||||
ret = self._axis.set_ticks(ticks, minor=minor)
|
||||
self.stale = True
|
||||
self._ticks_set = True
|
||||
return ret
|
||||
|
||||
def set_functions(self, functions):
|
||||
"""
|
||||
Set how the secondary axis converts limits from the parent axes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
functions : 2-tuple of func, or `Transform` with an inverse.
|
||||
Transform between the parent axis values and the secondary axis
|
||||
values.
|
||||
|
||||
If supplied as a 2-tuple of functions, the first function is
|
||||
the forward transform function and the second is the inverse
|
||||
transform.
|
||||
|
||||
If a transform is supplied, then the transform must have an
|
||||
inverse.
|
||||
"""
|
||||
if (isinstance(functions, tuple) and len(functions) == 2 and
|
||||
callable(functions[0]) and callable(functions[1])):
|
||||
# make an arbitrary convert from a two-tuple of functions
|
||||
# forward and inverse.
|
||||
self._functions = functions
|
||||
elif functions is None:
|
||||
self._functions = (lambda x: x, lambda x: x)
|
||||
else:
|
||||
raise ValueError('functions argument of secondary axes '
|
||||
'must be a two-tuple of callable functions '
|
||||
'with the first function being the transform '
|
||||
'and the second being the inverse')
|
||||
self._set_scale()
|
||||
|
||||
# Should be changed to draw(self, renderer) once the deprecation of
|
||||
# renderer=None and of inframe expires.
|
||||
def draw(self, *args, **kwargs):
|
||||
"""
|
||||
Draw the secondary axes.
|
||||
|
||||
Consults the parent axes for its limits and converts them
|
||||
using the converter specified by
|
||||
`~.axes._secondary_axes.set_functions` (or *functions*
|
||||
parameter when axes initialized.)
|
||||
"""
|
||||
self._set_lims()
|
||||
# this sets the scale in case the parent has set its scale.
|
||||
self._set_scale()
|
||||
super().draw(*args, **kwargs)
|
||||
|
||||
def _set_scale(self):
|
||||
"""
|
||||
Check if parent has set its scale
|
||||
"""
|
||||
|
||||
if self._orientation == 'x':
|
||||
pscale = self._parent.xaxis.get_scale()
|
||||
set_scale = self.set_xscale
|
||||
if self._orientation == 'y':
|
||||
pscale = self._parent.yaxis.get_scale()
|
||||
set_scale = self.set_yscale
|
||||
if pscale == self._parentscale:
|
||||
return
|
||||
|
||||
if pscale == 'log':
|
||||
defscale = 'functionlog'
|
||||
else:
|
||||
defscale = 'function'
|
||||
|
||||
if self._ticks_set:
|
||||
ticks = self._axis.get_ticklocs()
|
||||
|
||||
# need to invert the roles here for the ticks to line up.
|
||||
set_scale(defscale, functions=self._functions[::-1])
|
||||
|
||||
# OK, set_scale sets the locators, but if we've called
|
||||
# axsecond.set_ticks, we want to keep those.
|
||||
if self._ticks_set:
|
||||
self._axis.set_major_locator(mticker.FixedLocator(ticks))
|
||||
|
||||
# If the parent scale doesn't change, we can skip this next time.
|
||||
self._parentscale = pscale
|
||||
|
||||
def _set_lims(self):
|
||||
"""
|
||||
Set the limits based on parent limits and the convert method
|
||||
between the parent and this secondary axes.
|
||||
"""
|
||||
if self._orientation == 'x':
|
||||
lims = self._parent.get_xlim()
|
||||
set_lim = self.set_xlim
|
||||
if self._orientation == 'y':
|
||||
lims = self._parent.get_ylim()
|
||||
set_lim = self.set_ylim
|
||||
order = lims[0] < lims[1]
|
||||
lims = self._functions[0](np.array(lims))
|
||||
neworder = lims[0] < lims[1]
|
||||
if neworder != order:
|
||||
# Flip because the transform will take care of the flipping.
|
||||
lims = lims[::-1]
|
||||
set_lim(lims)
|
||||
|
||||
def set_aspect(self, *args, **kwargs):
|
||||
"""
|
||||
Secondary axes cannot set the aspect ratio, so calling this just
|
||||
sets a warning.
|
||||
"""
|
||||
cbook._warn_external("Secondary axes can't set the aspect ratio")
|
||||
|
||||
def set_xlabel(self, xlabel, fontdict=None, labelpad=None, **kwargs):
|
||||
"""
|
||||
Set the label for the x-axis.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
xlabel : str
|
||||
The label text.
|
||||
|
||||
labelpad : float, default: ``self.xaxis.labelpad``
|
||||
Spacing in points between the label and the x-axis.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
**kwargs : `.Text` properties
|
||||
`.Text` properties control the appearance of the label.
|
||||
|
||||
See Also
|
||||
--------
|
||||
text : Documents the properties supported by `.Text`.
|
||||
"""
|
||||
if labelpad is not None:
|
||||
self.xaxis.labelpad = labelpad
|
||||
return self.xaxis.set_label_text(xlabel, fontdict, **kwargs)
|
||||
|
||||
def set_ylabel(self, ylabel, fontdict=None, labelpad=None, **kwargs):
|
||||
"""
|
||||
Set the label for the y-axis.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ylabel : str
|
||||
The label text.
|
||||
|
||||
labelpad : float, default: ``self.yaxis.labelpad``
|
||||
Spacing in points between the label and the y-axis.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
**kwargs : `.Text` properties
|
||||
`.Text` properties control the appearance of the label.
|
||||
|
||||
See Also
|
||||
--------
|
||||
text : Documents the properties supported by `.Text`.
|
||||
"""
|
||||
if labelpad is not None:
|
||||
self.yaxis.labelpad = labelpad
|
||||
return self.yaxis.set_label_text(ylabel, fontdict, **kwargs)
|
||||
|
||||
def set_color(self, color):
|
||||
"""
|
||||
Change the color of the secondary axes and all decorators.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
color : color
|
||||
"""
|
||||
if self._orientation == 'x':
|
||||
self.tick_params(axis='x', colors=color)
|
||||
self.spines['bottom'].set_color(color)
|
||||
self.spines['top'].set_color(color)
|
||||
self.xaxis.label.set_color(color)
|
||||
else:
|
||||
self.tick_params(axis='y', colors=color)
|
||||
self.spines['left'].set_color(color)
|
||||
self.spines['right'].set_color(color)
|
||||
self.yaxis.label.set_color(color)
|
||||
|
||||
|
||||
_secax_docstring = '''
|
||||
Warnings
|
||||
--------
|
||||
This method is experimental as of 3.1, and the API may change.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
location : {'top', 'bottom', 'left', 'right'} or float
|
||||
The position to put the secondary axis. Strings can be 'top' or
|
||||
'bottom' for orientation='x' and 'right' or 'left' for
|
||||
orientation='y'. A float indicates the relative position on the
|
||||
parent axes to put the new axes, 0.0 being the bottom (or left)
|
||||
and 1.0 being the top (or right).
|
||||
|
||||
functions : 2-tuple of func, or Transform with an inverse
|
||||
|
||||
If a 2-tuple of functions, the user specifies the transform
|
||||
function and its inverse. i.e.
|
||||
``functions=(lambda x: 2 / x, lambda x: 2 / x)`` would be an
|
||||
reciprocal transform with a factor of 2.
|
||||
|
||||
The user can also directly supply a subclass of
|
||||
`.transforms.Transform` so long as it has an inverse.
|
||||
|
||||
See :doc:`/gallery/subplots_axes_and_figures/secondary_axis`
|
||||
for examples of making these conversions.
|
||||
|
||||
Returns
|
||||
-------
|
||||
ax : axes._secondary_axes.SecondaryAxis
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
**kwargs : `~matplotlib.axes.Axes` properties.
|
||||
Other miscellaneous axes parameters.
|
||||
'''
|
||||
docstring.interpd.update(_secax_docstring=_secax_docstring)
|
242
venv/Lib/site-packages/matplotlib/axes/_subplots.py
Normal file
242
venv/Lib/site-packages/matplotlib/axes/_subplots.py
Normal file
|
@ -0,0 +1,242 @@
|
|||
import functools
|
||||
import uuid
|
||||
|
||||
from matplotlib import cbook, docstring
|
||||
import matplotlib.artist as martist
|
||||
from matplotlib.axes._axes import Axes
|
||||
from matplotlib.gridspec import GridSpec, SubplotSpec
|
||||
import matplotlib._layoutbox as layoutbox
|
||||
|
||||
|
||||
class SubplotBase:
|
||||
"""
|
||||
Base class for subplots, which are :class:`Axes` instances with
|
||||
additional methods to facilitate generating and manipulating a set
|
||||
of :class:`Axes` within a figure.
|
||||
"""
|
||||
|
||||
def __init__(self, fig, *args, **kwargs):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
fig : `matplotlib.figure.Figure`
|
||||
|
||||
*args : tuple (*nrows*, *ncols*, *index*) or int
|
||||
The array of subplots in the figure has dimensions ``(nrows,
|
||||
ncols)``, and *index* is the index of the subplot being created.
|
||||
*index* starts at 1 in the upper left corner and increases to the
|
||||
right.
|
||||
|
||||
If *nrows*, *ncols*, and *index* are all single digit numbers, then
|
||||
*args* can be passed as a single 3-digit number (e.g. 234 for
|
||||
(2, 3, 4)).
|
||||
|
||||
**kwargs
|
||||
Keyword arguments are passed to the Axes (sub)class constructor.
|
||||
"""
|
||||
|
||||
self.figure = fig
|
||||
self._subplotspec = SubplotSpec._from_subplot_args(fig, args)
|
||||
self.update_params()
|
||||
# _axes_class is set in the subplot_class_factory
|
||||
self._axes_class.__init__(self, fig, self.figbox, **kwargs)
|
||||
# add a layout box to this, for both the full axis, and the poss
|
||||
# of the axis. We need both because the axes may become smaller
|
||||
# due to parasitic axes and hence no longer fill the subplotspec.
|
||||
if self._subplotspec._layoutbox is None:
|
||||
self._layoutbox = None
|
||||
self._poslayoutbox = None
|
||||
else:
|
||||
name = self._subplotspec._layoutbox.name + '.ax'
|
||||
name = name + layoutbox.seq_id()
|
||||
self._layoutbox = layoutbox.LayoutBox(
|
||||
parent=self._subplotspec._layoutbox,
|
||||
name=name,
|
||||
artist=self)
|
||||
self._poslayoutbox = layoutbox.LayoutBox(
|
||||
parent=self._layoutbox,
|
||||
name=self._layoutbox.name+'.pos',
|
||||
pos=True, subplot=True, artist=self)
|
||||
|
||||
def __reduce__(self):
|
||||
# get the first axes class which does not inherit from a subplotbase
|
||||
axes_class = next(
|
||||
c for c in type(self).__mro__
|
||||
if issubclass(c, Axes) and not issubclass(c, SubplotBase))
|
||||
return (_picklable_subplot_class_constructor,
|
||||
(axes_class,),
|
||||
self.__getstate__())
|
||||
|
||||
def get_geometry(self):
|
||||
"""Get the subplot geometry, e.g., (2, 2, 3)."""
|
||||
rows, cols, num1, num2 = self.get_subplotspec().get_geometry()
|
||||
return rows, cols, num1 + 1 # for compatibility
|
||||
|
||||
# COVERAGE NOTE: Never used internally or from examples
|
||||
def change_geometry(self, numrows, numcols, num):
|
||||
"""Change subplot geometry, e.g., from (1, 1, 1) to (2, 2, 3)."""
|
||||
self._subplotspec = GridSpec(numrows, numcols,
|
||||
figure=self.figure)[num - 1]
|
||||
self.update_params()
|
||||
self.set_position(self.figbox)
|
||||
|
||||
def get_subplotspec(self):
|
||||
"""Return the `.SubplotSpec` instance associated with the subplot."""
|
||||
return self._subplotspec
|
||||
|
||||
def set_subplotspec(self, subplotspec):
|
||||
"""Set the `.SubplotSpec`. instance associated with the subplot."""
|
||||
self._subplotspec = subplotspec
|
||||
|
||||
def get_gridspec(self):
|
||||
"""Return the `.GridSpec` instance associated with the subplot."""
|
||||
return self._subplotspec.get_gridspec()
|
||||
|
||||
def update_params(self):
|
||||
"""Update the subplot position from ``self.figure.subplotpars``."""
|
||||
self.figbox, _, _, self.numRows, self.numCols = \
|
||||
self.get_subplotspec().get_position(self.figure,
|
||||
return_all=True)
|
||||
|
||||
@cbook.deprecated("3.2", alternative="ax.get_subplotspec().rowspan.start")
|
||||
@property
|
||||
def rowNum(self):
|
||||
return self.get_subplotspec().rowspan.start
|
||||
|
||||
@cbook.deprecated("3.2", alternative="ax.get_subplotspec().colspan.start")
|
||||
@property
|
||||
def colNum(self):
|
||||
return self.get_subplotspec().colspan.start
|
||||
|
||||
def is_first_row(self):
|
||||
return self.get_subplotspec().rowspan.start == 0
|
||||
|
||||
def is_last_row(self):
|
||||
return self.get_subplotspec().rowspan.stop == self.get_gridspec().nrows
|
||||
|
||||
def is_first_col(self):
|
||||
return self.get_subplotspec().colspan.start == 0
|
||||
|
||||
def is_last_col(self):
|
||||
return self.get_subplotspec().colspan.stop == self.get_gridspec().ncols
|
||||
|
||||
def label_outer(self):
|
||||
"""
|
||||
Only show "outer" labels and tick labels.
|
||||
|
||||
x-labels are only kept for subplots on the last row; y-labels only for
|
||||
subplots on the first column.
|
||||
"""
|
||||
lastrow = self.is_last_row()
|
||||
firstcol = self.is_first_col()
|
||||
if not lastrow:
|
||||
for label in self.get_xticklabels(which="both"):
|
||||
label.set_visible(False)
|
||||
self.get_xaxis().get_offset_text().set_visible(False)
|
||||
self.set_xlabel("")
|
||||
if not firstcol:
|
||||
for label in self.get_yticklabels(which="both"):
|
||||
label.set_visible(False)
|
||||
self.get_yaxis().get_offset_text().set_visible(False)
|
||||
self.set_ylabel("")
|
||||
|
||||
def _make_twin_axes(self, *args, **kwargs):
|
||||
"""Make a twinx axes of self. This is used for twinx and twiny."""
|
||||
if 'sharex' in kwargs and 'sharey' in kwargs:
|
||||
# The following line is added in v2.2 to avoid breaking Seaborn,
|
||||
# which currently uses this internal API.
|
||||
if kwargs["sharex"] is not self and kwargs["sharey"] is not self:
|
||||
raise ValueError("Twinned Axes may share only one axis")
|
||||
# The dance here with label is to force add_subplot() to create a new
|
||||
# Axes (by passing in a label never seen before). Note that this does
|
||||
# not affect plot reactivation by subplot() as twin axes can never be
|
||||
# reactivated by subplot().
|
||||
sentinel = str(uuid.uuid4())
|
||||
real_label = kwargs.pop("label", sentinel)
|
||||
twin = self.figure.add_subplot(
|
||||
self.get_subplotspec(), *args, label=sentinel, **kwargs)
|
||||
if real_label is not sentinel:
|
||||
twin.set_label(real_label)
|
||||
self.set_adjustable('datalim')
|
||||
twin.set_adjustable('datalim')
|
||||
if self._layoutbox is not None and twin._layoutbox is not None:
|
||||
# make the layout boxes be explicitly the same
|
||||
twin._layoutbox.constrain_same(self._layoutbox)
|
||||
twin._poslayoutbox.constrain_same(self._poslayoutbox)
|
||||
self._twinned_axes.join(self, twin)
|
||||
return twin
|
||||
|
||||
def __repr__(self):
|
||||
fields = []
|
||||
if self.get_label():
|
||||
fields += [f"label={self.get_label()!r}"]
|
||||
titles = []
|
||||
for k in ["left", "center", "right"]:
|
||||
title = self.get_title(loc=k)
|
||||
if title:
|
||||
titles.append(f"{k!r}:{title!r}")
|
||||
if titles:
|
||||
fields += ["title={" + ",".join(titles) + "}"]
|
||||
if self.get_xlabel():
|
||||
fields += [f"xlabel={self.get_xlabel()!r}"]
|
||||
if self.get_ylabel():
|
||||
fields += [f"ylabel={self.get_ylabel()!r}"]
|
||||
return f"<{self.__class__.__name__}:" + ", ".join(fields) + ">"
|
||||
|
||||
|
||||
# this here to support cartopy which was using a private part of the
|
||||
# API to register their Axes subclasses.
|
||||
|
||||
# In 3.1 this should be changed to a dict subclass that warns on use
|
||||
# In 3.3 to a dict subclass that raises a useful exception on use
|
||||
# In 3.4 should be removed
|
||||
|
||||
# The slow timeline is to give cartopy enough time to get several
|
||||
# release out before we break them.
|
||||
_subplot_classes = {}
|
||||
|
||||
|
||||
@functools.lru_cache(None)
|
||||
def subplot_class_factory(axes_class=None):
|
||||
"""
|
||||
Make a new class that inherits from `.SubplotBase` and the
|
||||
given axes_class (which is assumed to be a subclass of `.axes.Axes`).
|
||||
This is perhaps a little bit roundabout to make a new class on
|
||||
the fly like this, but it means that a new Subplot class does
|
||||
not have to be created for every type of Axes.
|
||||
"""
|
||||
if axes_class is None:
|
||||
cbook.warn_deprecated(
|
||||
"3.3", message="Support for passing None to subplot_class_factory "
|
||||
"is deprecated since %(since)s; explicitly pass the default Axes "
|
||||
"class instead. This will become an error %(removal)s.")
|
||||
axes_class = Axes
|
||||
try:
|
||||
# Avoid creating two different instances of GeoAxesSubplot...
|
||||
# Only a temporary backcompat fix. This should be removed in
|
||||
# 3.4
|
||||
return next(cls for cls in SubplotBase.__subclasses__()
|
||||
if cls.__bases__ == (SubplotBase, axes_class))
|
||||
except StopIteration:
|
||||
return type("%sSubplot" % axes_class.__name__,
|
||||
(SubplotBase, axes_class),
|
||||
{'_axes_class': axes_class})
|
||||
|
||||
|
||||
Subplot = subplot_class_factory(Axes) # Provided for backward compatibility.
|
||||
|
||||
|
||||
def _picklable_subplot_class_constructor(axes_class):
|
||||
"""
|
||||
Stub factory that returns an empty instance of the appropriate subplot
|
||||
class when called with an axes class. This is purely to allow pickling of
|
||||
Axes and Subplots.
|
||||
"""
|
||||
subplot_class = subplot_class_factory(axes_class)
|
||||
return subplot_class.__new__(subplot_class)
|
||||
|
||||
|
||||
docstring.interpd.update(Axes=martist.kwdoc(Axes))
|
||||
docstring.dedent_interpd(Axes.__init__)
|
||||
|
||||
docstring.interpd.update(Subplot=martist.kwdoc(Axes))
|
2534
venv/Lib/site-packages/matplotlib/axis.py
Normal file
2534
venv/Lib/site-packages/matplotlib/axis.py
Normal file
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue