Exemple #1
0
class DataView(HasTraits):
    ''' Show location data as cursor moves about'''

    # latitude, longitude for current cursor
    latitude = Float(0)
    longitude = Float(0)

    # latitude, longitude for current cursor
    easting = Float(0)
    northing = Float(0)

    # depth of current mouse position
    depth = Float(0)

    # power & gain at current cursor
    power = CFloat(0)
    gain = CFloat(0)

    traits_view = View(Item('latitude'),
                       Item('longitude'),
                       Item('_'),
                       Item('easting'),
                       Item('northing'),
                       Item('_'),
                       Item('depth'),
                       Item('_'),
                       Item('power'),
                       Item('gain'),
                       resizable=True)
class SampleHole(HasTraits):
    id = Str
    x = Float
    y = Float
    x_cor = CFloat(0)
    y_cor = CFloat(0)
    render = Str
    shape = Str
    dimension = Float
    interpolated = False
    corrected = False
    interpolation_holes = None
    analyzed = False

    nominal_position = Property(depends_on='x,y')
    corrected_position = Property(depends_on='x_cor,y_cor')

    associated_hole = Str

    def _get_corrected_position(self):
        return self.x_cor, self.y_cor

    def _get_nominal_position(self):
        return self.x, self.y

    def has_correction(self):
        return self.corrected
Exemple #3
0
class AbstractNumericSensor(AbstractSensor):
    """
        Abstract class for numeric sensor types, that allows limiting
        value within a specific range.

        If limiting values (:attr:`.value_min`, :attr:`.value_max`) are used, value that exceeds
        these limits, is clipped to the range.
    """

    #: Minimum allowed value for status
    value_min = CFloat(float('-inf'))

    #: Maximum allowed value for status
    value_max = CFloat(float('inf'))

    view = AbstractSensor.view + ['value_min', 'value_max']

    @property
    def is_finite_range(self):
        return self.value_max - self.value_min < float('inf')

    def get_as_datadict(self):
        d = super().get_as_datadict()
        d.update(dict(value_min=self.value_min, value_max=self.value_max))
        return d

    def set_status(self, status, **kwargs):
        if status is None:
            clipped_status = None
        else:
            clipped_status = max(min(float(status), self.value_max),
                                 self.value_min)
        super().set_status(clipped_status, **kwargs)
Exemple #4
0
class ExportPane(TraitsDockPane):

    id = 'edu.mit.synbio.cytoflowgui.export_pane'
    name = 'Export'

    # the task serving as the dock pane's controller
    task = Instance(Task)

    closable = False
    dock_area = 'right'
    floatable = False
    movable = False
    visible = True

    # the dinky little model that this pane displays
    width = CFloat(11)
    height = CFloat(8.5)
    dpi = CInt(96)

    do_exit = Event
    do_export = Event

    def default_traits_view(self):
        return View(
            Item('width', editor=TextEditor(auto_set=False)),
            Item('height', editor=TextEditor(auto_set=False)),
            Item('dpi', editor=TextEditor(auto_set=False)),
            Item('do_export',
                 editor=ButtonEditor(value=True, label="Export figure..."),
                 show_label=False),
            Item('do_exit',
                 editor=ButtonEditor(value=True, label="Return to Cytoflow"),
                 show_label=False))
Exemple #5
0
class RangeSubset(HasStrictTraits):
    name = Str
    values = List
    high = CFloat(Undefined)
    low = CFloat(Undefined)
    
    str = Property(Str, depends_on = "name, values, high, low")
    
    def default_traits_view(self):
        return View(Item('high',
                         label = self.name,
                         editor = ValuesBoundsEditor(
                                     name = 'values',
                                     low_name = 'low',
                                     high_name = 'high',
                                     format = '%g',
                                     auto_set = False)))
        
    # MAGIC: gets the value of the Property trait "subset_str"
    def _get_str(self):
        if self.low == self.values[0] and self.high == self.values[-1]:
            return ""
        elif self.low == self.high:
            return "({0} == {1})" \
                   .format(util.sanitize_identifier(self.name), self.low)
        else:
            return "({0} >= {1} and {0} <= {2})" \
                   .format(util.sanitize_identifier(self.name), self.low, self.high) 
        
    @on_trait_change('values, values[]')
    def _values_changed(self):
        if self.high is Undefined:
            self.high = max(self.values)
            
        if self.low is Undefined:
            self.low = min(self.values)
        
        
    def __eq__(self, other):
        return (self.name == other.name and
                self.values == other.values and
                self.low == other.low and
                self.high == other.high)
        
    def __hash__(self):
        return hash((self.name, 
                     tuple(self.values), 
                     self.low, 
                     self.high))
Exemple #6
0
class AbstractPollingSensor(AbstractSensor):
    """ Abstract baseclass for sensor that polls periodically its status"""

    #: How often to do polling
    interval = CFloat(5)

    #: This can be used to enable/disable polling
    poll_active = CBool(True)

    _stop = CBool(False, transient=True)
    _pollthread = Any(transient=True)
    view = AbstractSensor.view + ["interval"]
    silent = CBool(True)
    history_frequency = CFloat(1.0)

    def setup(self):
        self._restart()

    def _poll_active_changed(self, old, new):
        if not self.traits_inited():
            return
        if new:
            self._restart()
        else:
            self._pollthread.cancel()

    def _restart(self):
        if self._stop:
            return
        if self._pollthread and self._pollthread.is_alive():
            self._pollthread.cancel()
        if self.poll_active:
            self.update_status()
            self._pollthread = threading.Timer(
                self.interval, threaded(self.system, self._restart))
            time_after_interval = datetime.now() + timedelta(
                seconds=self.interval)
            self._pollthread.name = "PollingSensor: %s next poll at %s (%.2f sek)" % (
                self.name, time_after_interval, self.interval)
            self._pollthread.start()

    def update_status(self):
        pass

    def cleanup(self):
        self._stop = True
        if self._pollthread:
            self._pollthread.cancel()
Exemple #7
0
class Converter(HasStrictTraits):

    # Trait definitions:
    input_amount = CFloat(12.0, desc="the input quantity")
    input_units = Units('inches', desc="the input quantity's units")
    output_amount = Property(
        depends_on=['input_amount', 'input_units', 'output_units'],
        desc="the output quantity")
    output_units = Units('feet', desc="the output quantity's units")

    # User interface views:
    traits_view = View(VGroup(HGroup(Item('input_amount', springy=True),
                                     Item('input_units', show_label=False),
                                     label='Input',
                                     show_border=True),
                              HGroup(Item('output_amount',
                                          style='readonly',
                                          springy=True),
                                     Item('output_units', show_label=False),
                                     label='Output',
                                     show_border=True),
                              help=ViewHelp),
                       title='Units Converter',
                       buttons=['Undo', 'OK', 'Help'])

    # Property implementations
    def _get_output_amount(self):
        return ((self.input_amount * self.input_units_) / self.output_units_)
Exemple #8
0
class StatusSaverService(AbstractUserService):
    """
        Service which is responsible for scheduling dumping system into file periodically.
    """

    autoload = True

    #: Dump saving interval, in seconds. Default 30 minutes.
    dump_interval = CFloat(30 * 60)

    _exit = CBool(False)
    _timer = Any(transient=True)

    def setup(self):
        if self.system.filename:
            self.system.on_trait_change(self.exit_save, "pre_exit_trigger")
            if self.dump_interval:
                self.save_system_periodically()

    def save_system_periodically(self):
        self.logger.debug('Saving system state')
        self.system.save_state()
        self._timer = Timer(self.dump_interval, self.save_system_periodically)
        self._timer.start()

    def exit_save(self):
        self.system.save_state()
        self._exit = True

    def cleanup(self):
        if self._timer and self._timer.is_alive():
            self._timer.cancel()
Exemple #9
0
 def _get_trait(self):
     if self.type == 'metadata' or self.type == 'category':
         return CStr()
     elif self.type == 'float':
         return CFloat()
     elif self.type == 'bool':
         return ConvertingBool()
Exemple #10
0
class WarpScalarFactory(PipeFactory):
    """Applies the WarpScalar mayavi filter to the given VTK object."""

    _target = Instance(filters.WarpScalar, ())

    warp_scale = CFloat(1.0, adapts="filter.scale_factor",
                            help="scale of the warp scalar")
class Rectangle(HasTraits):

    width = CFloat(1.0)

    height = CFloat(2.0)

    area = Property(depends_on=['width', 'height'])

    view = View(VGroup(
        HGroup(Item('width', label='W'), Item('height', label='H')),
        Item('area', style='readonly'),
    ),
                buttons=OKCancelButtons)

    def _get_area(self):

        return self.width * self.height
Exemple #12
0
class Title(SingletonModuleFactory):
    """Creates a title for the figure.

    **Function signature**::

        title(text, ...)

    """

    size = CFloat(1, help="the size of the title")

    height = CFloat(0.8,
                    adapts='y_position',
                    help="""height of the title, in portion of the
                                 figure height""")

    def _size_changed(self):
        self._target.width = min(0.05 * self.size * len(self._text), 1)
        self._target.x_position = 0.5 * (1 - self._target.width)

    _target = Instance(modules.Text)

    def __target_default(self):
        """ This is called only if no existing title is found."""
        width = min(0.05 * self.size * len(self._text), 1)
        text = modules.Text(
            text=self._text,
            y_position=self.height,
            x_position=0.5 * (1 - width),
        )
        text.width = width
        return text

    def __init__(self, text, **kwargs):
        self._text = text  # This will be used by _size_changed
        if not 'name' in kwargs:
            # The name is used as au unique marker to identify the
            # title. We need to set it ASAP.
            self.name = kwargs['name'] = 'Title'
        super(Title, self).__init__(**kwargs)
        self._target.text = self._text
        # We need to set position after Text is initiated, as text will
        # override these positions
        self._target.y_position = self.height
        self._size_changed()
Exemple #13
0
class TemperatureSensor(AbstractPollingSensor):

    """
        W1 interface (on Raspberry Pi board) that polls polling temperature.
        (kernel modules w1-gpio and w1-therm required).
        Not using RPIO, but placed this here, since this is also Raspberry Pi related sensor.
    """

    _status = CFloat

    #: Address of W1 temperature sensor (something like ``"28-00000558263c"``), see what you have in
    #: ``/sys/bus/w1/devices/``
    addr = CUnicode
    view = list(set(UserFloatSensor.view + AbstractPollingSensor.view + ["addr"]))

    #: Maximum jump in temperature, between measurements. These temperature sensors
    #: tend to give sometimes erroneous results.
    max_jump = CFloat(5.0)

    _error_count = Int(0, transient=True)

    #: Maximum number of erroneous measurements, until value is really set
    max_errors = Int(5)

    _first_reading = CBool(True, transient=True)

    def get_status_display(self, **kwargs):
        if 'value' in kwargs:
            value = kwargs['value']
        else:
            value = self.status
        return u"%.1f ⁰C" % value

    def update_status(self):
        w1file = "/sys/bus/w1/devices/%s/w1_slave" % self.addr
        try:
            f = open(w1file)
        except IOError:
            self.logger.error("IO-error, can't open %s, not set", w1file)
            return

        try:
            temp = float(f.read().split("\n")[1].split(" ")[9].split("=")[1]) / 1000.
        except IOError:
            self.logger.error("IO-error, cant' read %s, not set", w1file)
            return
        if (abs(temp-self.status) > self.max_jump
                and self._error_count < self.max_errors
                and not self._first_reading):
            self._error_count += 1
        else:
            self._first_reading = False
            self._error_count = 0
            self.set_status(temp)
        f.close()
Exemple #14
0
class ModuleFactory(PipeFactory):
    """ Base class for all the modules factories"""
    color = Trait(
        None,
        None,
        TraitTuple(Range(0., 1.), Range(0., 1.), Range(0., 1.)),
        help="""the color of the vtk object. Overides the colormap,
                        if any, when specified. This is specified as a
                        triplet of float ranging from 0 to 1, eg (1, 1,
                        1) for white.""",
    )

    def _color_changed(self):
        if self.color:
            self._target.actor.property.color = self.color
            if hasattr(self._target.actor.mapper, "scalar_visibility"):
                self._target.actor.mapper.scalar_visibility = False
            if hasattr(self._target, "property"):
                self._target.property.color = self.color

    opacity = CFloat(1., desc="""The overall opacity of the vtk object.""")

    def _opacity_changed(self):
        try:
            self._target.actor.property.opacity = self.opacity
        except AttributeError:
            try:
                self._target.property.opacity = self.opacity
            except AttributeError:
                pass

    line_width = CFloat(2., desc=""" The width of the lines, if any used.""")

    def _line_width_changed(self):
        try:
            self._target.actor.property.line_width = self.line_width
        except (AttributeError, TraitError):
            try:
                self._target.property.line_width = self.line_width
            except (AttributeError, TraitError):
                pass
Exemple #15
0
class TubeFactory(PipeFactory):
    """Applies the Tube mayavi filter to the given VTK object."""

    _target = Instance(filters.Tube, ())

    tube_sides = CInt(6, adapts='filter.number_of_sides',
                        desc="""number of sides of the tubes used to
                        represent the lines.""")

    tube_radius = CFloat(0.05, adapts='filter.radius',
                        desc="""radius of the tubes used to represent the
                        lines.""")
class Text3D(ModuleFactory):
    """ Positions text at a 3D location in the scene.

        **Function signature**::

            text3d(x, y, z, text, ...)

        x, y, and z are the position of the origin of the text. The
        text is positioned in 3D, in figure coordinates.
        """

    _target = Instance(modules.Text3D, ())

    scale = Either(CFloat(1),
                   CArray(shape=(3, )),
                   help="""The scale of the text, in figure units.
                                Either a float, or 3-tuple of floats.""")

    orientation = CArray(shape=(3, ),
                         adapts='orientation',
                         desc="""the angles giving the orientation of the
                        text. If the text is oriented to the camera,
                        these angles are referenced to the axis of the
                        camera. If not, these angles are referenced to
                        the z axis.""")

    orient_to_camera = Bool(True,
                            adapts='orient_to_camera',
                            desc="""if the text is kept oriented to the
                        camera, or is pointing in a specific direction,
                        regardless of the camera position.""")

    def __init__(self, x, y, z, text, **kwargs):
        """ Override init as for different positional arguments."""
        if not 'scale' in kwargs:
            kwargs['scale'] = 1
        super(Text3D, self).__init__(None, **kwargs)
        self._target.text = text
        self._target.position = (x, y, z)

    def _scale_changed(self):
        scale = self.scale
        if isinstance(scale, numbers.Number):
            scale = scale * np.ones((3, ))
        self._target.scale = scale
Exemple #17
0
class RpioSensor(UserBoolSensor):

    """
        Boolean-valued sensor object that reads Raspberry Pi GPIO input pins.
    """

    user_editable = CBool(False)

    #: GPIO port
    port = Int

    #: Set to True to have inversed status value
    inverted = Bool(False)

    #: Button setup: "down": pushdown resistor, "up": pushup resistor, or "none": no resistor set up.
    button_type = Enum("down", "up", "none")

    view = UserBoolSensor.view + ["port", "button_type"]

    history_frequency = CFloat(1.0)

    _hw_service = Instance(AbstractSystemService, transient=True)

    def setup(self):
        self._hw_service = self.system.request_service('RpioService')
        self._hw_service.enable_input_port(self.port, self.gpio_callback, self.button_type)

    def _button_type_changed(self, new):
        if self._hw_service:
            self._hw_service.disable_input_port(self.port)
            self._hw_service.enable_input_port(self.port, self.gpio_callback, new)

    def gpio_callback(self, gpio_id, value):
        self.set_status(value if not self.inverted else not value)

    def update_status(self):
        self.gpio_callback(None, self._hw_service.get_input_status(self.port))

    def _port_changed(self, old, new):
        if not self._hw_service:
            return
        if old:
            self._hw_service.disable_input_port(old)
        self._hw_service.enable_input_port(new, self.gpio_callback, self.button_type)
Exemple #18
0
class Text(ModuleFactory):
    """ Adds a text on the figure.

        **Function signature**::

            text(x, y, text, ...)

        x, and y are the position of the origin of the text. If no z
        keyword argument is given, x and y are the 2D projection of the
        figure, they belong to [0, 1]. If a z keyword  argument is given, the
        text is positionned in 3D, in figure coordinnates.
        """

    width = Trait(None,
                  None,
                  CFloat,
                  adapts='width',
                  help="""width of the text.""")

    z = Trait(None,
              None,
              CFloat,
              help="""Optional z position. When specified, the
                      text is positioned in 3D""")

    _target = Instance(modules.Text, ())

    opacity = CFloat(1,
                     adapts="property.opacity",
                     help="""The opacity of the text.""")

    def __init__(self, x, y, text, **kwargs):
        """ Override init as for different positional arguments."""
        if 'z' in kwargs and kwargs['z'] is not None:
            self._target.z_position = kwargs['z']
            self._target.position_in_3d = True
        elif not (x < 1. and x > 0. and y > 0. and y < 1.):
            raise ValueError('Text positions should be in [0, 1] if no z'
                             'position is given')
        super(Text, self).__init__(None, **kwargs)
        self._target.text = text
        self._target.x_position = x
        self._target.y_position = y
Exemple #19
0
class AbstractArduinoSensor(AbstractSensor):
    """
        Abstract base class for Arduino sensors
    """

    history_frequency = CFloat(1.0)
    user_editable = CBool(False)

    #: Arduino service number (specify, if more than 1 ArduinoServices are configured in system)
    service = CInt(0)

    #: Arduino pin number
    pin = CInt

    _arduino = Instance(AbstractSystemService, transient=True)

    def setup(self, *args, **kwargs):
        super().setup(*args, **kwargs)
        self.logger.debug('Arduino sensor setup')
        self._arduino = self.system.request_service('ArduinoService',
                                                    self.service)
class ArduinoServoActuator(AbstractArduinoActuator):
    """
        Float-valued actuator object for Arduino output pins that can be configured in Servo mode
        Status is servo angle (0-360).
    """

    _status = CFloat(transient=True)

    #: Minimum pulse time (in microseconds)
    min_pulse = CInt(544)

    #: Maximum pulse time (in microseconds)
    max_pulse = CInt(2400)

    def _min_pulse_changed(self):
        if self.traits_inited():
            self.setup()

    def _max_pulse_changed(self):
        if self.traits_inited():
            self.setup()

    def setup(self, *args, **kwargs):
        super().setup(*args, **kwargs)
        self.logger.debug("setup_servo %s %s %s %s %s %s", self, self.service,
                          self.pin, self.min_pulse, self.max_pulse,
                          int(round(self._status)))
        self._arduino.setup_servo(self.pin, self.min_pulse, self.max_pulse,
                                  int(round(self._status)))

    def _status_changed(self):
        self.logger.debug("change_servo %s %s %s", self.pin,
                          int(round(self._status)))
        self._arduino.change_digital(self.pin, int(round(self._status)))

    def cleanup(self):
        self._arduino.cleanup_digital_actuator(self.pin)
class ArduinoRemotePWMActuator(AbstractArduinoActuator):
    """
        Actuator that sends target device analog (PWM) output pin status change requests

        Needs `AutomateFirmata <https://github.com/tuomas2/AutomateFirmata>`_
    """

    _status = CFloat(transient=True)

    #: Target device number
    device = CInt

    def setup(self, *args, **kwargs):
        super().setup(*args, **kwargs)
        self._arduino.send_virtualwire_command(
            self.device, arduino_service.VIRTUALWIRE_SET_PIN_MODE, self.pin,
            pyfirmata.PWM)

    def _status_changed(self):
        value = min(max(self.status, 0.), 1.)
        value = int(round(value * 255))  # Arduino PWM has 8 bit resolution
        self._arduino.send_virtualwire_command(
            self.device, arduino_service.VIRTUALWIRE_ANALOG_MESSAGE, self.pin,
            value)
Exemple #22
0
class Model(HasTraits):
    npts_x = CInt(256)
    npts_y = CInt(256)
    npts_z = CInt(109)

    min_x = CFloat(-2 * pi)
    max_x = CFloat(2 * pi)
    min_y = CFloat(-2 * pi)
    max_y = CFloat(2 * pi)
    min_z = CFloat(-pi)
    max_z = CFloat(pi)

    xs = Array
    ys = Array
    vals = Array

    minval = Float
    maxval = Float

    model_changed = Event

    def __init__(self, *args, **kwargs):
        super(Model, self).__init__(*args, **kwargs)
        self.compute_model()

    @on_trait_change("npts_+", "min_+", "max_+")
    def compute_model(self):
        def vfunc(x, y, z):
            return sin(x * z) * cos(y) * sin(z) + sin(0.5 * z)

        # Create the axes
        self.xs = linspace(self.min_x, self.max_x, self.npts_x)
        self.ys = linspace(self.min_y, self.max_y, self.npts_y)
        self.zs = linspace(self.min_z, self.max_z, self.npts_z)

        # Generate a cube of values by using newaxis to span new dimensions
        self.vals = vfunc(self.xs[:, newaxis, newaxis],
                          self.ys[newaxis, :, newaxis], self.zs[newaxis,
                                                                newaxis, :])

        self.minval = nanmin(self.vals)
        self.maxval = nanmax(self.vals)
        self.model_changed = True
Exemple #23
0
class StreamlineFactory(DataModuleFactory):
    """Applies the Streamline mayavi module to the given VTK data object."""
    _target = Instance(modules.Streamline, ())

    linetype = Trait('line', 'ribbon', 'tube',
            adapts='streamline_type',
            desc="""the type of line-like object used to display the
                   streamline.""")

    seedtype = Trait('sphere',
            {'sphere':0, 'line':1, 'plane':2, 'point':3},
            desc="""the widget used as a seed for the streamlines.""")

    seed_visible = Bool(True,
            adapts='seed.widget.enabled',
            desc="Control the visibility of the seed.",
            )

    seed_scale = CFloat(1.,
            desc="Scales the seed around its default center",
            )

    seed_resolution = Either(None, CInt,
            desc='The resolution of the seed. Determines the number of '
                 'seed points')

    integration_direction = Trait('forward', 'backward', 'both',
            adapts='stream_tracer.integration_direction',
            desc="The direction of the integration.",
            )


    def _seedtype_changed(self):
        # XXX: this also acts for seed_scale and seed_resolution, but no
        # need to define explicit callbacks, as all the callbacks are
        # being called anyhow.
        self._target.seed.widget = widget = \
                            self._target.seed.widget_list[self.seedtype_]

        if  not self.seed_scale==1.:
            widget.enabled = True
            if self.seedtype == 'line':
                p1 = widget.point1
                p2 = widget.point2
                center = (p1 + p2)/2.
                widget.point1 = center + self.seed_scale*(p1 - center)
                widget.point2 = center + self.seed_scale*(p2 - center)
            elif self.seedtype == 'plane':
                p1 = widget.point1
                p2 = widget.point2
                center = (p1 + p2)/2.
                o = widget.origin
                widget.point1 = center + self.seed_scale*(p1 - center)
                widget.point2 = center + self.seed_scale*(p2 - center)
                widget.origin = center + self.seed_scale*(o - center)
            elif self.seedtype == 'sphere':
                widget.radius *= self.seed_scale

            # XXX: Very ugly, but this is only way I have found to
            # propagate changes.
            self._target.seed.stop()
            self._target.seed.start()
            widget.enabled = self.seed_visible

        if self.seed_resolution is not None:
            widget.enabled = True
            if self.seedtype in ('plane', 'line'):
                widget.resolution = self.seed_resolution
            elif self.seedtype == 'sphere':
                widget.phi_resolution = widget.theta_resolution = \
                        self.seed_resolution

            # XXX: Very ugly, but this is only way I have found to
            # propagate changes.
            self._target.seed.stop()
            self._target.seed.start()
            widget.enabled = self.seed_visible
Exemple #24
0
class VectorsFactory(DataModuleFactory):
    """Applies the Vectors mayavi module to the given data object
        source (Mayavi source, or VTK dataset).
    """

    _target = Instance(modules.Vectors, ())

    scale_factor = CFloat(1., adapts='glyph.glyph.scale_factor',
                            desc="""the scaling applied to the glyphs. The
                                    size of the glyph is by default in drawing
                                    units.""")

    scale_mode = Trait('vector', {'none':'data_scaling_off',
                                'scalar':'scale_by_scalar',
                                'vector':'scale_by_vector'},
                            help="""the scaling mode for the glyphs
                            ('vector', 'scalar', or 'none').""")

    resolution = CInt(8, desc="The resolution of the glyph created. For "
                        "spheres, for instance, this is the number of "
                        "divisions along theta and phi.")

    mask_points = Either(None, CInt,
                        desc="If supplied, only one out of 'mask_points' "
                        "data point is displayed. This option is useful "
                        "to reduce the number of points displayed "
                        "on large datasets")

    def _resolution_changed(self):
        glyph = self._target.glyph.glyph_source.glyph_source
        if hasattr(glyph, 'theta_resolution'):
            glyph.theta_resolution = self.resolution
        if hasattr(glyph, 'phi_resolution'):
            glyph.phi_resolution = self.resolution
        if hasattr(glyph, 'resolution'):
            glyph.resolution = self.resolution
        if hasattr(glyph, 'shaft_resolution'):
            glyph.shaft_resolution = self.resolution
        if hasattr(glyph, 'tip_resolution'):
            glyph.tip_resolution = self.resolution

    def _mask_points_changed(self):
        if self.mask_points is not None:
            self._target.glyph.mask_input_points = True
            self._target.glyph.mask_points.on_ratio = self.mask_points

    def _scale_mode_changed(self):
        self._target.glyph.scale_mode = self.scale_mode_

    mode = Trait('2darrow', glyph_mode_dict,
                    desc="""the mode of the glyphs.""")

    def _mode_changed(self):
        v = self._target
        # Workaround for different version of VTK:
        if hasattr(v.glyph.glyph_source, 'glyph_source'):
            g = v.glyph.glyph_source
        else:
            g = v.glyph
        if self.mode == 'point':
            g.glyph_source = tvtk.PointSource(radius=0, number_of_points=1)
        else:
            g.glyph_source = g.glyph_list[self.mode_]
        if self.mode_ == 0:
            g.glyph_source.glyph_type = self.mode[2:]
Exemple #25
0
class Mesh(Pipeline):
    """
    Plots a surface using grid-spaced data supplied as 2D arrays.

    **Function signatures**::

        mesh(x, y, z, ...)

    x, y, z are 2D arrays, all of the same shape, giving the positions of
    the vertices of the surface. The connectivity between these points is
    implied by the connectivity on the arrays.

    For simple structures (such as orthogonal grids) prefer the `surf`
    function, as it will create more efficient data structures. For mesh
    defined by triangles rather than regular implicit connectivity, see the
    `triangular_mesh` function.
    """

    scale_mode = Trait('none', {
        'none': 'data_scaling_off',
        'scalar': 'scale_by_scalar',
        'vector': 'scale_by_vector'
    },
                       help="""the scaling mode for the glyphs
                            ('vector', 'scalar', or 'none').""")

    scale_factor = CFloat(0.05,
                          desc="""scale factor of the glyphs used to represent
                        the vertices, in fancy_mesh mode. """)

    tube_radius = Trait(0.025,
                        CFloat,
                        None,
                        help="""radius of the tubes used to represent the
                        lines, in mesh mode. If None, simple lines are used.
                        """)

    scalars = Array(help="""optional scalar data.""")

    mask = Array(help="""boolean mask array to suppress some data points.
                 Note: this works based on colormapping of scalars and will
                 not work if you specify a solid color using the
                 `color` keyword.""")

    representation = Trait(
        'surface',
        'wireframe',
        'points',
        'mesh',
        'fancymesh',
        desc="""the representation type used for the surface.""")

    _source_function = Callable(grid_source)

    _pipeline = [
        ExtractEdgesFactory, GlyphFactory, TubeFactory, SurfaceFactory
    ]

    def __call_internal__(self, *args, **kwargs):
        """ Override the call to be able to choose whether to apply
        filters.
        """
        self.source = self._source_function(*args, **kwargs)
        kwargs.pop('name', None)
        self.store_kwargs(kwargs)
        # Copy the pipeline so as not to modify it for the next call
        self.pipeline = self._pipeline[:]
        if not self.kwargs['representation'] in ('mesh', 'fancymesh'):
            self.pipeline.remove(ExtractEdgesFactory)
            self.pipeline.remove(TubeFactory)
            self.pipeline.remove(GlyphFactory)
            self.pipeline = [
                PolyDataNormalsFactory,
            ] + self.pipeline
        else:
            if self.kwargs['tube_radius'] is None:
                self.pipeline.remove(TubeFactory)
            if not self.kwargs['representation'] == 'fancymesh':
                self.pipeline.remove(GlyphFactory)
            self.kwargs['representation'] = 'surface'
        return self.build_pipeline()
Exemple #26
0
class BarChart(Pipeline):
    """
    Plots vertical glyphs (like bars) scaled vertical, to do
    histogram-like plots.

    This functions accepts a wide variety of inputs, with positions given
    in 2-D or in 3-D.

    **Function signatures**::

        barchart(s, ...)
        barchart(x, y, s, ...)
        barchart(x, y, f, ...)
        barchart(x, y, z, s, ...)
        barchart(x, y, z, f, ...)

    If only one positional argument is passed, it can be a 1-D, 2-D, or 3-D
    array giving the length of the vectors. The positions of the data
    points are deducted from the indices of array, and an
    uniformly-spaced data set is created.

    If 3 positional arguments (x, y, s) are passed the last one must be
    an array s, or a callable, f, that returns an array. x and y give the
    2D coordinates of positions corresponding to the s values.

    If 4 positional arguments (x, y, z, s) are passed, the 3 first are
    arrays giving the 3D coordinates of the data points, and the last one
    is an array s, or a callable, f, that returns an array giving the
    data value.
    """

    _source_function = Callable(vertical_vectors_source)

    _pipeline = [
        VectorsFactory,
    ]

    mode = Trait('cube',
                 bar_mode_dict,
                 desc='The glyph used to represent the bars.')

    lateral_scale = CFloat(0.9,
                           desc='The lateral scale of the glyph, '
                           'in units of the distance between nearest points')

    auto_scale = true(desc='whether to compute automatically the '
                      'lateral scaling of the glyphs. This might be '
                      'computationally expensive.')

    def __call_internal__(self, *args, **kwargs):
        """ Override the call to be able to scale automatically the axis.
        """
        g = Pipeline.__call_internal__(self, *args, **kwargs)
        gs = g.glyph.glyph_source
        # Use a cube source for glyphs.
        if not 'mode' in kwargs:
            gs.glyph_source = gs.glyph_dict['cube_source']
        # Position the glyph tail on the point.
        gs.glyph_position = 'tail'
        gs.glyph_source.center = (0.0, 0.0, 0.5)
        g.glyph.glyph.orient = False
        if not 'color' in kwargs:
            g.glyph.color_mode = 'color_by_scalar'
        if not 'scale_mode' in kwargs:
            g.glyph.scale_mode = 'scale_by_vector_components'
        g.glyph.glyph.clamping = False
        # The auto-scaling code. It involves finding the minimum
        # distance between points, which can be very expensive. We
        # shortcut this calculation for structured data
        if len(args) == 1 or self.auto_scale:
            min_axis_distance = 1
        else:
            x, y, z = g.mlab_source.x, g.mlab_source.y, g.mlab_source.z
            min_axis_distance = \
                    tools._min_axis_distance(x, y, z)
        scale_factor = g.glyph.glyph.scale_factor * min_axis_distance
        lateral_scale = kwargs.pop('lateral_scale', self.lateral_scale)
        try:
            g.glyph.glyph_source.glyph_source.y_length = \
                    lateral_scale / (scale_factor)
            g.glyph.glyph_source.glyph_source.x_length = \
                    lateral_scale / (scale_factor)
        except TraitError:
            " Not all types of glyphs have controlable y_length and x_length"

        return g
class DataRange2D(BaseDataRange):
    """ A range on (2-D) image data.

    In a mathematically general sense, a 2-D range is an arbitrary region in
    the plane.  Arbitrary regions are difficult to implement well, so this
    class supports only rectangular regions for now.
    """

    # The actual value of the lower bound of this range. To set it, use
    # **low_setting**.
    low = Property  # (2,) array of lower-left x,y
    # The actual value of the upper bound of this range. To set it, use
    # **high_setting**.
    high = Property  # (2,) array of upper-right x,y

    # Property for the lower bound of this range (overrides AbstractDataRange).
    low_setting = Property
    # Property for the upper bound of this range (overrides AbstractDataRange).
    high_setting = Property

    # The 2-D grid range is actually implemented as two 1-D ranges, which can
    # be accessed individually.  They can also be set to new DataRange1D
    # instances; in that case, the DataRange2D's sources are removed from
    # its old 1-D dataranges and added to the new one.

    # Property for the range in the x-dimension.
    x_range = Property
    # Property for the range in the y-dimension.
    y_range = Property

    # Do "auto" bounds imply an exact fit to the data? (One Boolean per
    # dimension) If False, the bounds pad a little bit of margin on either
    # side.
    tight_bounds = Tuple(Bool(True), Bool(True))
    # The minimum percentage difference between low and high for each
    # dimension. That is, (high-low) >= epsilon * low.
    epsilon = Tuple(CFloat(1.0e-4), CFloat(1.0e-4))

    #------------------------------------------------------------------------
    # Private traits
    #------------------------------------------------------------------------

    # DataRange1D for the x-dimension.
    _xrange = Instance(DataRange1D, args=())
    # DataRange1D for the y-dimension.
    _yrange = Instance(DataRange1D, args=())

    #------------------------------------------------------------------------
    # AbstractRange interface
    #------------------------------------------------------------------------

    def clip_data(self, data):
        """ Returns a list of data values that are within the range.

        Implements AbstractDataRange.
        """
        return compress(self.mask_data(data), data, axis=0)

    def mask_data(self, data):
        """ Returns a mask array, indicating whether values in the given array
        are inside the range.

        Implements AbstractDataRange.
        """
        x_points, y_points = transpose(data)
        x_mask = (x_points >= self.low[0]) & (x_points <= self.high[0])
        y_mask = (y_points >= self.low[1]) & (y_points <= self.high[1])
        return x_mask & y_mask

    def bound_data(self, data):
        """ Not implemented for this class.
        """
        raise NotImplementedError("bound_data() has not been implemented "
                                  "for 2d pointsets.")

    def set_bounds(self, low, high):
        """ Sets all the bounds of the range simultaneously.

        Implements AbstractDataRange.

        Parameters
        ----------
        low : (x,y)
            Lower-left corner of the range.
        high : (x,y)
            Upper right corner of the range.
        """
        self._do_set_low_setting(low, fire_event=False)
        self._do_set_high_setting(high)

    #------------------------------------------------------------------------
    # Public methods
    #------------------------------------------------------------------------

    def __init__(self, *args, **kwargs):
        super(DataRange2D, self).__init__(*args, **kwargs)

    def reset(self):
        """ Resets the bounds of this range.
        """
        self.high_setting = ('auto', 'auto')
        self.low_setting = ('auto', 'auto')
        self.refresh()

    def refresh(self):
        """ If any of the bounds is 'auto', this method refreshes the actual
        low and high values from the set of the view filters' data sources.
        """
        if 'auto' not in self.low_setting and \
           'auto' not in self.high_setting:
            # If the user has hard-coded bounds, then refresh() doesn't do
            # anything.
            return
        else:
            self._refresh_bounds()

    #------------------------------------------------------------------------
    # Private methods
    #------------------------------------------------------------------------

    def _refresh_bounds(self):
        self._xrange.refresh()
        self._yrange.refresh()

    #------------------------------------------------------------------------
    # Property getters and setters
    #------------------------------------------------------------------------

    def _get_low(self):
        return (self._xrange.low, self._yrange.low)

    def _set_low(self, val):
        return self._set_low_setting(val)

    def _get_low_setting(self):
        return (self._xrange.low_setting, self._yrange.low_setting)

    def _set_low_setting(self, val):
        self._do_set_low_setting(val)

    def _do_set_low_setting(self, val, fire_event=True):
        self._xrange.low_setting = val[0]
        self._yrange.low_setting = val[1]

    def _get_high(self):
        return (self._xrange.high, self._yrange.high)

    def _set_high(self, val):
        return self._set_high_setting(val)

    def _get_high_setting(self):
        return (self._xrange.high_setting, self._yrange.high_setting)

    def _set_high_setting(self, val):
        self._do_set_high_setting(val)

    def _do_set_high_setting(self, val, fire_event=True):
        self._xrange.high_setting = val[0]
        self._yrange.high_setting = val[1]

    def _get_x_range(self):
        return self._xrange

    def _set_x_range(self, newrange):
        self._set_1d_range("_xdata", self._xrange, newrange)
        self._xrange = newrange

    def _get_y_range(self):
        return self._yrange

    def _set_y_range(self, newrange):
        self._set_1d_range("_ydata", self._yrange, newrange)
        self._yrange = newrange

    def _set_1d_range(self, dataname, oldrange, newrange):
        # dataname is the name of the underlying 1d data source of the
        # ImageData instances in self.sources, e.g. "_xdata" or "_ydata"
        for source in self.sources:
            source1d = getattr(source, dataname, None)
            if source1d:
                if oldrange:
                    oldrange.remove(source1d)
                if newrange:
                    newrange.add(source1d)
        return

    #------------------------------------------------------------------------
    # Event handlers
    #------------------------------------------------------------------------

    def _sources_items_changed(self, event):
        for source in event.removed:
            source.on_trait_change(self.refresh, "data_changed", remove=True)
        for source in event.added:
            source.on_trait_change(self.refresh, "data_changed")
        # the _xdata and _ydata of the sources may be created anew on every
        # access, so we can't just add/delete from _xrange and _yrange sources
        # based on object identity. So recreate lists each time:
        self._xrange.sources = [s._xdata for s in self.sources]
        self._yrange.sources = [s._ydata for s in self.sources]
        self.refresh()

    def _sources_changed(self, old, new):
        for source in old:
            source.on_trait_change(self.refresh, "data_changed", remove=True)
        for source in new:
            source.on_trait_change(self.refresh, "data_changed")
        # the _xdata and _ydata of the sources may be created anew on every
        # access, so we can't just add/delete from _xrange and _yrange sources
        # based on object identity. So recreate lists each time:
        self._xrange.sources = [s._xdata for s in self.sources]
        self._yrange.sources = [s._ydata for s in self.sources]
        self.refresh()

    @on_trait_change("_xrange.updated,_yrange.updated")
    def _subranges_updated(self):
        self.updated = True
class Model(HasTraits):

    #Traits view definitions:
    traits_view = View(
        Group(Item('function'),
              HGroup(Item('npts_x', label="Number X Points"),
                     Item('npts_y', label="Number Y Points")),
              HGroup(Item('min_x', label="Min X value"),
                     Item('max_x', label="Max X value")),
              HGroup(Item('min_y', label="Min Y value"),
                     Item('max_y', label="Max Y value"))),
                     buttons=["OK", "Cancel"])

    function = Str("tanh(x**2+y)*cos(y)*jn(0,x+y*2)")

    npts_x = CInt(400)
    npts_y = CInt(200)

    min_x = CFloat(-2*pi)
    max_x = CFloat(2*pi)
    min_y = CFloat(-1.5*pi)
    max_y = CFloat(1.5*pi)

    xs = Array
    ys = Array
    zs = Array

    minz = Float
    maxz = Float

    model_changed = Event

    def __init__(self, *args, **kwargs):
        super(Model, self).__init__(*args, **kwargs)
        self.compute_model()

    def compute_model(self):
        # The xs and ys used for the image plot range need to be the
        # edges of the cells.
        self.xs = linspace(self.min_x, self.max_x, self.npts_x+1)
        self.ys = linspace(self.min_y, self.max_y, self.npts_y+1)

        # The grid of points at which we will evaluate the 2D function
        # is located at cell centers, so use halfsteps from the
        # min/max values (which are edges)
        xstep = (self.max_x - self.min_x) / self.npts_x
        #ystep = (self.max_y - self.min_y) / self.npts_y
        gridx = linspace(self.min_x+xstep/2, self.max_x-xstep/2, self.npts_x)
        gridy = linspace(self.min_y+xstep/2, self.max_y-xstep/2, self.npts_y)
        x, y = meshgrid(gridx, gridy)
        try:
            d = dict(x=x, y=y)
            exec "from scipy import *" in d
            exec "from scipy.special import *" in d
            self.zs = eval(self.function, d)
            self.minz = nanmin(self.zs)
            self.maxz = nanmax(self.zs)
            self.model_changed = True
            self._function = self.function
        except:
            self.set(function = self._function, trait_change_notify=False)

    def _anytrait_changed(self, name, value):
        if name in ['function', 'npts_x', 'npts_y',
                    'min_x', 'max_x', 'min_y', 'max_y']:
            self.compute_model()
Exemple #29
0
class RangeOp(HasStrictTraits):
    """Apply a range gate to a cytometry experiment.
    
    Attributes
    ----------
    name : Str
        The operation name.  Used to name the new metadata field in the
        experiment that's created by apply()
        
    channel : Str
        The name of the channel to apply the range gate.
        
    low : Float
        The lowest value to include in this gate.
        
    high : Float
        The highest value to include in this gate.
        
    Examples
    --------
    >>> range = flow.RangeOp()
    >>> range.name = "Y2-A+"
    >>> range.channel = 'Y2-A'
    >>> range.low = 0.3
    >>> range.high = 0.8
    >>> 
    >>> ex3 = range.apply(ex2)
    
    Alternately  (in an IPython notebook with `%matplotlib notebook`)
    
    >>> r = RangeOp(name = 'Y2-A+',
    ...             channel = 'Y2-A')
    >>> rv = r.default_view()
    >>> rv.interactive = True
    >>> rv.plot(ex2)
    >>> ### draw a range on the plot ###
    >>> ex3 = r.apply(ex2)
    """

    # traits
    id = Constant('edu.mit.synbio.cytoflow.operations.range')
    friendly_id = Constant('Range')

    name = CStr()
    channel = Str()
    low = CFloat()
    high = CFloat()

    def apply(self, experiment):
        """Applies the threshold to an experiment.
        
        Parameters
        ----------
        experiment : Experiment
            the old_experiment to which this op is applied
            
        Returns
        -------
            a new experiment, the same as old_experiment but with a new
            column the same as the operation name.  The bool is True if the
            event's measurement in self.channel is greater than self.low and
            less than self.high; it is False otherwise.
        """

        if not experiment:
            raise util.CytoflowOpError("No experiment specified")

        # make sure name got set!
        if not self.name:
            raise util.CytoflowOpError("You have to set the gate's name "
                                       "before applying it!")

        if self.name in experiment.data.columns:
            raise util.CytoflowOpError(
                "Experiment already has a column named {0}".format(self.name))

        if not self.channel:
            raise util.CytoflowOpError("Channel not specified")

        if not self.channel in experiment.channels:
            raise util.CytoflowOpError(
                "Channel {0} not in the experiment".format(self.channel))

        if self.high <= self.low:
            raise util.CytoflowOpError("range high must be > range low")

        if self.high <= experiment[self.channel].min():
            raise util.CytoflowOpError("range high must be > {0}".format(
                experiment[self.channel].min()))
        if self.low >= experiment[self.channel].max:
            raise util.CytoflowOpError("range low must be < {0}".format(
                experiment[self.channel].max()))

        gate = experiment[self.channel].between(self.low, self.high)
        new_experiment = experiment.clone()
        new_experiment.add_condition(self.name, "bool", gate)
        new_experiment.history.append(self.clone_traits())

        return new_experiment

    def default_view(self, **kwargs):
        return RangeSelection(op=self, **kwargs)
Exemple #30
0
class RobotView(HasTraits):
    size = (300, 300)

    view = View(Group(Item('plot',
                           editor=ComponentEditor(size=size),
                           show_label=False),
                      Item(name='velocity_x'),
                      Item(name='velocity_y'),
                      orientation="vertical"),
                resizable=True,
                width=size[0],
                height=size[1])

    wheel_line = Instance(Line, args=())

    velocity_x = CFloat(0.0, desc="Velocity in the x direction", label="Vx")
    velocity_y = CFloat(0.0, desc="Velocity in the y direction", label="Vy")

    def __init__(self,
                 velocity_callback=None,
                 x_range=DataRange1D(low=0, high=size[0]),
                 y_range=DataRange1D(low=0, high=size[1]),
                 *args,
                 **kwargs):
        super().__init__(*args, **kwargs)

        self.robot_bounding_box = {
            "x": [100, 200, 200, 100],
            "y": [80, 80, 220, 220]
        }

        self.robot_wheels = [
            {  # Bottom-left
                "x": [90, 110, 110, 90],
                "y": [90, 90, 140, 140]
            },
            {  # Bottom-right
                "x": [190, 210, 210, 190],
                "y": [90, 90, 140, 140]
            },
            {  # Top-right
                "x": [90, 110, 110, 90],
                "y": [160, 160, 210, 210]
            },
            {  # Top-left
                "x": [190, 210, 210, 190],
                "y": [160, 160, 210, 210]
            },
        ]

        self.plot_data = ArrayPlotData(robot_x=self.robot_bounding_box['x'],
                                       robot_y=self.robot_bounding_box['y'],
                                       wheel_bl_x=self.robot_wheels[0]['x'],
                                       wheel_bl_y=self.robot_wheels[0]['y'],
                                       wheel_br_x=self.robot_wheels[1]['x'],
                                       wheel_br_y=self.robot_wheels[1]['y'],
                                       wheel_tr_x=self.robot_wheels[2]['x'],
                                       wheel_tr_y=self.robot_wheels[2]['y'],
                                       wheel_tl_x=self.robot_wheels[3]['x'],
                                       wheel_tl_y=self.robot_wheels[3]['y'],
                                       arrow_x=[0, 100, 200],
                                       arrow_y=[0, 100, 100],
                                       arrowhead_x=[150, 200],
                                       arrowhead_y=[150, 200])

        self.x_range = x_range
        self.y_range = y_range
        self.velocity_callback = velocity_callback

        velocity_tool = VelocityTool((150, 150), (300, 300),
                                     self.update_velocity, self.hover_velocity)

        # Show starting plot
        self.plot = self.plot_robot()
        self.plot.tools.append(velocity_tool)
        self.plot_velocity()

    def update_velocity(self, velocity):
        target_x = 150 + velocity[0] * 150
        target_y = 150 + velocity[1] * 150
        self.velocity_x = (target_x - 150) / 150
        self.velocity_y = (target_y - 150) / 150
        self.plot_arrow((150, 150), (target_x, target_y), (1.0, 0, 0, 0.9),
                        'vel')

        if self.velocity_callback is not None:
            self.velocity_callback(self.velocity_x, self.velocity_y)

    def hover_velocity(self, hover_velocity):
        target_x = 150 + hover_velocity[0] * 150
        target_y = 150 + hover_velocity[1] * 150
        self.plot_arrow((150, 150), (target_x, target_y),
                        (0.0, 0.0, 0.0, 0.25), 'hov_vel')
        pass

    def plot_robot(self):
        plot = Plot(self.plot_data)
        plot.range2d.x_range = self.x_range
        plot.range2d.y_range = self.y_range
        plot.x_mapper = LinearMapper(range=self.x_range)
        plot.y_mapper = LinearMapper(range=self.y_range)
        plot.title = "robot"

        plot.plot(('robot_x', 'robot_y'),
                  type='polygon',
                  marker='dot',
                  marker_size=1.0,
                  color=(0, 0, 0, 0.8))

        wheel_outline_color = (0, 0, 0, 1.0)
        wheel_color = (0, 0, 0, 0.8)
        plot.plot(('wheel_bl_x', 'wheel_bl_y'),
                  type='polygon',
                  marker='dot',
                  marker_size=1.0,
                  face_color=wheel_color,
                  color=wheel_outline_color)

        plot.plot(('wheel_br_x', 'wheel_br_y'),
                  type='polygon',
                  marker='dot',
                  marker_size=1.0,
                  face_color=wheel_color,
                  color=wheel_outline_color)

        plot.plot(('wheel_tr_x', 'wheel_tr_y'),
                  type='polygon',
                  marker='dot',
                  marker_size=1.0,
                  face_color=wheel_color,
                  color=wheel_outline_color)

        plot.plot(('wheel_tl_x', 'wheel_tl_y'),
                  type='polygon',
                  marker='dot',
                  marker_size=1.0,
                  face_color=wheel_color,
                  color=wheel_outline_color)
        return plot

    def plot_velocity(self):
        origin_x = self.size[0] / 2
        origin_y = self.size[1] / 2
        self.plot_arrow((origin_x, origin_y), (origin_x, origin_y))
        pass

    def plot_arrow(self,
                   from_point,
                   to_point,
                   color=(1.0, 1.0, 0.0, 0.1),
                   label="arrow"):
        """
        Plot an arrow between two points
        :param from_point: The starting point of the arrow
        :param to_point: The ending point of the arrow
        :param color The color of the arrow
        :return:
        """
        if to_point == from_point:
            return

        # compute the length of the arrow and angle with the horizontal
        r = np.sqrt(
            pow((to_point[0] - from_point[0]), 2) +
            pow((to_point[1] - from_point[1]), 2))
        theta = np.arccos((to_point[0] - from_point[0]) / r)

        # length adjustment for arrow side points
        r_factor = 0.70 + 0.15 * (r / 212.5)

        # angle offset for arrow side points
        angle = 0.25 - 0.125 * (r / 212.5)

        # Compute where the side points should be drawn
        pos_neg = 1 if (from_point[1] < to_point[1]) else -1
        x1 = from_point[0] + r_factor * r * np.cos(theta - angle)
        x2 = from_point[0] + r_factor * r * np.cos(theta + angle)
        y1 = from_point[1] + pos_neg * r_factor * r * np.sin(theta - angle)
        y2 = from_point[1] + pos_neg * r_factor * r * np.sin(theta + angle)

        update = True if f'{label}_x' in self.plot.data.arrays.keys(
        ) else False

        # Update middle line segment
        self.plot.data.set_data(f'{label}_x', [from_point[0], to_point[0]])
        self.plot.data.set_data(f'{label}_y', [from_point[1], to_point[1]])

        # Draw arrowhead
        self.plot.data.set_data(f'{label}_arrowhead_x',
                                [to_point[0], x1, x2, to_point[0]])
        self.plot.data.set_data(f'{label}_arrowhead_y',
                                [to_point[1], y1, y2, to_point[1]])

        if not update:
            self.plot.plot(
                (f'{label}_x', f'{label}_y'),
                type='line',
                marker='dot',
                line_width=2.0,
                face_color=color,
                color=color,
            )

            self.plot.plot(
                (f'{label}_arrowhead_x', f'{label}_arrowhead_y'),
                type='polygon',
                marker='dot',
                line_width=1.0,
                edge_color=color,
                face_color=color,
            )