Skip to content
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

Improvements to link editor #1141

Merged
merged 2 commits into from
Oct 10, 2016
Merged
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
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ v0.9.0 (unreleased)

* Improve support for spectral cubes. [#1075]

* Allow link functions/helpers to define a category using the ``category=``
argument (which defaults to ``General``), and make it possible to filter
by category in the link editor. [#1141]

* Only show the 'waiting' cursor when glue is doing something. [#1097]

* Make sure that the scatter layer artist style editor is shown when overplotting
Expand Down
31 changes: 12 additions & 19 deletions glue/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,8 +466,10 @@ class LinkFunctionRegistry(Registry):
"""Stores functions to convert between quantities

The members properety is a list of (function, info_string,
output_labels) namedtuples. `info_string` is describes what the
function does. `output_labels` is a list of names for each output.
output_labels) namedtuples. ``info_string`` describes what the
function does. ``output_labels`` is a list of names for each output.
``category`` is a category in which the link funtion will appear (defaults
to 'General').

New link functions can be registered via

Expand All @@ -478,18 +480,13 @@ def degrees2arcsec(degrees):

Link functions are expected to receive and return numpy arrays
"""
item = namedtuple('LinkFunction', 'function info output_labels')
item = namedtuple('LinkFunction', 'function info output_labels category')

def default_members(self):
from glue.core import link_helpers
return list(self.item(l, "", l.output_args)
for l in link_helpers.__LINK_FUNCTIONS__)

def __call__(self, info="", output_labels=None):
def __call__(self, info="", output_labels=None, category='General'):
out = output_labels or []

def adder(func):
self.add(self.item(func, info, out))
self.add(self.item(func, info, out, category))
return func
return adder

Expand All @@ -516,7 +513,8 @@ class LinkHelperRegistry(Registry):
The members property is a list of (object, info_string,
input_labels) tuples. `Object` is the link helper. `info_string`
describes what `object` does. `input_labels` is a list labeling
the inputs.
the inputs. ``category`` is a category in which the link funtion will appear
(defaults to 'General').

Each link helper takes a list of ComponentIDs as inputs, and
returns an iterable object (e.g. list) of ComponentLinks.
Expand All @@ -529,16 +527,11 @@ def new_helper(degree, arcsecond):
return [ComponentLink([degree], arcsecond, using=lambda d: d*3600),
ComponentLink([arcsecond], degree, using=lambda a: a/3600)]
"""
item = namedtuple('LinkHelper', 'helper info input_labels')

def default_members(self):
from glue.core.link_helpers import __LINK_HELPERS__ as helpers
return list(self.item(l, l.info_text, l.input_args)
for l in helpers)
item = namedtuple('LinkHelper', 'helper info input_labels category')

def __call__(self, info, input_labels):
def __call__(self, info, input_labels, category='General'):
def adder(func):
self.add(self.item(func, info, input_labels))
self.add(self.item(func, info, input_labels, category))
return func
return adder

Expand Down
22 changes: 5 additions & 17 deletions glue/core/link_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@
This module provides several classes and LinkCollection classes to
assist in linking data.

The functions in this class (and stored in the __LINK_FUNCTIONS__
list) define common coordinate transformations. They are meant to be
used for the `using` parameter in
:class:`glue.core.component_link.ComponentLink` instances.

The :class:`LinkCollection` class and its sublcasses are factories to create
multiple ComponentLinks easily. They are meant to be passed to
:meth:`~glue.core.data_collection.DataCollection.add_link()`
"""

from __future__ import absolute_import, division, print_function

from glue.config import link_function
from glue.external import six
from glue.core.data import ComponentID
from glue.core.component_link import ComponentLink
Expand All @@ -22,27 +18,19 @@
__all__ = ['LinkCollection', 'LinkSame', 'LinkTwoWay', 'MultiLink',
'LinkAligned']

__LINK_FUNCTIONS__ = []
__LINK_HELPERS__ = []


@link_function("Link conceptually identical components",
output_labels=['y'])
def identity(x):
return x
identity.output_args = ['y']


@link_function("Convert between linear measurements and volume",
output_labels=['volume'])
def lengths_to_volume(width, height, depth):
"""Compute volume from linear measurements of a box"""
# included for demonstration purposes
return width * height * depth


lengths_to_volume.output_args = ['area']

__LINK_FUNCTIONS__.append(identity)
__LINK_FUNCTIONS__.append(lengths_to_volume)


class PartialResult(object):

def __init__(self, func, index, name_prefix=""):
Expand Down
60 changes: 24 additions & 36 deletions glue/dialogs/link_editor/qt/link_equation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,30 @@

import os
from inspect import getargspec
from collections import OrderedDict

from qtpy import QtWidgets
from qtpy import PYSIDE
from glue import core
from glue.config import link_function, link_helper
from glue.utils import nonpartial
from glue.utils.qt import load_ui, messagebox_on_error
from glue.utils.qt import load_ui, messagebox_on_error, update_combobox
from glue.utils.qt.widget_properties import CurrentComboTextProperty, CurrentComboDataProperty

__all__ = ['LinkEquation']


def get_function_name(item):
if hasattr(item, 'display') and item.display is not None:
return item.display
else:
return item.__name__


def function_label(function):
""" Provide a label for a function

:param function: A member from the glue.config.link_function registry
"""
name = function.function.__name__
args = getargspec(function.function)[0]
args = ', '.join(args)
output = function.output_labels
Expand Down Expand Up @@ -114,19 +121,13 @@ class LinkEquation(QtWidgets.QWidget):
widget = LinkEquation()
"""

category = CurrentComboTextProperty('_ui.category')
function = CurrentComboDataProperty('_ui.function')

def __init__(self, parent=None):
super(LinkEquation, self).__init__(parent)
from glue.config import link_function, link_helper

# Set up mapping of function/helper name -> function/helper tuple. For the helpers, we use the 'display' name if available.
def get_name(item):
if hasattr(item, 'display') and item.display is not None:
return item.display
else:
return item.__name__
f = [f for f in link_function.members if len(f.output_labels) == 1]
self._functions = OrderedDict((get_name(l[0]), l) for l in
f + link_helper.members)
self._argument_widgets = []
self.spacer = None
self._output_widget = ArgumentWidget("")
Expand All @@ -140,6 +141,8 @@ def get_name(item):
self.setLayout(l)

self._init_widgets()
self._populate_category_combo()
self.category = 'General'
self._populate_function_combo()
self._connect()
self._setup_editor()
Expand Down Expand Up @@ -189,27 +192,6 @@ def signature(self, inout):
a.component_id = i
self._output_widget.component_id = out

@property
def function(self):
""" The currently-selected function

:rtype: A function or helper tuple
"""
fname = str(self._ui.function.currentText())
func = self._functions[fname]
return func

@function.setter
def function(self, val):
if hasattr(val[0], 'display') and val[0].display is not None:
name = val[0].display
else:
name = val[0].__name__
pos = self._ui.function.findText(name)
if pos < 0:
raise KeyError("No function or helper found %s" % [val])
self._ui.function.setCurrentIndex(pos)

@messagebox_on_error("Failed to create links")
def links(self):
""" Create ComponentLinks from the state of the widget
Expand Down Expand Up @@ -245,6 +227,7 @@ def _connect(self):
signal.connect(nonpartial(self._setup_editor))
signal.connect(nonpartial(self._update_add_enabled))
self._output_widget.editor.textChanged.connect(nonpartial(self._update_add_enabled))
self._ui.category.currentIndexChanged.connect(self._populate_function_combo)

def clear_inputs(self):
for w in self._argument_widgets:
Expand Down Expand Up @@ -312,8 +295,13 @@ def _clear_input_canvas(self):

self._argument_widgets = []

def _populate_category_combo(self):
f = [f for f in link_function.members if len(f.output_labels) == 1]
categories = sorted(set(l.category for l in f + link_helper.members))
update_combobox(self._ui.category, list(zip(categories, categories)))

def _populate_function_combo(self):
""" Add name of functions to function combo box """
self._ui.function.clear()
for f in self._functions:
self._ui.function.addItem(f)
f = [f for f in link_function.members if len(f.output_labels) == 1]
functions = ((get_function_name(l[0]), l) for l in f + link_helper.members if l.category == self.category)
update_combobox(self._ui.function, functions)
85 changes: 54 additions & 31 deletions glue/dialogs/link_editor/qt/link_equation.ui
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>466</width>
<height>605</height>
<width>714</width>
<height>336</height>
</rect>
</property>
<property name="minimumSize">
Expand All @@ -24,36 +24,59 @@
<number>4</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0,0,8,8,0">
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0,8,8,0">
<item>
<widget class="QLabel" name="top_label">
<property name="font">
<font>
<pointsize>14</pointsize>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Select a function</string>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="function">
<property name="toolTip">
<string>Select a translation function to use</string>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLength</enum>
</property>
</widget>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Category:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="category"/>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Function:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="function">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Select a translation function to use</string>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLength</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="info">
Expand Down
4 changes: 2 additions & 2 deletions glue/dialogs/link_editor/qt/tests/test_link_equation.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,11 @@ def test_select_function_helper(self):
assert self.widget.function is member

def test_select_invalid_function(self):
with pytest.raises(KeyError) as exc:
with pytest.raises(ValueError) as exc:
def bad(x):
pass
self.widget.function = (bad, None, None)
assert exc.value.args[0].startswith('No function or helper found')
assert exc.value.args[0].startswith('Cannot find data')

def test_make_link_function(self):
widget = LinkEquation()
Expand Down
Loading