matplotlib - Keyboard shortcuts to pan left/right/up/down - event-handling

In matplotlib, is there a keyboard alternative to dragging a plot with the left mouse click for panning in the four directions?
Reason is, apart from the fact that it seems like an obvious keyboard shortcut, I'm printing the xdata value every time I left-click. It would be useful to drag without clicking the plot.
Otherwise, is there a way to connect to a double-click event? That way I could print my value only on that event. For the moment I have solved by printing on right-click.

def on_dbl_click(event):
if event.dblclick:
print event.x, event.y
fig, ax = plt.subplots(1, 1)
fig.canvas.mpl_connect('button_press_event', on_dbl_click)
You just need to test if the event has dblcilck set (doc)

This is how I added a "ctrl+c" shortcut to a matplotlib figure. Any figure created with the function below will copy a picture of the figure to the clipboard through "ctrl+c".
import matplotlib.pyplot as plt
from PyQt4 import QtGui
def figure(num = None):
"""Creates and returns a matplotlib figure and adds a 'ctrl+c' shortcut that copies figure to clipboard"""
def on_ctrl_c_click(event):
if event.key == 'ctrl+c' or event.key == 'ctrl+C':
QtGui.QApplication.clipboard().setPixmap(QtGui.QPixmap.grabWidget(fig.canvas))
fig = plt.figure(num)
fig.canvas.mpl_connect('key_press_event', on_ctrl_c_click)
return fig

Here's a simple function to allow panning in all 4 directions using the arrow keys.
import matplotlib.pyplot as plt
def pan_nav(event):
ax_tmp = plt.gca()
if event.key == 'left':
lims = ax_tmp.get_xlim()
adjust = (lims[1] - lims[0]) * 0.9
ax_tmp.set_xlim((lims[0] - adjust, lims[1] - adjust))
plt.draw()
elif event.key == 'right':
lims = ax_tmp.get_xlim()
adjust = (lims[1] - lims[0]) * 0.9
ax_tmp.set_xlim((lims[0] + adjust, lims[1] + adjust))
plt.draw()
elif event.key == 'down':
lims = ax_tmp.get_ylim()
adjust = (lims[1] - lims[0]) * 0.9
ax_tmp.set_ylim((lims[0] - adjust, lims[1] - adjust))
plt.draw()
elif event.key == 'up':
lims = ax_tmp.get_ylim()
adjust = (lims[1] - lims[0]) * 0.9
ax_tmp.set_ylim((lims[0] + adjust, lims[1] + adjust))
plt.draw()
fig = plt.figure()
fig.canvas.mpl_connect('key_press_event', pan_nav)
plt.plot(1, 1, 'ko') # plot something

Related

Change dimension of cube using MatrixTransform in vispy

import numpy as np
from vispy import app, scene
from vispy.visuals import transforms
canvas = scene.SceneCanvas(keys='interactive', show=True)
vb = canvas.central_widget.add_view()
vb.camera = 'turntable'
vb.camera.rect = (-10, -10, 20, 20)
box = scene.visuals.Box(width=1, height=2, depth=3, color=(0, 0, 1, 0.3),
edge_color='green')
vb.add(box)
# Define a scale and translate transformation :
box.transform = transforms.STTransform(translate=(0., 0., 0.),
scale=(1., 1., 1.))
#canvas.events.key_press.connect
def on_key_press(ev):
tr = np.array(box.transform.translate)
sc = np.array(box.transform.scale)
if ev.text in '+':
tr[0] += .1
elif ev.text == '-':
tr[0] -= .1
elif ev.text == '(':
sc[0] += .1
elif ev.text == ')':
sc[0] -= .1
box.transform.translate = tr
box.transform.scale = sc
print('Translate (x, y, z): ', list(tr),
'\nScale (x, y, z): ', list(sc), '\n')
if __name__ == '__main__':
import sys
if sys.flags.interactive != 1:
app.run()
In the above code if I add a MatrixTransform, and rotate the cube and then apply scaling, the cube becomes a Rhombus
What I would like to achieve is to rotate the cube in a canvas and scale it only in X direction, without other dimensions getting affected
I think we covered this in a vispy repository bug report. The solution was to swap the order of the matrix transform and st transform in your multiplication. If this is still an issue, could you provide your code when you are using the matrix and we'll continue debugging this. Thanks.

cartopy: map overlay on NOAA APT image

I am working on a project trying to decode NOAA APT images, so far I reached the stage where I can get the images from raw IQ recordings from RTLSDRs. Here is one of the decoded images,
Decoded NOAA APT image this image will be used as input for the code (seen as m3.png here on)
Now I am working on overlaying map boundaries on the image (Note: Only on the left half part of the above image)
We know, the time at which the image was captured and the satellite info: position, direction etc. So, I used the position of the satellite to get the center of map projection and and direction of satellite to rotate the image appropriately.
First I tried in Basemap, here is the code
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import numpy as np
from scipy import ndimage
im = plt.imread('m3.png')
im = im[:,85:995] # crop only the first part of whole image
rot = 198.3913296679117 # degrees, direction of sat movement
center = (50.83550180700588, 16.430852851867176) # lat long
rotated_img = ndimage.rotate(im, rot) # rotate image
w = rotated_img.shape[1]*4000*0.81 # in meters, spec says 4km per pixel, but I had to make it 81% less to get better image
h = rotated_img.shape[0]*4000*0.81 # in meters, spec says 4km per pixel, but I had to make it 81% less to get better image
m = Basemap(projection='cass',lon_0 = center[1],lat_0 = center[0],width = w,height = h, resolution = "i")
m.drawcoastlines(color='yellow')
m.drawcountries(color='yellow')
im = plt.imshow(rotated_img, cmap='gray', extent=(*plt.xlim(), *plt.ylim()))
plt.show()
I got this image as a result, which seems pretty good
I wanted to move the code to Cartopy as it is easier to install and is actively being developed. I was unable to find a similar way to set boundaries i.e. width and height in meters. So, I modified most similar example. I found a function which would add meters to longs and lats and used that to set the boundaries.
Here is the code in Cartopy,
import matplotlib.pyplot as plt
import numpy as np
import cartopy.crs as ccrs
from scipy import ndimage
import cartopy.feature
im = plt.imread('m3.png')
im = im[:,85:995] # crop only the first part of whole image
rot = 198.3913296679117 # degrees, direction of sat movement
center = (50.83550180700588, 16.430852851867176) # lat long
def add_m(center, dx, dy):
# source: https://stackoverflow.com/questions/7477003/calculating-new-longitude-latitude-from-old-n-meters
new_latitude = center[0] + (dy / 6371000.0) * (180 / np.pi)
new_longitude = center[1] + (dx / 6371000.0) * (180 / np.pi) / np.cos(center[0] * np.pi/180)
return [new_latitude, new_longitude]
fig = plt.figure()
img = ndimage.rotate(im, rot)
dx = img.shape[0]*4000/2*0.81 # in meters
dy = img.shape[1]*4000/2*0.81 # in meters
leftbot = add_m(center, -1*dx, -1*dy)
righttop = add_m(center, dx, dy)
img_extent = (leftbot[1], righttop[1], leftbot[0], righttop[0])
ax = plt.axes(projection=ccrs.PlateCarree())
ax.imshow(img, origin='upper', cmap='gray', extent=img_extent, transform=ccrs.PlateCarree())
ax.coastlines(resolution='50m', color='yellow', linewidth=1)
ax.add_feature(cartopy.feature.BORDERS, linestyle='-', edgecolor='yellow')
plt.show()
Here is the result from Cartopy, it is not as good as the result from Basemap.
I have following questions:
I found it impossible to rotate the map instead of the image, in
both basemap and cartopy. Hence I resorted to rotating the image, is
there a way to rotate the map?
How do I improve the output of cartopy? I think it is the way in
which I am calculating the extent a problem. Is there a way I can
provide meters to set the boundaries of the image?
Is there a better way to do what I am trying to do? any projection that are specific to these kind of applications?
I am adjusting the scale (the part where I decide the number of kms per pixel) manually, is there a way to do this based
on
satellite's altitude?
Any sort of input would be highly appreciated. Thank you so much for your time!
If you are interested you can find the project here.
As far as I can see, there is no ability for the underlying Proj.4 to define satellite projections with rotated perspectives (happy to be shown otherwise - I'm no expert!) (note: perhaps via ob_tran?). This is the main reason you can't do this in "native" coordinates/orientation with Basemap or Cartopy.
This question really comes down to a georeferencing problem, to which I couldn't find enough information in places like https://www.cder.dz/download/Art7-1_1.pdf.
My solution is entirely a fudge, but does get you quite close to referencing this image. I double the fudge factors are actually universal, which is a bit of an issue if you want to write general-purpose code.
Some of the fudges I had to make (trial-and-error):
adjust the satellite bearing by 3.2 degrees
adjust where the image centre is by moving it along the satellite trajectory by 10km
adjust where the image centre is by moving it perpendicularly along the satellite trajectory by 10km
scale the x and y pixel sizes by 0.62 and 0.65 respectively
use the "near-sided perspective" projection at an unrealistic satellite_height
The result is what appears to be a relatively well registered image, but as I say, seems unlikely to be generally applicable to all images received:
The code to produce this image (fairly involved, but complete):
import urllib.request
urllib.request.urlretrieve('https://i.stack.imgur.com/UBIuA.jpg', 'm3.jpg')
import matplotlib.pyplot as plt
import numpy as np
import cartopy.crs as ccrs
from scipy import ndimage
import cartopy.feature
im = plt.imread('m3.jpg')
im = im[:,85:995] # crop only the first part of whole image
rot = 198.3913296679117 # degrees, direction of sat movement
center = (50.83550180700588, 16.430852851867176) # lat long
import numpy as np
from cartopy.geodesic import Geodesic
import matplotlib.transforms as mtransforms
from matplotlib.axes import Axes
tweaked_rot = rot - 3.2
geod = Geodesic()
# Move the center along the trajectory of the satellite by 10KM
f = np.array(
geod.direct([center[1], center[0]],
180 - tweaked_rot,
10000))
tweaked_center = f[0, 0], f[0, 1]
# Move the satellite perpendicular from its proposed trajectory by 15KM
f = np.array(
geod.direct([tweaked_center[0], tweaked_center[1]],
180 - tweaked_rot + 90,
10000))
tweaked_center = f[0, 0], f[0, 1]
data_crs = ccrs.NearsidePerspective(
central_latitude=tweaked_center[1],
central_longitude=tweaked_center[0],
)
# Compute the center in data_crs coordinates.
center_lon_lat_ortho = data_crs.transform_point(
tweaked_center[0], tweaked_center[1], ccrs.Geodetic())
# Define the affine rotation in terms of matplotlib transforms.
rotation = mtransforms.Affine2D().rotate_deg_around(
center_lon_lat_ortho[0], center_lon_lat_ortho[1], tweaked_rot)
# Some fudge factors. Sorry - there are entirely application specific,
# perhaps some reading of https://www.cder.dz/download/Art7-1_1.pdf
# would enlighten these... :(
ff_x, ff_y = 0.62, 0.65
ff_x = ff_y = 0.81
x_extent = im.shape[1]*4000/2 * ff_x
y_extent = im.shape[0]*4000/2 * ff_y
img_extent = [-x_extent, x_extent, -y_extent, y_extent]
fig = plt.figure(figsize=(10, 10))
ax = plt.axes(projection=data_crs)
ax.margins(0.02)
with ax.hold_limits():
ax.stock_img()
# Uing matplotlib's image transforms if the projection is the
# same as the map, otherwise we need to fall back to cartopy's
# (slower) image resampling algorithm
if ax.projection == data_crs:
transform = rotation + ax.transData
else:
transform = rotation + data_crs._as_mpl_transform(ax)
# Use the original Axes method rather than cartopy's GeoAxes.imshow.
mimg = Axes.imshow(ax, im, origin='upper', cmap='gray',
extent=img_extent, transform=transform)
lower_left = rotation.frozen().transform_point([-x_extent, -y_extent])
lower_right = rotation.frozen().transform_point([x_extent, -y_extent])
upper_left = rotation.frozen().transform_point([-x_extent, y_extent])
upper_right = rotation.frozen().transform_point([x_extent, y_extent])
plt.plot(lower_left[0], lower_left[1],
upper_left[0], upper_left[1],
upper_right[0], upper_right[1],
lower_right[0], lower_right[1],
marker='x', color='black',
transform=data_crs)
ax.coastlines(resolution='10m', color='yellow', linewidth=1)
ax.add_feature(cartopy.feature.BORDERS, linestyle='-', edgecolor='yellow')
sat_pos = np.array(geod.direct(tweaked_center, 180 - tweaked_rot,
np.linspace(-x_extent*2, x_extent*2, 50)))
with ax.hold_limits():
plt.plot(sat_pos[:, 0], sat_pos[:, 1], transform=ccrs.Geodetic(),
label='Satellite path')
plt.plot(tweaked_center, 'ob')
plt.legend()
As you can probably tell, I got a bit carried away with this question. It is a super interesting problem, but not really a cartopy/Basemap one per-say.
Hope that helps!

How to have 100% zoom factor withChaco

As an example, in chaco/examples/demo/basic/image_inspector.py, how to set the zoom factor such that 1 array point corresponds to 1 screen pixel (100% zoom). It seems that the ZoomTool methods (zoom_in, zoom_out, ...) only deal with zoom factor changes, not with absolute factor setting.
I would try something with plot.range2d.low, plot.range2d.high and plot.outer_bounds. The first two relate to data space, while the latter relates to the size of the picture area. By setting the limits of the data space using the picture area, you can map 1 pixel to 1 data unit. Here's an example, the interesting bit is in the _zoom_100_percent method:
import numpy as np
from chaco.api import Plot, ArrayPlotData
from chaco.tools.api import PanTool, ZoomTool
from enable.api import ComponentEditor
from traits.api import Button, HasTraits, Instance, on_trait_change
from traitsui.api import Item, View
class HundredPercentZoom(HasTraits):
plot = Instance(Plot)
zoom_button = Button('100% Zoom')
traits_view = View(
Item('plot', editor=ComponentEditor(), show_label=False),
'zoom_button',
width=800,
height=600,
)
def _plot_default(self):
t = np.linspace(0, 1000, 200)
y = 400 * (np.sin(t) + 0.1 * np.sin(t * 100))
plot = Plot(ArrayPlotData(t=t, y=y))
plot.plot(('t', 'y'))
plot.tools.append(PanTool(plot))
plot.tools.append(ZoomTool(plot))
return plot
#on_trait_change('zoom_button')
def _zoom_100_percent(self):
low = self.plot.range2d.low
bounds = self.plot.outer_bounds
print(bounds)
self.plot.range2d.high = (low[0] + bounds[0], low[1] + bounds[1])
if __name__ == "__main__":
hpz = HundredPercentZoom()
hpz.configure_traits()
I added a print statement in there so you can see that the plot area is different than the window area, which is 800x600. I also added a PanTool and ZoomTool, so you can pan around once zoomed in. You can go back to the orignal zoom state using the Escape key, as long as your plot has a ZoomTool.
The solution I have arrived to, starting from the original example image_inspector.py . A button allows to have a 100 % zoom factor around a point chosen as the zoom center.
All is in the _btn_fired method in class Demo.
There may still be a problem of 1 not being subtracted or added to some bounds or limits, as the button operation is not strictly involutive (a second press should not do anything) as it should.
Anything simpler?
#!/usr/bin/env python
"""
Demonstrates the ImageInspectorTool and overlay on a colormapped image
plot. The underlying plot is similar to the one in cmap_image_plot.py.
- Left-drag pans the plot.
- Mousewheel up and down zooms the plot in and out.
- Pressing "z" brings up the Zoom Box, and you can click-drag a rectangular
region to zoom. If you use a sequence of zoom boxes, pressing alt-left-arrow
and alt-right-arrow moves you forwards and backwards through the "zoom
history".
- Pressing "p" will toggle the display of the image inspector overlay.
"""
# Major library imports
from numpy import linspace, meshgrid, pi, sin, divide, multiply
# Enthought library imports
from enable.api import Component, ComponentEditor
from traits.api import HasTraits, Instance, Button, Float
from traitsui.api import Item, Group, View, HGroup
# Chaco imports
from chaco.api import ArrayPlotData, jet, Plot
from chaco.tools.api import PanTool, ZoomTool
from chaco.tools.image_inspector_tool import ImageInspectorTool, \
ImageInspectorOverlay
#===============================================================================
# # Create the Chaco plot.
#===============================================================================
def _create_plot_component():# Create a scalar field to colormap
xbounds = (-2*pi, 2*pi, 600)
ybounds = (-1.5*pi, 1.5*pi, 300)
xs = linspace(*xbounds)
ys = linspace(*ybounds)
x, y = meshgrid(xs,ys)
z = sin(x)*y
# Create a plot data obect and give it this data
pd = ArrayPlotData()
pd.set_data("imagedata", z)
# Create the plot
plot = Plot(pd)
img_plot = plot.img_plot("imagedata",
xbounds = xbounds[:2],
ybounds = ybounds[:2],
colormap=jet)[0]
# Tweak some of the plot properties
plot.title = "My First Image Plot"
plot.padding = 50
# Attach some tools to the plot
plot.tools.append(PanTool(plot))
zoom = ZoomTool(component=plot, tool_mode="box", always_on=False)
plot.overlays.append(zoom)
imgtool = ImageInspectorTool(img_plot)
img_plot.tools.append(imgtool)
overlay = ImageInspectorOverlay(component=img_plot, image_inspector=imgtool,
bgcolor="white", border_visible=True)
img_plot.overlays.append(overlay)
return plot
#===============================================================================
# Attributes to use for the plot view.
size = (800, 600)
title="Inspecting a Colormapped Image Plot"
#===============================================================================
# # Demo class that is used by the demo.py application.
#===============================================================================
class Demo(HasTraits):
plot = Instance(Component)
center_x = Float
center_y = Float
btn = Button('100 %')
def _btn_fired(self):
img_plot, = self.plot.plots['plot0']
zoom_center = (self.center_x, self.center_y)
# Size of plot in screen pixels
plot_size = img_plot.bounds
# Zoom center in screen space
zoom_center_screen, = img_plot.map_screen(zoom_center)
# Get actual bounds in data space
low, high = (img_plot.index_mapper.range.low,
img_plot.index_mapper.range.high)
# Get data space x and y units in terms of x and y array indices
sizes = [item.get_size() for item in img_plot.index.get_data()]
(min_x, min_y), (max_x, max_y) = img_plot.index.get_bounds()
unit = divide((max_x - min_x, max_y - min_y), sizes)
# Calculate new bounds
new_low = zoom_center - multiply(zoom_center_screen, unit)
new_high = new_low + multiply(plot_size, unit)
# Set new bounds
img_plot.index_mapper.range.set_bounds(new_low,new_high)
traits_view = View(
Group(
Item('plot', editor=ComponentEditor(size=size),
show_label=False),
HGroup('center_x', 'center_y', 'btn'),
orientation = "vertical"
),
resizable=True, title=title
)
def _plot_default(self):
return _create_plot_component()
demo = Demo()
if __name__ == "__main__":
demo.configure_traits()

Can I plot a colorbar for a bokeh heatmap?

Does bokeh have a simple way to plot the colorbar for a heatmap?
In this example it would be a strip illustrating how colors correspond to values.
In matlab, its called a 'colorbar' and looks like this:
UPDATE: This is now much easier: see
http://docs.bokeh.org/en/latest/docs/user_guide/annotations.html#color-bars
I'm afraid I don't have a great answer, this should be easier in Bokeh. But I have done something like this manually before.
Because I often want these off my plot, I make a new plot, and then assemble it together with something like hplot or gridplot.
There is an example of this here: https://github.com/birdsarah/pycon_2015_bokeh_talk/blob/master/washmap/washmap/water_map.py#L179
In your case, the plot should be pretty straight forward. If you made a datasource like this:
| value | color
| 1 | blue
.....
| 9 | red
Then you could do something like:
legend = figure(tools=None)
legend.toolbar_location=None
legend.rect(x=0.5, y='value', fill_color='color', width=1, height=1, source=source)
layout = hplot(main, legend)
show(legend)
However, this does rely on you knowing the colors that your values correspond to. You can pass a palette to your heatmap chart call - as shown here: http://docs.bokeh.org/en/latest/docs/gallery/cat_heatmap_chart.html so then you would be able to use that to construct the new data source from that.
I'm pretty sure there's at least one open issue around color maps. I know I just added one for off-plot legends.
Since other answers here seem very complicated, here an easily understandable piece of code that generates a colorbar on a bokeh heatmap.
import numpy as np
from bokeh.plotting import figure, show
from bokeh.models import LinearColorMapper, BasicTicker, ColorBar
data = np.random.rand(10,10)
color_mapper = LinearColorMapper(palette="Viridis256", low=0, high=1)
plot = figure(x_range=(0,1), y_range=(0,1))
plot.image(image=[data], color_mapper=color_mapper,
dh=[1.0], dw=[1.0], x=[0], y=[0])
color_bar = ColorBar(color_mapper=color_mapper, ticker= BasicTicker(),
location=(0,0))
plot.add_layout(color_bar, 'right')
show(plot)
Since the 0.12.3 version Bokeh has the ColorBar.
This documentation was very useful to me:
http://docs.bokeh.org/en/dev/docs/user_guide/annotations.html#color-bars
To do this I did the same as #birdsarah. As an extra tip though if you use the rect method as your colour map, then use the rect method once again in the colour bar and use the same source. The end result is that you can select sections of the colour bar and it also selects in your plot.
Try it out:
http://simonbiggs.github.io/electronfactors
Here is some code loosely based on birdsarah's response for generating a colorbar:
def generate_colorbar(palette, low=0, high=15, plot_height = 100, plot_width = 500, orientation = 'h'):
y = np.linspace(low,high,len(palette))
dy = y[1]-y[0]
if orientation.lower()=='v':
fig = bp.figure(tools="", x_range = [0, 1], y_range = [low, high], plot_width = plot_width, plot_height=plot_height)
fig.toolbar_location=None
fig.xaxis.visible = None
fig.rect(x=0.5, y=y, color=palette, width=1, height = dy)
elif orientation.lower()=='h':
fig = bp.figure(tools="", y_range = [0, 1], x_range = [low, high],plot_width = plot_width, plot_height=plot_height)
fig.toolbar_location=None
fig.yaxis.visible = None
fig.rect(x=y, y=0.5, color=palette, width=dy, height = 1)
return fig
Also, if you are interested in emulating matplot lib colormaps, try using this:
import matplotlib as mpl
def return_bokeh_colormap(name):
cm = mpl.cm.get_cmap(name)
colormap = [rgb_to_hex(tuple((np.array(cm(x))*255).astype(np.int))) for x in range(0,cm.N)]
return colormap
def rgb_to_hex(rgb):
return '#%02x%02x%02x' % rgb[0:3]
This is high on my wish list as well. It would also need to automatically adjust the range if the plotted data changed (e.g. moving through one dimension of a 3D data set). The code below does something which people might find useful. The trick is to add an extra axis to the colourbar which you can control through a data source when the data changes.
import numpy
from bokeh.plotting import Figure
from bokeh.models import ColumnDataSource, Plot, LinearAxis
from bokeh.models.mappers import LinearColorMapper
from bokeh.models.ranges import Range1d
from bokeh.models.widgets import Slider
from bokeh.models.widgets.layouts import VBox
from bokeh.core.properties import Instance
from bokeh.palettes import RdYlBu11
from bokeh.io import curdoc
class Colourbar(VBox):
plot = Instance(Plot)
cbar = Instance(Plot)
power = Instance(Slider)
datasrc = Instance(ColumnDataSource)
cbarrange = Instance(ColumnDataSource)
cmap = Instance(LinearColorMapper)
def __init__(self):
self.__view_model__ = "VBox"
self.__subtype__ = "MyApp"
super(Colourbar,self).__init__()
numslices = 6
x = numpy.linspace(1,2,11)
y = numpy.linspace(2,4,21)
Z = numpy.ndarray([numslices,y.size,x.size])
for i in range(numslices):
for j in range(y.size):
for k in range(x.size):
Z[i,j,k] = (y[j]*x[k])**(i+1) + y[j]*x[k]
self.power = Slider(title = 'Power',name = 'Power',start = 1,end = numslices,step = 1,
value = round(numslices/2))
self.power.on_change('value',self.inputchange)
z = Z[self.power.value]
self.datasrc = ColumnDataSource(data={'x':x,'y':y,'z':[z],'Z':Z})
self.cmap = LinearColorMapper(palette = RdYlBu11)
r = Range1d(start = z.min(),end = z.max())
self.cbarrange = ColumnDataSource(data = {'range':[r]})
self.plot = Figure(title="Colourmap plot",x_axis_label = 'x',y_axis_label = 'y',
x_range = [x[0],x[-1]],y_range=[y[0],y[-1]],
plot_height = 500,plot_width = 500)
dx = x[1] - x[0]
dy = y[1] - y[0]
self.plot.image('z',source = self.datasrc,x = x[0]-dx/2, y = y[0]-dy/2,
dw = [x[-1]-x[0]+dx],dh = [y[-1]-y[0]+dy],
color_mapper = self.cmap)
self.generate_colorbar()
self.children.append(self.power)
self.children.append(self.plot)
self.children.append(self.cbar)
def generate_colorbar(self,cbarlength = 500,cbarwidth = 50):
pal = RdYlBu11
minVal = self.datasrc.data['z'][0].min()
maxVal = self.datasrc.data['z'][0].max()
vals = numpy.linspace(minVal,maxVal,len(pal))
self.cbar = Figure(tools = "",x_range = [minVal,maxVal],y_range = [0,1],
plot_width = cbarlength,plot_height = cbarwidth)
self.cbar.toolbar_location = None
self.cbar.min_border_left = 10
self.cbar.min_border_right = 10
self.cbar.min_border_top = 0
self.cbar.min_border_bottom = 0
self.cbar.xaxis.visible = None
self.cbar.yaxis.visible = None
self.cbar.extra_x_ranges = {'xrange':self.cbarrange.data['range'][0]}
self.cbar.add_layout(LinearAxis(x_range_name = 'xrange'),'below')
for r in self.cbar.renderers:
if type(r).__name__ == 'Grid':
r.grid_line_color = None
self.cbar.rect(x = vals,y = 0.5,color = pal,width = vals[1]-vals[0],height = 1)
def updatez(self):
data = self.datasrc.data
newdata = data
z = data['z']
z[0] = data['Z'][self.power.value - 1]
newdata['z'] = z
self.datasrc.trigger('data',data,newdata)
def updatecbar(self):
minVal = self.datasrc.data['z'][0].min()
maxVal = self.datasrc.data['z'][0].max()
self.cbarrange.data['range'][0].start = minVal
self.cbarrange.data['range'][0].end = maxVal
def inputchange(self,attrname,old,new):
self.updatez()
self.updatecbar()
curdoc().add_root(Colourbar())

How to make PanTool and ZoomTool behave if plot origin is 'top left'?

I'm trying to use PanTool and ZoomTool in a Chaco plot whose origin is set to 'top left' but the behavior of these tools is not as expected. Panning moves in the opposite direction and box zooming doesn't necessarily zoom to the highlighted region. Example code is:
plot.plot((x_key, y_key), origin='top left')
plot.tools.append(PanTool(plot))
plot.overlays.append(ZoomTool(plot, tool_mode='box', always_on=False))
If origin='top left' is removed, the panning and zooming behavior is as I'd expect.
This is a really late reply, but basically the origin needs to be set on the main Plot instance, instead of the call to its plot method. (The origin set when initializing Plot gets passed into plot also)
import numpy as np
from enable.api import Component, ComponentEditor
from traits.api import HasTraits, Instance
from traitsui.api import UItem, Group, View
from chaco.api import ArrayPlotData, Plot
from chaco.tools.api import PanTool, ZoomTool
class Demo(HasTraits):
plot = Instance(Component)
traits_view = View(
Group(
UItem('plot', editor=ComponentEditor(size=(900, 500))),
),
)
def _plot_default(self):
x = np.linspace(-2.0, 10.0, 100)
data = ArrayPlotData(x=x, y=np.sin(x))
# This works
plot = Plot(data, origin='top left')
plot.plot(('x', 'y'))
# This doesn't
# plot = Plot(data)
# plot.plot(('x', 'y'), origin='top left')
plot.tools.append(PanTool(plot))
zoom = ZoomTool(component=plot, tool_mode="box", always_on=False)
plot.overlays.append(zoom)
return plot
if __name__ == "__main__":
demo = Demo()
demo.configure_traits()