Automatic paddings in Chaco? - python

Is it possible to make chaco plot automatically show full output and not hiding the parts of ticks and labels? E.g. this is the output of standard example:
from chaco.api import ArrayPlotData, Plot
from enable.component_editor import ComponentEditor
from traits.api import HasTraits, Instance
from traitsui.api import View, Item
class MyPlot(HasTraits):
plot = Instance(Plot)
traits_view = View(Item('plot', editor = ComponentEditor(), show_label = False),
width = 500, height = 500, resizable = True)
def __init__(self, x, y, *args, **kw):
super(MyPlot, self).__init__(*args, **kw)
plotdata = ArrayPlotData(x=x,y=y)
plot = Plot(plotdata)
plot.plot(("x","y"), type = "line", color = "blue")
self.plot = plot
import numpy as np
x = np.linspace(-300,300,10000)
y = np.sin(x)*x**3
lineplot = MyPlot(x,y)
lineplot.configure_traits()
As you see the part of tick labels are hidden.. the only thing I can do is to manually adjust left padding of the plot. But this becomes extremely incovinient when you plot different data and different scales or fonts with the plot in application. Is it possible somehow to make padding automatically adjusted to include ALL related info?
UPD.: I've found ensure_labels_bounded property for the axis, but seems it has no effect.

Chaco does not support advanced layout features like these. If you use Chaco, you should use it for its speed, not for nice graphs or features. That being said, here's a version as close as I could get. It requires you to re-size the window with the mouse at least once for the padding correction to take place. Maybe you can find a way to refresh the window without having to manually resize it, I didn't have any luck with that. Anyways hope that gets you on the right track.
from chaco.api import ArrayPlotData, Plot
from enable.component_editor import ComponentEditor
from traits.api import HasTraits, Instance
from traitsui.api import View, Item
class MyPlot(HasTraits):
plot = Instance(Plot)
traits_view = View(Item('plot', editor = ComponentEditor(), show_label = False),
width = 500, height = 500, resizable = True)
def __init__(self, x, y, *args, **kw):
super(MyPlot, self).__init__(*args, **kw)
plotdata = ArrayPlotData(x=x,y=y)
plot = Plot(plotdata, padding=25)
plot.plot(("x","y"), type = "line", color = "blue", name='abc')
self.plot = plot
# watch for changes to the bounding boxes of the tick labels
self.plot.underlays[2].on_trait_change(self._update_size, '_tick_label_bounding_boxes')
self.plot.underlays[3].on_trait_change(self._update_size, '_tick_label_bounding_boxes')
def _update_size(self):
if len(self.plot.underlays[2]._tick_label_bounding_boxes) > 0:
self.plot.padding_bottom = int(np.amax(np.array(self.plot.underlays[2]._tick_label_bounding_boxes),0)[1]+8+4)
if len(self.plot.underlays[3]._tick_label_bounding_boxes) > 0:
self.plot.padding_left = int(np.amax(np.array(self.plot.underlays[3]._tick_label_bounding_boxes),0)[0]+8+4)
import numpy as np
x = np.linspace(-300,300,10000)
y = np.sin(x)*x**3
lineplot = MyPlot(x,y)
lineplot.configure_traits()

Related

In python bokeh how to modify the field of a fill_color interactively without js?

I am trying to use bokeh to plot the iris data and modify the fill color of the circles interactively but I am running into a problem. I call the plot and the circle with the following:
plot = figure(plot_height=600, plot_width=1000, title="Iris Data",
x_axis_label = 'Sepal length (cm)',
y_axis_label = 'Sepal width (cm)',
tools = "crosshair, pan, reset, save, wheel_zoom")
plot_circle = plot.circle(x='sepal_length', y='sepal_width', source=source,
line_color=None, fill_color={'field':'petal_width','transform':color_mapper},
size='size', fill_alpha = 0.2)
which works but when I try to add the interactivity in the call back it is not clear to me how to modify the 'field' parameter in the fill_color argument to circle. I have tried this:
def update_bubble_color(attrname, old, new):
if new=='petal_width':
color_mapper.low = min(flowers['petal_width'])
color_mapper.high = max(flowers['petal_width'])
fill_color.field='petal_width'
return
if new=='petal_length':
color_mapper.low = min(flowers['petal_length'])
color_mapper.high = max(flowers['petal_length'])
fill_color.field='petal_length'
return
select_bubble_color.on_change('value', update_bubble_color)
the color mapper limits are handled correctly but the colors are not scaled according to the new choice. When I attempt to change it to petal_length with fill_color.field='petal_length' I get an "'name 'fill_color' is not defined" error.
Any help greatly appreciated!
Full code below for reference
import numpy as np
from bokeh.io import curdoc
from bokeh.layouts import row, column
from bokeh.models import ColumnDataSource, LinearColorMapper
from bokeh.models.widgets import Select
from bokeh.plotting import figure
# Load Data
from bokeh.sampledata.iris import flowers
# Global constants (even if python dies not like it)
min_bubble_size = 10
max_bubble_size = 90
def get_scaled_size(vector):
min_vector = min(vector)
max_vector = max(vector)
scaling = (max_bubble_size-min_bubble_size)/(max_vector-min_vector)
scaled_size = [ scaling*(item-min_vector) + min_bubble_size for item in vector]
return scaled_size
# Color Mapper
color_mapper = LinearColorMapper(palette='Inferno256',
low = min(flowers['petal_width']),
high = max(flowers['petal_width']) )
# Define source
flowers['size'] = get_scaled_size(flowers['petal_length'])
source = ColumnDataSource(flowers)
# Set up plot
plot = figure(plot_height=600, plot_width=1000, title="Iris Data",
x_axis_label = 'Sepal length (cm)',
y_axis_label = 'Sepal width (cm)',
tools = "crosshair, pan, reset, save, wheel_zoom")
plot_circle = plot.circle(x='sepal_length', y='sepal_width', source=source,
line_color=None, fill_color={'field':'petal_width','transform':color_mapper},
size='size', fill_alpha = 0.2)
# Set up widgets
select_bubble_size = Select(title ='Bubble size by', value='petal_width',
options = ['petal_width','petal_length'],
width = 200)
select_bubble_color = Select(title ='Bubble color by', value='petal_width',
options = ['petal_width', 'petal_length'],
width = 200)
# Colorbar
from bokeh.models import ColorBar
bar = ColorBar(color_mapper=color_mapper,location=(0,0))
plot.add_layout(bar, 'left')
# Set up callbacks=
# Bubble size call back
def update_bubble_size(attrname, old, new):
if new=='petal_width':
source.data['size'] = get_scaled_size(flowers['petal_width'])
return
if new=='petal_length':
source.data['size'] = get_scaled_size(flowers['petal_length'])
return
select_bubble_size.on_change('value', update_bubble_size)
# bubble color call back
def update_bubble_color(attrname, old, new):
if new=='petal_width':
color_mapper.low = min(flowers['petal_width'])
color_mapper.high = max(flowers['petal_width'])
fill_color.field='petal_width'
return
if new=='petal_length':
color_mapper.low = min(flowers['petal_length'])
color_mapper.high = max(flowers['petal_length'])
fill_color.field='petal_length'
return
select_bubble_color.on_change('value', update_bubble_color)
# Set up layouts and add to document
curdoc().add_root(column(plot, row(select_bubble_size,select_bubble_color), width=800))
curdoc().title = "Iris Data"
fill_color is a property of the glyph, you will need to access it through the glyph:
plot_circle.glyph.fill_color
In your script there is not free variable fill_color anywhere, which is the source of the NameError.

Not all data are displayed by bar graph with chaco

With creating simple bar plot with chaco strange behavior is occured: half of plot is filled with line_color, another one with fill_color and the last one is not drawn at all (view screenshot). It is expected that data from 0 to 7000 for x and from 0 to 150 for y should be displayed and filled with line_color. With using small values it works fine (for example using 50 instead of 150 for y values). Is there an explanation of such behavior? How it can be solved?
Code below demonstrates the problem:
from enable.api import ComponentEditor
from traits.api import Instance, HasStrictTraits
from traitsui.api import View, UItem
from chaco.api import Plot, ArrayPlotData
class TestPlot(HasStrictTraits):
plot = Instance(Plot)
traits_view = View(UItem('plot', editor=ComponentEditor()),
width=1000, height=800, resizable=True,)
def __init__(self, **kw):
super(TestPlot, self).__init__(**kw)
plot_data = ArrayPlotData(x=list(xrange(0, 7000)), y=[150] * 7000)
self.plot = Plot(plot_data)
self.plot.plot(('x', 'y'), type='bar', line_color="gray", fill_color="lightgray")
self.plot.index_mapper.range.set(low=0 - 150, high=8000 + 50)
self.plot.value_mapper.range.set(low=0 - 50, high=100 + 100)
test = TestPlot()
if __name__ == "__main__":
test.configure_traits()

Latex in Chaco text?

Is it possible to create a chaco plot with latex text? For example, if we wanted latex symbols in the title of this exampe:
from traits.api import HasTraits, Instance
from traitsui.api import View, Item
from chaco.api import Plot, ArrayPlotData
from enable.component_editor import ComponentEditor
from numpy import linspace, sin
class LinePlot(HasTraits):
plot = Instance(Plot)
traits_view = View(
Item('plot',editor=ComponentEditor(), show_label=False),
width=500, height=500, resizable=True, title="Chaco Plot")
def __init__(self):
super(LinePlot, self).__init__()
x = linspace(-14, 14, 100)
y = sin(x) * x**3
plotdata = ArrayPlotData(x=x, y=y)
plot = Plot(plotdata)
plot.plot(("x", "y"), type="line", color="blue")
plot.title = "sin(x) * x^3"
self.plot = plot
if __name__ == "__main__":
LinePlot().configure_traits()
I tried replacing title with $sin(x)^3$ to no avail, and wondered if this was possible? Screenshot below:
No, it is not (it is a matplotlib feature). But you could try to use unicode symbols for easy cases.

Python/Matplotlib/Pyside Fast Timetrace scrolling

I have large time-traces that must be inspected visually, so I need a fast scrolling tool.
How can I achieve the fastest Maplotlib/Pyside scrolling?
Right know, I added a PySide scroll-bar to a MPL figure and update the x-range of the plot with set_xlim() method. This is not fast enough especially because in the final application I have at least 8 time-traces in different subplots that must all scroll together. A figure of the plot is attached.
Is there room for improvement?
Here I attach the demo code that demonstrate the relatively low scrolling. It's long but it's almost all boiler-plate code. The interesting bit (that needs improvement) is in xpos_changed() method where the plot xlimits are changed.
EDIT: Below I incorporated some micro-optimizations suggested by tcaswell, but the update speed is not improved.
from PySide import QtGui, QtCore
import pylab as plt
import numpy as np
N_SAMPLES = 1e6
def test_plot():
time = np.arange(N_SAMPLES)*1e-3
sample = np.random.randn(N_SAMPLES)
plt.plot(time, sample, label="Gaussian noise")
plt.title("1000s Timetrace \n (use the slider to scroll and the spin-box to set the width)")
plt.xlabel('Time (s)')
plt.legend(fancybox=True)
q = ScrollingToolQT(plt.gcf(), scroll_step=10)
return q # WARNING: it's important to return this object otherwise
# python will delete the reference and the GUI will not respond!
class ScrollingToolQT(object):
def __init__(self, fig, scroll_step=10):
# Setup data range variables for scrolling
self.fig = fig
self.scroll_step = scroll_step
self.xmin, self.xmax = fig.axes[0].get_xlim()
self.width = 1 # axis units
self.pos = 0 # axis units
self.scale = 1e3 # conversion betweeen scrolling units and axis units
# Save some MPL shortcuts
self.ax = self.fig.axes[0]
self.draw = self.fig.canvas.draw
#self.draw_idle = self.fig.canvas.draw_idle
# Retrive the QMainWindow used by current figure and add a toolbar
# to host the new widgets
QMainWin = fig.canvas.parent()
toolbar = QtGui.QToolBar(QMainWin)
QMainWin.addToolBar(QtCore.Qt.BottomToolBarArea, toolbar)
# Create the slider and spinbox for x-axis scrolling in toolbar
self.set_slider(toolbar)
self.set_spinbox(toolbar)
# Set the initial xlimits coherently with values in slider and spinbox
self.ax.set_xlim(self.pos,self.pos+self.width)
self.draw()
def set_slider(self, parent):
self.slider = QtGui.QSlider(QtCore.Qt.Horizontal, parent=parent)
self.slider.setTickPosition(QtGui.QSlider.TicksAbove)
self.slider.setTickInterval((self.xmax-self.xmin)/10.*self.scale)
self.slider.setMinimum(self.xmin*self.scale)
self.slider.setMaximum((self.xmax-self.width)*self.scale)
self.slider.setSingleStep(self.width*self.scale/4.)
self.slider.setPageStep(self.scroll_step*self.width*self.scale)
self.slider.setValue(self.pos*self.scale) # set the initial position
self.slider.valueChanged.connect(self.xpos_changed)
parent.addWidget(self.slider)
def set_spinbox(self, parent):
self.spinb = QtGui.QDoubleSpinBox(parent=parent)
self.spinb.setDecimals(3)
self.spinb.setRange(0.001,3600.)
self.spinb.setSuffix(" s")
self.spinb.setValue(self.width) # set the initial width
self.spinb.valueChanged.connect(self.xwidth_changed)
parent.addWidget(self.spinb)
def xpos_changed(self, pos):
#pprint("Position (in scroll units) %f\n" %pos)
pos /= self.scale
self.ax.set_xlim(pos, pos+self.width)
self.draw()
def xwidth_changed(self, width):
#pprint("Width (axis units) %f\n" % step)
if width <= 0: return
self.width = width
self.slider.setSingleStep(self.width*self.scale/5.)
self.slider.setPageStep(self.scroll_step*self.width*self.scale)
old_xlim = self.ax.get_xlim()
self.xpos_changed(old_xlim[0]*self.scale)
if __name__ == "__main__":
q = test_plot()
plt.show()
As requested in the comments, here is a pyqtgraph demo which scrolls two large traces together (via mouse).
The documentation isn't complete for the pyqtgraph project but there are some good examples you can view with python -m pyqtgraph.examples which should point you in the right direction. The crosshair.py example might be particularly interesting for you.
If you go with pyqtgraph, connect your slider widget to the setXRange method in the last line of this demo.
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
import numpy as np
app = QtGui.QApplication([])
win = pg.GraphicsWindow()
x = np.arange(1e5)
y1 = np.random.randn(x.size)
y2 = np.random.randn(x.size)
p1 = win.addPlot(x=x, y=y1, name='linkToMe')
p1.setMouseEnabled(x=True, y=False)
win.nextRow()
p2 = win.addPlot(x=x, y=y2)
p2.setXLink('linkToMe')
p1.setXRange(2000,3000)
This seems a bit faster/more responsive:
from PySide import QtGui, QtCore
import pylab as plt
import numpy as np
N_SAMPLES = 1e6
def test_plot():
time = np.arange(N_SAMPLES)*1e-3
sample = np.random.randn(N_SAMPLES)
plt.plot(time, sample, label="Gaussian noise")
plt.legend(fancybox=True)
plt.title("Use the slider to scroll and the spin-box to set the width")
q = ScrollingToolQT(plt.gcf())
return q # WARNING: it's important to return this object otherwise
# python will delete the reference and the GUI will not respond!
class ScrollingToolQT(object):
def __init__(self, fig):
# Setup data range variables for scrolling
self.fig = fig
self.xmin, self.xmax = fig.axes[0].get_xlim()
self.step = 1 # axis units
self.scale = 1e3 # conversion betweeen scrolling units and axis units
# Retrive the QMainWindow used by current figure and add a toolbar
# to host the new widgets
QMainWin = fig.canvas.parent()
toolbar = QtGui.QToolBar(QMainWin)
QMainWin.addToolBar(QtCore.Qt.BottomToolBarArea, toolbar)
# Create the slider and spinbox for x-axis scrolling in toolbar
self.set_slider(toolbar)
self.set_spinbox(toolbar)
# Set the initial xlimits coherently with values in slider and spinbox
self.set_xlim = self.fig.axes[0].set_xlim
self.draw_idle = self.fig.canvas.draw_idle
self.ax = self.fig.axes[0]
self.set_xlim(0, self.step)
self.fig.canvas.draw()
def set_slider(self, parent):
# Slider only support integer ranges so use ms as base unit
smin, smax = self.xmin*self.scale, self.xmax*self.scale
self.slider = QtGui.QSlider(QtCore.Qt.Horizontal, parent=parent)
self.slider.setTickPosition(QtGui.QSlider.TicksAbove)
self.slider.setTickInterval((smax-smin)/10.)
self.slider.setMinimum(smin)
self.slider.setMaximum(smax-self.step*self.scale)
self.slider.setSingleStep(self.step*self.scale/5.)
self.slider.setPageStep(self.step*self.scale)
self.slider.setValue(0) # set the initial position
self.slider.valueChanged.connect(self.xpos_changed)
parent.addWidget(self.slider)
def set_spinbox(self, parent):
self.spinb = QtGui.QDoubleSpinBox(parent=parent)
self.spinb.setDecimals(3)
self.spinb.setRange(0.001, 3600.)
self.spinb.setSuffix(" s")
self.spinb.setValue(self.step) # set the initial width
self.spinb.valueChanged.connect(self.xwidth_changed)
parent.addWidget(self.spinb)
def xpos_changed(self, pos):
#pprint("Position (in scroll units) %f\n" %pos)
# self.pos = pos/self.scale
pos /= self.scale
self.set_xlim(pos, pos + self.step)
self.draw_idle()
def xwidth_changed(self, xwidth):
#pprint("Width (axis units) %f\n" % step)
if xwidth <= 0: return
self.step = xwidth
self.slider.setSingleStep(self.step*self.scale/5.)
self.slider.setPageStep(self.step*self.scale)
old_xlim = self.ax.get_xlim()
self.xpos_changed(old_xlim[0] * self.scale)
# self.set_xlim(self.pos,self.pos+self.step)
# self.fig.canvas.draw()
if __name__ == "__main__":
q = test_plot()
plt.show()

Why can't I use app.MainLoop() with iPython?

I'd like a rectangle to appear every time the user clicks on the line. I've gotten this to work procedurally like in this example: http://www.daniweb.com/software-development/python/code/216648 but once I implemented iPython compatibility and started using classes, I could no longer use the app.MainLoop() without the program crashing. How do I refresh a wx.Frame object from inside a class? Why does the self.figure.canvas.draw() not work?
The code is below. Open ipython with the -pylab option. x = [-10,10] and y = x are decent parameters for this problem.
import wx
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigCanv
from pylab import *
import IPython.ipapi
ip = IPython.ipapi.get()
import sys
class MainCanvas(wx.Frame):
""" Set up the canvas and plot on which the rectangle will lie """
def __init__(self, *args):
wx.Frame.__init__(self,None,-1, size=(550,350))
self.x = args[0]
self.y = args[1]
self.figure = plt.figure()
self.axes = self.figure.add_subplot(111)
self.axes.plot(*args)
self.line, = self.axes.plot(self.x, self.y, picker = 3,
visible = False)
self.canvas = FigCanv(self, -1, self.figure)
self.rect = patches.Rectangle((0, 0), 2, 2, visible=True)
self.axes.add_patch(self.rect)
self.figure.canvas.mpl_connect('pick_event', self.onPick)
def onPick(self, event):
""" Move rectangle to last click on line """
self.rect.set_x(event.mouseevent.xdata)
self.rect.set_y(event.mouseevent.ydata)
self.rect.set_visible(True)
print "rect x: ", self.rect.get_x()
print "rect y: ", self.rect.get_y()
self.figure.canvas.draw()
def run_this_plot(self, arg_s=''):
""" Run in iPython
Examples
In [1]: import demo
In [2]: runplot x y <z>
Where x, y, and z are numbers of any type
"""
args = []
for arg in arg_s.split():
try:
args.append(self.shell.user_ns[arg])
except KeyError:
raise ValueError("Invalid argument: %r" % arg)
mc = MainCanvas(*args)
ip.expose_magic("runplot", run_this_plot)
Thanks!
--Erin
It seems likely that matplotlib is set to use a backend other than wx. Try either setting this in the matplotlibrc file, or it can be set in the program (but it must be set before matplotlib is imported). Instructions are here.

Categories