Skip to content

Commit a121275

Browse files
Merge pull request #3143 from fdion/matplotlylib_fixes
Matplotlylib fixes
2 parents 34d61d0 + 9be781c commit a121275

File tree

5 files changed

+144
-29
lines changed

5 files changed

+144
-29
lines changed

Diff for: packages/python/plotly/plotly/matplotlylib/mplexporter/tests/test_basic.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -167,17 +167,19 @@ def test_multiaxes():
167167
def test_image():
168168
# Test fails for matplotlib 1.5+ because the size of the image
169169
# generated by matplotlib has changed.
170-
if LooseVersion(matplotlib.__version__) >= LooseVersion('1.5.0'):
171-
pytest.skip("Test fails for matplotlib version > 1.5.0")
170+
if LooseVersion(matplotlib.__version__) == LooseVersion('3.4.1'):
171+
image_size = 432
172+
else:
173+
pytest.skip("Test fails for older matplotlib")
172174
np.random.seed(0) # image size depends on the seed
173175
fig, ax = plt.subplots(figsize=(2, 2))
174176
ax.imshow(np.random.random((10, 10)),
175177
cmap=plt.cm.jet, interpolation='nearest')
176178
_assert_output_equal(fake_renderer_output(fig, FakeRenderer),
177-
"""
179+
f"""
178180
opening figure
179181
opening axes
180-
draw image of size 1240
182+
draw image of size {image_size}
181183
closing axes
182184
closing figure
183185
""")
@@ -204,6 +206,8 @@ def test_legend_dots():
204206
ax.plot([1, 2, 3], label='label')
205207
ax.plot([2, 2, 2], 'o', label='dots')
206208
ax.legend().set_visible(True)
209+
# legend draws 1 line and 1 marker
210+
# path around legend now has 13 vertices??
207211
_assert_output_equal(fake_renderer_output(fig, FullFakeRenderer),
208212
"""
209213
opening figure
@@ -213,9 +217,9 @@ def test_legend_dots():
213217
opening legend
214218
draw line with 2 points
215219
draw text 'label' None
216-
draw 2 markers
220+
draw 1 markers
217221
draw text 'dots' None
218-
draw path with 4 vertices
222+
draw path with 13 vertices
219223
closing legend
220224
closing axes
221225
closing figure

Diff for: packages/python/plotly/plotly/matplotlylib/mplexporter/tests/test_utils.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ def test_path_data():
1313

1414
def test_linestyle():
1515
linestyles = {'solid': 'none', '-': 'none',
16-
'dashed': '6,6', '--': '6,6',
17-
'dotted': '2,2', ':': '2,2',
18-
'dashdot': '4,4,2,4', '-.': '4,4,2,4',
16+
'dashed': '5.550000000000001,2.4000000000000004',
17+
'--': '5.550000000000001,2.4000000000000004',
18+
'dotted': '1.5,2.4749999999999996', ':': '1.5,2.4749999999999996',
19+
'dashdot': '9.600000000000001,2.4000000000000004,1.5,2.4000000000000004',
20+
'-.': '9.600000000000001,2.4000000000000004,1.5,2.4000000000000004',
1921
'': None, 'None': None}
2022

2123
for ls, result in linestyles.items():

Diff for: packages/python/plotly/plotly/matplotlylib/mplexporter/utils.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ def get_axis_properties(axis):
217217
props['tickformat'] = ""
218218
elif isinstance(formatter, ticker.FixedFormatter):
219219
props['tickformat'] = list(formatter.seq)
220+
elif isinstance(formatter, ticker.FuncFormatter):
221+
props['tickformat'] = list(formatter.func.args[0].values())
220222
elif not any(label.get_visible() for label in axis.get_ticklabels()):
221223
props['tickformat'] = ""
222224
else:
@@ -243,7 +245,7 @@ def get_axis_properties(axis):
243245

244246
def get_grid_style(axis):
245247
gridlines = axis.get_gridlines()
246-
if axis._gridOnMajor and len(gridlines) > 0:
248+
if axis._major_tick_kw['gridOn'] and len(gridlines) > 0:
247249
color = export_color(gridlines[0].get_color())
248250
alpha = gridlines[0].get_alpha()
249251
dasharray = get_dasharray(gridlines[0])

Diff for: packages/python/plotly/plotly/matplotlylib/mpltools.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,17 @@ def get_spine_visible(ax, spine_key):
365365
"""Return some spine parameters for the spine, `spine_key`."""
366366
spine = ax.spines[spine_key]
367367
ax_frame_on = ax.get_frame_on()
368-
spine_frame_like = spine.is_frame_like()
368+
position = spine._position or ("outward", 0.0)
369+
if isinstance(position, str):
370+
if position == "center":
371+
position = ("axes", 0.5)
372+
elif position == "zero":
373+
position = ("data", 0)
374+
position_type, amount = position
375+
if position_type == "outward" and amount == 0:
376+
spine_frame_like = True
377+
else:
378+
spine_frame_like = False
369379
if not spine.get_visible():
370380
return False
371381
elif not spine._edgecolor[-1]: # user's may have set edgecolor alpha==0

Diff for: packages/python/plotly/plotly/matplotlylib/renderer.py

+115-18
Original file line numberDiff line numberDiff line change
@@ -312,10 +312,84 @@ def draw_bar(self, coll):
312312
"assuming data redundancy, not plotting."
313313
)
314314

315+
def draw_legend_shapes(self, mode, shape, **props):
316+
"""Create a shape that matches lines or markers in legends.
317+
318+
Main issue is that path for circles do not render, so we have to use 'circle'
319+
instead of 'path'.
320+
"""
321+
for single_mode in mode.split("+"):
322+
x = props["data"][0][0]
323+
y = props["data"][0][1]
324+
if single_mode == "markers" and props.get("markerstyle"):
325+
size = shape.pop("size", 6)
326+
symbol = shape.pop("symbol")
327+
# aligning to "center"
328+
x0 = 0
329+
y0 = 0
330+
x1 = size
331+
y1 = size
332+
markerpath = props["markerstyle"].get("markerpath")
333+
if markerpath is None and symbol != "circle":
334+
self.msg += "not sure how to handle this marker without a valid path\n"
335+
return
336+
# marker path to SVG path conversion
337+
path = ' '.join([f"{a} {t[0]},{t[1]}" for a, t in zip(markerpath[1], markerpath[0])])
338+
339+
if symbol == "circle":
340+
# symbols like . and o in matplotlib, use circle
341+
# plotly also maps many other markers to circle, such as 1,8 and p
342+
path = None
343+
shape_type = "circle"
344+
x0 = -size / 2
345+
y0 = size / 2
346+
x1 = size / 2
347+
y1 = size + size / 2
348+
else:
349+
# triangles, star etc
350+
shape_type = "path"
351+
legend_shape = go.layout.Shape(
352+
type=shape_type,
353+
xref="paper",
354+
yref="paper",
355+
x0=x0,
356+
y0=y0,
357+
x1=x1,
358+
y1=y1,
359+
xsizemode="pixel",
360+
ysizemode="pixel",
361+
xanchor=x,
362+
yanchor=y,
363+
path=path,
364+
**shape
365+
)
366+
367+
elif single_mode == "lines":
368+
mode = "line"
369+
x1 = props["data"][1][0]
370+
y1 = props["data"][1][1]
371+
372+
legend_shape = go.layout.Shape(
373+
type=mode,
374+
xref="paper",
375+
yref="paper",
376+
x0=x,
377+
y0=y+0.02,
378+
x1=x1,
379+
y1=y1+0.02,
380+
**shape
381+
)
382+
else:
383+
self.msg += "not sure how to handle this element\n"
384+
return
385+
self.plotly_fig.add_shape(legend_shape)
386+
self.msg += " Heck yeah, I drew that shape\n"
387+
315388
def draw_marked_line(self, **props):
316389
"""Create a data dict for a line obj.
317390
318-
This will draw 'lines', 'markers', or 'lines+markers'.
391+
This will draw 'lines', 'markers', or 'lines+markers'. For legend elements,
392+
this will use layout.shapes, so they can be positioned with paper refs.
319393
320394
props.keys() -- [
321395
'coordinates', ('data', 'axes', 'figure', or 'display')
@@ -346,7 +420,7 @@ def draw_marked_line(self, **props):
346420
347421
"""
348422
self.msg += " Attempting to draw a line "
349-
line, marker = {}, {}
423+
line, marker, shape = {}, {}, {}
350424
if props["linestyle"] and props["markerstyle"]:
351425
self.msg += "... with both lines+markers\n"
352426
mode = "lines+markers"
@@ -361,23 +435,43 @@ def draw_marked_line(self, **props):
361435
props["linestyle"]["color"], props["linestyle"]["alpha"]
362436
)
363437

364-
# print(mpltools.convert_dash(props['linestyle']['dasharray']))
365-
line = go.scatter.Line(
366-
color=color,
367-
width=props["linestyle"]["linewidth"],
368-
dash=mpltools.convert_dash(props["linestyle"]["dasharray"]),
369-
)
438+
if props["coordinates"] == "data":
439+
line = go.scatter.Line(
440+
color=color,
441+
width=props["linestyle"]["linewidth"],
442+
dash=mpltools.convert_dash(props["linestyle"]["dasharray"]),
443+
)
444+
else:
445+
shape=dict(
446+
line = dict(
447+
color=color,
448+
width=props["linestyle"]["linewidth"],
449+
dash=mpltools.convert_dash(props["linestyle"]["dasharray"])
450+
)
451+
)
370452
if props["markerstyle"]:
371-
marker = go.scatter.Marker(
372-
opacity=props["markerstyle"]["alpha"],
373-
color=props["markerstyle"]["facecolor"],
374-
symbol=mpltools.convert_symbol(props["markerstyle"]["marker"]),
375-
size=props["markerstyle"]["markersize"],
376-
line=dict(
377-
color=props["markerstyle"]["edgecolor"],
378-
width=props["markerstyle"]["edgewidth"],
379-
),
380-
)
453+
if props["coordinates"] == "data":
454+
marker = go.scatter.Marker(
455+
opacity=props["markerstyle"]["alpha"],
456+
color=props["markerstyle"]["facecolor"],
457+
symbol=mpltools.convert_symbol(props["markerstyle"]["marker"]),
458+
size=props["markerstyle"]["markersize"],
459+
line=dict(
460+
color=props["markerstyle"]["edgecolor"],
461+
width=props["markerstyle"]["edgewidth"],
462+
),
463+
)
464+
else:
465+
shape = dict(
466+
opacity=props["markerstyle"]["alpha"],
467+
fillcolor=props["markerstyle"]["facecolor"],
468+
symbol=mpltools.convert_symbol(props["markerstyle"]["marker"]),
469+
size=props["markerstyle"]["markersize"],
470+
line=dict(
471+
color=props["markerstyle"]["edgecolor"],
472+
width=props["markerstyle"]["edgewidth"],
473+
),
474+
)
381475
if props["coordinates"] == "data":
382476
marked_line = go.Scatter(
383477
mode=mode,
@@ -404,6 +498,9 @@ def draw_marked_line(self, **props):
404498
)
405499
self.plotly_fig.add_trace(marked_line),
406500
self.msg += " Heck yeah, I drew that line\n"
501+
elif props["coordinates"] == "axes":
502+
# dealing with legend graphical elements
503+
self.draw_legend_shapes(mode=mode,shape=shape, **props)
407504
else:
408505
self.msg += " Line didn't have 'data' coordinates, " "not drawing\n"
409506
warnings.warn(

0 commit comments

Comments
 (0)