Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commite97547c

Browse files
RobertAugustynowiczomarchehab98henryhu123DennisTismenkoJinming-Zhang
committed
Implement Axes.label_lines
Co-authored-by: Robert Augustynowicz <robert.augustynowicz@mail.utoronto.ca>Co-authored-by: Omar Chehab <omarchehab98@gmail.com>Co-authored-by: Jinyang Hu <jinyang.hu@mail.utoronto.ca>Co-authored-by: Dennis Tismenko <dtismenkodeveloper@gmail.com>Co-authored-by: Jinming Zhang <jinming.zhang@mail.utoronto.ca>Closes:#12939
1 parentd1f07d3 commite97547c

File tree

6 files changed

+264
-0
lines changed

6 files changed

+264
-0
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""
2+
====================================
3+
Line labels using pre-defined labels
4+
====================================
5+
6+
Defining line labels with plots.
7+
"""
8+
9+
10+
importnumpyasnp
11+
importmatplotlib.pyplotasplt
12+
13+
# Make some fake data.
14+
a=b=np.arange(0,3,.02)
15+
c=np.exp(a)
16+
d=c[::-1]
17+
18+
# Create plots with pre-defined labels.
19+
fig,ax=plt.subplots()
20+
ax.spines['top'].set_visible(False)
21+
ax.spines['right'].set_visible(False)
22+
ax.plot(a,c,'k--',label='Model length')
23+
ax.plot(a,d,'k:',label='Data length')
24+
ax.plot(a,c+d,'k',label='Total message length')
25+
26+
ax.label_lines()
27+
28+
plt.show()
29+
30+
#############################################################################
31+
#
32+
# ------------
33+
#
34+
# References
35+
# """"""""""
36+
#
37+
# The use of the following functions, methods, classes and modules is shown
38+
# in this example:
39+
40+
importmatplotlib
41+
matplotlib.axes.Axes.plot
42+
matplotlib.pyplot.plot
43+
matplotlib.axes.Axes.label_lines
44+
matplotlib.pyplot.label_lines

‎lib/matplotlib/axes/_axes.py

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,212 @@ def text(self, x, y, s, fontdict=None, **kwargs):
753753
self._add_text(t)
754754
returnt
755755

756+
deflabel_lines(self,*args,**kwargs):
757+
"""
758+
Place labels at the end of lines on the chart.
759+
760+
Call signatures::
761+
762+
label_lines()
763+
label_lines(labels)
764+
label_lines(handles, labels)
765+
766+
The call signatures correspond to three different ways of how to use
767+
this method.
768+
769+
The simplest way to use line_labels is without parameters. After the
770+
lines are created with their labels, the user can call this function
771+
which automatically applies the stored labels by the corresponding
772+
lines. This would be most effectively used after the complete creation
773+
of the line chart.
774+
775+
The second way to call this method is by specifying the first parameter
776+
which is labels. In doing so you would be able to name the lines if
777+
they lack names, or rename them as needed. This process occurs in the
778+
order in which the lines were added to the chart.
779+
780+
The Final way to use this method is specifying both the handles and the
781+
labels. In doing so you can specify names for select lines leaving the
782+
rest blank. This would be useful when users would want to specify
783+
select pieces of data to monitor when data clumps occur.
784+
785+
Parameters
786+
----------
787+
handles : sequence of `.Artist`, optional
788+
A list of Artists (lines) to be added to the line labels.
789+
Use this together with *labels*, if you need full control on what
790+
is shown in the line labels and the automatic mechanism described
791+
above is not sufficient.
792+
793+
The length of handles and labels should be the same in this
794+
case. If they are not, they are truncated to the smaller length.
795+
796+
labels : list of str, optional
797+
A list of labels to show next to the artists.
798+
Use this together with *handles*, if you need full control on what
799+
is shown in the line labels and the automatic mechanism described
800+
above is not sufficient.
801+
802+
Returns
803+
-------
804+
None
805+
806+
Notes
807+
-----
808+
Only line handles are supported by this method.
809+
810+
Examples
811+
--------
812+
.. plot:: gallery/text_labels_and_annotations/label_lines.py
813+
"""
814+
handles,labels,extra_args,kwargs=mlegend._parse_legend_args(
815+
[self],
816+
*args,
817+
**kwargs)
818+
iflen(extra_args):
819+
raiseTypeError(
820+
'label_lines only accepts two nonkeyword arguments')
821+
822+
self._line_labels= []
823+
824+
defget_last_data_point(handle):
825+
last_x=handle.get_xdata()[-1]
826+
last_y=handle.get_ydata()[-1]
827+
returnlast_x,last_y
828+
829+
# Get last data point for each handle
830+
xys=np.array([get_last_data_point(x)forxinhandles])
831+
832+
# Find the largest last x and y value
833+
data_maxx,data_maxy=np.max(xys,axis=0)
834+
# Find the smallest last y value
835+
data_miny=np.min(xys,axis=0)[1]
836+
837+
# Get the figure width and height in pixels
838+
fig_dpi_transform=self.figure.dpi_scale_trans.inverted()
839+
fig_bbox=self.get_window_extent().transformed(fig_dpi_transform)
840+
fig_width_px=fig_bbox.width*self.figure.dpi
841+
fig_height_px=fig_bbox.height*self.figure.dpi
842+
843+
# Get the figure width and height in points
844+
fig_minx,fig_maxx=self.get_xbound()
845+
fig_miny,fig_maxy=self.get_ybound()
846+
fig_width_pt=abs(fig_maxx-fig_minx)
847+
fig_height_pt=abs(fig_maxy-fig_miny)
848+
849+
# Space to the left of the text converted from pixels
850+
margin_left=8* (fig_width_pt/fig_width_px)
851+
# Space to the top and bottom of the text converted from pixels
852+
margin_vertical=2* (fig_height_pt/fig_height_px)
853+
854+
text_fontsize=10
855+
# Height of the text converted from pixels
856+
text_height=text_fontsize* (fig_height_pt/fig_height_px)
857+
858+
# Height of each bucket
859+
bucket_height=text_height+2*margin_vertical
860+
# Total number of buckets
861+
buckets_total=1+int((fig_maxy-fig_miny-text_height)/
862+
bucket_height)
863+
# Bit array to track which buckets are used
864+
buckets_map=0
865+
866+
defget_bucket_index(y):
867+
returnint((y-fig_miny)/bucket_height)
868+
869+
# How many text SHOULD be in this bucket
870+
bucket_densities= [0]*buckets_total
871+
forxyinxys:
872+
data_x,data_y=xy
873+
ideal_bucket=get_bucket_index(data_y)
874+
ifideal_bucket>=0andideal_bucket<buckets_total:
875+
bucket_densities[ideal_bucket]+=1
876+
877+
defin_viewport(args):
878+
xy=args[2]
879+
x,y=xy
880+
returnfig_minx<x<fig_maxxandfig_miny<y<fig_maxy
881+
882+
defby_y(args):
883+
xy=args[2]
884+
x,y=xy
885+
returny
886+
887+
bucket_offset=None
888+
prev_ideal_bucket=-1
889+
# Iterate over all the handles, labels, and xy data sorted by y asc
890+
forhandle,label,xyinsorted(filter(in_viewport,
891+
zip(handles,labels,xys)),
892+
key=by_y):
893+
data_x,data_y=xy
894+
895+
ideal_bucket=get_bucket_index(data_y)
896+
ifideal_bucket!=prev_ideal_bucket:
897+
# If a bucket is dense, then we want to center the text around
898+
# the bucket. SSo, find out how many empty buckets there are
899+
# below the current bucket
900+
bucket_density=bucket_densities[ideal_bucket]
901+
ideal_offset=bucket_density//2
902+
empty_buckets_below=0
903+
foriinrange(ideal_bucket+1,
904+
ideal_bucket-ideal_offset+1,
905+
-1):
906+
# If reached bottom or bucket is not empty
907+
ifi==0orbuckets_map& (1<<i)==1:
908+
break
909+
empty_buckets_below+=1
910+
bucket_offset=-min(ideal_offset,empty_buckets_below)
911+
prev_ideal_bucket=ideal_bucket
912+
913+
bucket=ideal_bucket+bucket_offset
914+
text_x,text_y=None,None
915+
916+
# Find the nearest empty bucket
917+
forindexinrange(buckets_total):
918+
# 0 <= bucket_index <= buckets_total
919+
bucket_index=max(0,min(bucket+index,buckets_total))
920+
bucket_mask=1<<bucket_index
921+
# If this bucket is empty
922+
ifbuckets_map&bucket_mask==0:
923+
# Mark this bucket as used
924+
buckets_map|=bucket_mask
925+
text_x=data_maxx+margin_left
926+
text_y=bucket_index*bucket_height+fig_miny
927+
break
928+
929+
# If a bucket was found for the text
930+
iftext_xisnotNoneandtext_yisnotNone:
931+
text_color=handle.get_color()
932+
line_label=self.annotate(label, (data_maxx,data_y),
933+
xytext=(text_x,text_y),
934+
fontsize=text_fontsize,
935+
color=text_color,
936+
annotation_clip=True)
937+
self._line_labels.append(line_label)
938+
939+
# A bucket is not necessarily aligned with the data point
940+
# If space allows, center the text around the data point
941+
# TODO(omarchehab98): group fuzzing
942+
# for line_label in self._line_labels:
943+
# datax, datay = line_label.xy
944+
# targety = datay - text_height / 2
945+
# textx, texty = line_label.get_position()
946+
# direction = 1 if targety > texty else -1
947+
# target_bucket = get_bucket_index(targety) + direction
948+
# if (get_bucket_index(targety) == get_bucket_index(texty) and
949+
# (target_bucket == -1 or
950+
# buckets_map & (1 << target_bucket) == 0)):
951+
# line_label.set_position((textx, targety))
952+
953+
defhas_label_lines(self):
954+
returnself._line_labelsisnotNone
955+
956+
defrefresh_label_lines(self,*args,**kwargs):
957+
forlabelinself._line_labels:
958+
label.remove()
959+
self._line_labels=None
960+
self.label_lines(*args,**kwargs)
961+
756962
@cbook._rename_parameter("3.3","s","text")
757963
@docstring.dedent_interpd
758964
defannotate(self,text,xy,*args,**kwargs):

‎lib/matplotlib/axes/_base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,8 @@ def __init__(self, fig, rect,
514514

515515
self._layoutbox=None
516516
self._poslayoutbox=None
517+
518+
self._line_labels=None
517519

518520
def__getstate__(self):
519521
# The renderer should be re-created by the figure, and then cached at

‎lib/matplotlib/backend_bases.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3058,6 +3058,11 @@ def release_zoom(self, event):
30583058
defdraw(self):
30593059
"""Redraw the canvases, update the locators."""
30603060
forainself.canvas.figure.get_axes():
3061+
# TODO(henryhu123): Is this the right place do recompute the line
3062+
# labels?
3063+
ifa.has_label_lines():
3064+
a.refresh_label_lines()
3065+
30613066
xaxis=getattr(a,'xaxis',None)
30623067
yaxis=getattr(a,'yaxis',None)
30633068
locators= []

‎lib/matplotlib/pyplot.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2559,6 +2559,12 @@ def imshow(
25592559
return__ret
25602560

25612561

2562+
# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
2563+
@_copy_docstring_and_deprecators(Axes.label_lines)
2564+
deflabel_lines(*args,**kwargs):
2565+
returngca().label_lines(*args,**kwargs)
2566+
2567+
25622568
# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
25632569
@_copy_docstring_and_deprecators(Axes.legend)
25642570
deflegend(*args,**kwargs):

‎tools/boilerplate.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ def boilerplate_gen():
227227
'hist2d',
228228
'hlines',
229229
'imshow',
230+
'label_lines',
230231
'legend',
231232
'locator_params',
232233
'loglog',

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp