Skip to content

Backport qt editor improvements #6717

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions doc/users/whats_new/2016-04_qt_config-AL.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Improvements for the Qt figure options editor
---------------------------------------------

Various usability improvements were implemented for the Qt figure options
editor, among which:
- Line style entries are now sorted without duplicates.
- The colormap and normalization limits can now be set for images.
- Line edits for floating values now display only as many digits as necessary
to avoid precision loss. An important bug was also fixed regarding input
validation using Qt5 and a locale where the decimal separator is ",".
- The axes selector now uses shorter, more user-friendly names for axes, and
does not crash if there are no axes.
- Line and image entries using the default labels ("_lineX", "_imageX") are now
sorted numerically even when there are more than 10 entries.
2 changes: 2 additions & 0 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1732,6 +1732,8 @@ def add_image(self, image):
Returns the image.
"""
self._set_artist_props(image)
if not image.get_label():
image.set_label('_image%d' % len(self.images))
self.images.append(image)
image._remove_method = lambda h: self.images.remove(h)
self.stale = True
Expand Down
198 changes: 131 additions & 67 deletions lib/matplotlib/backends/qt_editor/figureoptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,24 @@
import six

import os.path as osp
import re

import matplotlib
from matplotlib import cm, markers, colors as mcolors
import matplotlib.backends.qt_editor.formlayout as formlayout
from matplotlib.backends.qt_compat import QtGui
from matplotlib import markers
from matplotlib.colors import colorConverter, rgb2hex


def get_icon(name):
import matplotlib
basedir = osp.join(matplotlib.rcParams['datapath'], 'images')
return QtGui.QIcon(osp.join(basedir, name))


LINESTYLES = {'-': 'Solid',
'--': 'Dashed',
'-.': 'DashDot',
':': 'Dotted',
'none': 'None',
'None': 'None',
}

DRAWSTYLES = {'default': 'Default',
Expand All @@ -43,8 +44,6 @@ def figure_edit(axes, parent=None):
"""Edit matplotlib figure options"""
sep = (None, None) # separator

has_curve = len(axes.get_lines()) > 0

# Get / General
xmin, xmax = axes.get_xlim()
ymin, ymax = axes.get_ylim()
Expand All @@ -69,57 +68,115 @@ def figure_edit(axes, parent=None):
xunits = axes.xaxis.get_units()
yunits = axes.yaxis.get_units()

if has_curve:
# Get / Curves
linedict = {}
for line in axes.get_lines():
label = line.get_label()
if label == '_nolegend_':
continue
linedict[label] = line
curves = []
linestyles = list(six.iteritems(LINESTYLES))
drawstyles = list(six.iteritems(DRAWSTYLES))
markers = list(six.iteritems(MARKERS))
curvelabels = sorted(linedict.keys())
for label in curvelabels:
line = linedict[label]
color = rgb2hex(colorConverter.to_rgb(line.get_color()))
ec = rgb2hex(colorConverter.to_rgb(line.get_markeredgecolor()))
fc = rgb2hex(colorConverter.to_rgb(line.get_markerfacecolor()))
curvedata = [('Label', label),
sep,
(None, '<b>Line</b>'),
('Line Style', [line.get_linestyle()] + linestyles),
('Draw Style', [line.get_drawstyle()] + drawstyles),
('Width', line.get_linewidth()),
('Color', color),
sep,
(None, '<b>Marker</b>'),
('Style', [line.get_marker()] + markers),
('Size', line.get_markersize()),
('Facecolor', fc),
('Edgecolor', ec),
]
curves.append([curvedata, label, ""])

# make sure that there is at least one displayed curve
has_curve = bool(curves)
# Sorting for default labels (_lineXXX, _imageXXX).
def cmp_key(label):
match = re.match(r"(_line|_image)(\d+)", label)
if match:
return match.group(1), int(match.group(2))
else:
return label, 0

# Get / Curves
linedict = {}
for line in axes.get_lines():
label = line.get_label()
if label == '_nolegend_':
continue
linedict[label] = line
curves = []

def prepare_data(d, init):
"""Prepare entry for FormLayout.

`d` is a mapping of shorthands to style names (a single style may
have multiple shorthands, in particular the shorthands `None`,
`"None"`, `"none"` and `""` are synonyms); `init` is one shorthand
of the initial style.

This function returns an list suitable for initializing a
FormLayout combobox, namely `[initial_name, (shorthand,
style_name), (shorthand, style_name), ...]`.
"""
# Drop duplicate shorthands from dict (by overwriting them during
# the dict comprehension).
name2short = {name: short for short, name in d.items()}
# Convert back to {shorthand: name}.
short2name = {short: name for name, short in name2short.items()}
# Find the kept shorthand for the style specified by init.
canonical_init = name2short[d[init]]
# Sort by representation and prepend the initial value.
return ([canonical_init] +
sorted(short2name.items(),
key=lambda short_and_name: short_and_name[1]))

curvelabels = sorted(linedict, key=cmp_key)
for label in curvelabels:
line = linedict[label]
color = mcolors.to_hex(
mcolors.to_rgba(line.get_color(), line.get_alpha()),
keep_alpha=True)
ec = mcolors.to_hex(line.get_markeredgecolor(), keep_alpha=True)
fc = mcolors.to_hex(line.get_markerfacecolor(), keep_alpha=True)
curvedata = [
('Label', label),
sep,
(None, '<b>Line</b>'),
('Line style', prepare_data(LINESTYLES, line.get_linestyle())),
('Draw style', prepare_data(DRAWSTYLES, line.get_drawstyle())),
('Width', line.get_linewidth()),
('Color (RGBA)', color),
sep,
(None, '<b>Marker</b>'),
('Style', prepare_data(MARKERS, line.get_marker())),
('Size', line.get_markersize()),
('Face color (RGBA)', fc),
('Edge color (RGBA)', ec)]
curves.append([curvedata, label, ""])
# Is there a curve displayed?
has_curve = bool(curves)

# Get / Images
imagedict = {}
for image in axes.get_images():
label = image.get_label()
if label == '_nolegend_':
continue
imagedict[label] = image
imagelabels = sorted(imagedict, key=cmp_key)
images = []
cmaps = [(cmap, name) for name, cmap in sorted(cm.cmap_d.items())]
for label in imagelabels:
image = imagedict[label]
cmap = image.get_cmap()
if cmap not in cm.cmap_d.values():
cmaps = [(cmap, cmap.name)] + cmaps
low, high = image.get_clim()
imagedata = [
('Label', label),
('Colormap', [cmap.name] + cmaps),
('Min. value', low),
('Max. value', high)]
images.append([imagedata, label, ""])
# Is there an image displayed?
has_image = bool(images)

datalist = [(general, "Axes", "")]
if has_curve:
if curves:
datalist.append((curves, "Curves", ""))
if images:
datalist.append((images, "Images", ""))

def apply_callback(data):
"""This function will be called to apply changes"""
if has_curve:
general, curves = data
else:
general, = data
general = data.pop(0)
curves = data.pop(0) if has_curve else []
images = data.pop(0) if has_image else []
if data:
raise ValueError("Unexpected field")

# Set / General
title, xmin, xmax, xlabel, xscale, ymin, ymax, ylabel, yscale, \
generate_legend = general
(title, xmin, xmax, xlabel, xscale, ymin, ymax, ylabel, yscale,
generate_legend) = general

if axes.get_xscale() != xscale:
axes.set_xscale(xscale)
Expand All @@ -140,26 +197,33 @@ def apply_callback(data):
axes.xaxis._update_axisinfo()
axes.yaxis._update_axisinfo()

if has_curve:
# Set / Curves
for index, curve in enumerate(curves):
line = linedict[curvelabels[index]]
label, linestyle, drawstyle, linewidth, color, \
marker, markersize, markerfacecolor, markeredgecolor \
= curve
line.set_label(label)
line.set_linestyle(linestyle)
line.set_drawstyle(drawstyle)
line.set_linewidth(linewidth)
line.set_color(color)
if marker is not 'none':
line.set_marker(marker)
line.set_markersize(markersize)
line.set_markerfacecolor(markerfacecolor)
line.set_markeredgecolor(markeredgecolor)
# Set / Curves
for index, curve in enumerate(curves):
line = linedict[curvelabels[index]]
(label, linestyle, drawstyle, linewidth, color, marker, markersize,
markerfacecolor, markeredgecolor) = curve
line.set_label(label)
line.set_linestyle(linestyle)
line.set_drawstyle(drawstyle)
line.set_linewidth(linewidth)
rgba = mcolors.to_rgba(color)
line.set_color(rgba[:3])
line.set_alpha(rgba[-1])
if marker is not 'none':
line.set_marker(marker)
line.set_markersize(markersize)
line.set_markerfacecolor(markerfacecolor)
line.set_markeredgecolor(markeredgecolor)

# Set / Images
for index, image_settings in enumerate(images):
image = imagedict[imagelabels[index]]
label, cmap, low, high = image_settings
image.set_label(label)
image.set_cmap(cm.get_cmap(cmap))
image.set_clim(*sorted([low, high]))

# re-generate legend, if checkbox is checked

if generate_legend:
draggable = None
ncol = 1
Expand Down
Loading