@@ -50,6 +50,9 @@ def _plot(self, split_gen, scales, orient):
50
50
if self ._sort :
51
51
data = data .sort_values (orient )
52
52
53
+ artist_kws = self .artist_kws .copy ()
54
+ self ._handle_capstyle (artist_kws , vals )
55
+
53
56
line = mpl .lines .Line2D (
54
57
data ["x" ].to_numpy (),
55
58
data ["y" ].to_numpy (),
@@ -61,7 +64,7 @@ def _plot(self, split_gen, scales, orient):
61
64
markerfacecolor = vals ["fillcolor" ],
62
65
markeredgecolor = vals ["edgecolor" ],
63
66
markeredgewidth = vals ["edgewidth" ],
64
- ** self . artist_kws ,
67
+ ** artist_kws ,
65
68
)
66
69
ax .add_line (line )
67
70
@@ -77,6 +80,9 @@ def _legend_artist(self, variables, value, scales):
77
80
if Version (mpl .__version__ ) < Version ("3.3.0" ):
78
81
vals ["marker" ] = vals ["marker" ]._marker
79
82
83
+ artist_kws = self .artist_kws .copy ()
84
+ self ._handle_capstyle (artist_kws , vals )
85
+
80
86
return mpl .lines .Line2D (
81
87
[], [],
82
88
color = vals ["color" ],
@@ -87,9 +93,17 @@ def _legend_artist(self, variables, value, scales):
87
93
markerfacecolor = vals ["fillcolor" ],
88
94
markeredgecolor = vals ["edgecolor" ],
89
95
markeredgewidth = vals ["edgewidth" ],
90
- ** self . artist_kws ,
96
+ ** artist_kws ,
91
97
)
92
98
99
+ def _handle_capstyle (self , kws , vals ):
100
+
101
+ # Work around for this matplotlib issue:
102
+ # https://github.com/matplotlib/matplotlib/issues/23437
103
+ if vals ["linestyle" ][1 ] is None :
104
+ capstyle = kws .get ("solid_capstyle" , mpl .rcParams ["lines.solid_capstyle" ])
105
+ kws ["dash_capstyle" ] = capstyle
106
+
93
107
94
108
@dataclass
95
109
class Line (Path ):
@@ -111,7 +125,15 @@ class Paths(Mark):
111
125
112
126
_sort : ClassVar [bool ] = False
113
127
114
- def _plot (self , split_gen , scales , orient ):
128
+ def __post_init__ (self ):
129
+
130
+ # LineCollection artists have a capstyle property but don't source its value
131
+ # from the rc, so we do that manually here. Unfortunately, because we add
132
+ # only one LineCollection, we have the use the same capstyle for all lines
133
+ # even when they are dashed. It's a slight inconsistency, but looks fine IMO.
134
+ self .artist_kws .setdefault ("capstyle" , mpl .rcParams ["lines.solid_capstyle" ])
135
+
136
+ def _setup_lines (self , split_gen , scales , orient ):
115
137
116
138
line_data = {}
117
139
@@ -131,36 +153,42 @@ def _plot(self, split_gen, scales, orient):
131
153
if self ._sort :
132
154
data = data .sort_values (orient )
133
155
134
- # TODO comment about block consolidation
156
+ # Column stack to avoid block consolidation
135
157
xy = np .column_stack ([data ["x" ], data ["y" ]])
136
158
line_data [ax ]["segments" ].append (xy )
137
159
line_data [ax ]["colors" ].append (vals ["color" ])
138
160
line_data [ax ]["linewidths" ].append (vals ["linewidth" ])
139
161
line_data [ax ]["linestyles" ].append (vals ["linestyle" ])
140
162
163
+ return line_data
164
+
165
+ def _plot (self , split_gen , scales , orient ):
166
+
167
+ line_data = self ._setup_lines (split_gen , scales , orient )
168
+
141
169
for ax , ax_data in line_data .items ():
142
- lines = mpl .collections .LineCollection (
143
- ** ax_data ,
144
- ** self .artist_kws ,
145
- )
146
- ax .add_collection (lines , autolim = False )
170
+ lines = mpl .collections .LineCollection (** ax_data , ** self .artist_kws )
171
+ # Handle datalim update manually
147
172
# https://github.com/matplotlib/matplotlib/issues/23129
148
- # TODO get paths from lines object?
173
+ ax . add_collection ( lines , autolim = False )
149
174
xy = np .concatenate (ax_data ["segments" ])
150
- ax .dataLim .update_from_data_xy (
151
- xy , ax .ignore_existing_data_limits , updatex = True , updatey = True
152
- )
175
+ ax .update_datalim (xy )
153
176
154
177
def _legend_artist (self , variables , value , scales ):
155
178
156
179
key = resolve_properties (self , {v : value for v in variables }, scales )
157
180
181
+ artist_kws = self .artist_kws .copy ()
182
+ capstyle = artist_kws .pop ("capstyle" )
183
+ artist_kws ["solid_capstyle" ] = capstyle
184
+ artist_kws ["dash_capstyle" ] = capstyle
185
+
158
186
return mpl .lines .Line2D (
159
187
[], [],
160
188
color = key ["color" ],
161
189
linewidth = key ["linewidth" ],
162
190
linestyle = key ["linestyle" ],
163
- ** self . artist_kws ,
191
+ ** artist_kws ,
164
192
)
165
193
166
194
@@ -170,3 +198,41 @@ class Lines(Paths):
170
198
A faster but less-flexible mark for drawing many lines.
171
199
"""
172
200
_sort : ClassVar [bool ] = True
201
+
202
+
203
+ @dataclass
204
+ class Interval (Paths ):
205
+ """
206
+ An oriented line mark drawn between min/max values.
207
+ """
208
+ def _setup_lines (self , split_gen , scales , orient ):
209
+
210
+ line_data = {}
211
+
212
+ other = {"x" : "y" , "y" : "x" }[orient ]
213
+
214
+ for keys , data , ax in split_gen (keep_na = not self ._sort ):
215
+
216
+ if ax not in line_data :
217
+ line_data [ax ] = {
218
+ "segments" : [],
219
+ "colors" : [],
220
+ "linewidths" : [],
221
+ "linestyles" : [],
222
+ }
223
+
224
+ vals = resolve_properties (self , keys , scales )
225
+ vals ["color" ] = resolve_color (self , keys , scales = scales )
226
+
227
+ cols = [orient , f"{ other } min" , f"{ other } max" ]
228
+ data = data [cols ].melt (orient , value_name = other )[["x" , "y" ]]
229
+ segments = [d .to_numpy () for _ , d in data .groupby (orient )]
230
+
231
+ line_data [ax ]["segments" ].extend (segments )
232
+
233
+ n = len (segments )
234
+ line_data [ax ]["colors" ].extend ([vals ["color" ]] * n )
235
+ line_data [ax ]["linewidths" ].extend ([vals ["linewidth" ]] * n )
236
+ line_data [ax ]["linestyles" ].extend ([vals ["linestyle" ]] * n )
237
+
238
+ return line_data
0 commit comments