Сегодня расскажу вам о замечательной программе под названием Chaco, которую разрабатывает компания Enthought.
Chaco — это кроссплатформенное приложение по созданию графиков любой сложности на языке Python. Ориентируется на отрисовку статических данных, но имеет и возможности создания анимации.

Так же, как и Mayavi умеет встраиваться в Wx и Qt (PyQt и PySide) приложения, дружит с Numpy-массивами.
Первым делом Chaco надо установить. Ставим зависимости: git, subversion, setuptools, swig, numpy, scipy, vtk, wxpython. Для Windows также потребуется установить mingw (vtk и wxpython для Win советую взять отсюда для экономии времени www.lfd.uci.edu/~gohlke/pythonlibs ). Забираем с git продукты ETS (ненужное можно будет удалить):
Затем все это дело собираем:
Возможно придется еще что-то доставить, тут нужно смотреть логи сборки. Чего на хватало, устанавливаем и запускаем сборку заново.
В папке ets/chaco/examples можно будет увидеть большой архив различных примеров. Примеры очень хорошие, так что мне что-то объяснять довольно трудно, получится копипаст кода.
Опишу лишь некоторые необычные графики, которые можно строить в Chaco:
Чтобы посмотреть как Chaco реализует анимацию, загляните в папку ets/chaco/examples/updating_plot
Chaco в HPGL-GUI
В HPGL-GUI нужно было строить гистограммы. Для этого одинаково подходили Matplotlib и Chaco. Выбор пал на Chaco, т.к. Matplotlib не поддерживал интеграцию в PySide.
Выглядит окно статистики вот так:

Код можно посмотреть тут:
raw.github.com/Snegovikufa/HPGL-GUI/master/gui_widgets/statistics_window.py
P.S. Если нужно рассказать про встраивание в PyQt4 или PySide, то допишу.
UPD. Обновил пример финансового графика: добавил подробные комментарии и сделал встраивание в виджет PySide.
Chaco — это кроссплатформенное приложение по созданию графиков любой сложности на языке Python. Ориентируется на отрисовку статических данных, но имеет и возможности создания анимации.

Так же, как и Mayavi умеет встраиваться в Wx и Qt (PyQt и PySide) приложения, дружит с Numpy-массивами.
Установка
Первым делом Chaco надо установить. Ставим зависимости: git, subversion, setuptools, swig, numpy, scipy, vtk, wxpython. Для Windows также потребуется установить mingw (vtk и wxpython для Win советую взять отсюда для экономии времени www.lfd.uci.edu/~gohlke/pythonlibs ). Забираем с git продукты ETS (ненужное можно будет удалить):
mkdir ets && cd ets
wget github.com/enthought/ets/raw/master/ets.py
python ets.py clone
Затем все это дело собираем:
python ets.py develop
Возможно придется еще что-то доставить, тут нужно смотреть логи сборки. Чего на хватало, устанавливаем и запускаем сборку заново.
Примеры
В папке ets/chaco/examples можно будет увидеть большой архив различных примеров. Примеры очень хорошие, так что мне что-то объяснять довольно трудно, получится копипаст кода.
Опишу лишь некоторые необычные графики, которые можно строить в Chaco:
Финансовые:
Данный пример использует встраивание в виджет PySide.
# -*- coding: utf-8 -*_<br/>
<br/>
# Важное установить переменную QT_API равной pyside до того, <br/>
# как происходит импорт модулей Chaco<br/>
import os<br/>
os.environ['QT_API'] = 'pyside'<br/>
os.environ['ETS_TOOLKIT'] = 'qt4'<br/>
from PySide import QtGui, QtCore<br/>
<br/>
from numpy import abs, arange, cumprod, random<br/>
from enable.example_support import DemoFrame, demo_main<br/>
from enable.api import Window, Component, ComponentEditor<br/>
from traits.api import HasTraits, Instance<br/>
from traitsui.api import Item, Group, View<br/>
from chaco.api import ArrayDataSource, BarPlot, DataRange1D, \<br/>
LinearMapper, VPlotContainer, PlotAxis, FilledLinePlot, \<br/>
add_default_grids, PlotLabel<br/>
from chaco.tools.api import PanTool, ZoomTool<br/>
<br/>
# Функция, создающая контейнер с графиками<br/>
def _create_plot_component():<br/>
<br/>
# Создадим случайные величины для графиков<br/>
numpoints = 500<br/>
index = arange(numpoints)<br/>
returns = random.lognormal(0.01, 0.1, size=numpoints)<br/>
price = 100.0 * cumprod(returns)<br/>
volume = abs(random.normal(1000.0, 1500.0, size=numpoints) + 2000.0)<br/>
<br/>
# ArrayDataSource - это массивы, хранящие наши данные<br/>
time_ds = ArrayDataSource(index)<br/>
vol_ds = ArrayDataSource(volume, sort_order="none")<br/>
price_ds = ArrayDataSource(price, sort_order="none")<br/>
<br/>
# LinearMapper - это массивы подписей по осям<br/>
xmapper = LinearMapper(range=DataRange1D(time_ds))<br/>
vol_mapper = LinearMapper(range=DataRange1D(vol_ds))<br/>
price_mapper = LinearMapper(range=DataRange1D(price_ds))<br/>
<br/>
# График цены типа FilledLinePlot с заполнением области под графиком<br/>
price_plot = FilledLinePlot(index = time_ds, value = price_ds,<br/>
index_mapper = xmapper,<br/>
value_mapper = price_mapper,<br/>
edge_color = "blue",<br/>
face_color = "paleturquoise",<br/>
bgcolor = "white",<br/>
border_visible = True)<br/>
<br/>
# Добавим сетку и оси<br/>
add_default_grids(price_plot)<br/>
price_plot.overlays.append(PlotAxis(price_plot, orientation='left'))<br/>
price_plot.overlays.append(PlotAxis(price_plot, orientation='bottom'))<br/>
<br/>
# Добавим возможность передвигания графика<br/>
price_plot.tools.append(PanTool(price_plot, constrain=True,<br/>
constrain_direction="x"))<br/>
# Добавим зум<br/>
price_plot.overlays.append(ZoomTool(price_plot, drag_button="right",<br/>
always_on=True,<br/>
tool_mode="range",<br/>
axis="index"))<br/>
<br/>
# BarPlot - график в виде "столбиков"<br/>
vol_plot = BarPlot(index = time_ds, value = vol_ds,<br/>
index_mapper = xmapper,<br/>
value_mapper = vol_mapper,<br/>
line_color = "transparent",<br/>
fill_color = "black",<br/>
bar_width = 1.0,<br/>
bar_width_type = "screen",<br/>
antialias = False,<br/>
height = 100,<br/>
resizable = "h",<br/>
bgcolor = "white",<br/>
border_visible = True)<br/>
<br/>
# Добавим сетку и оси<br/>
add_default_grids(vol_plot)<br/>
vol_plot.underlays.append(PlotAxis(vol_plot, orientation='left'))<br/>
vol_plot.tools.append(PanTool(vol_plot, constrain=True,<br/>
constrain_direction="x"))<br/>
<br/>
# container - массив наших графиков, управляет их расположением<br/>
container = VPlotContainer(bgcolor = "lightblue",<br/>
spacing = 20,<br/>
padding = 50,<br/>
fill_padding=False)<br/>
container.add(vol_plot)<br/>
container.add(price_plot)<br/>
# Добавим надпись над контейнером<br/>
container.overlays.append(PlotLabel("Financial Plot",<br/>
component=container,<br/>
font="Arial 24"))<br/>
return container<br/>
<br/>
<br/>
class Demo(HasTraits):<br/>
# HasTraits - особый класс-словарь Traits, который связывается <br/>
# с некоторым обработчиком.<br/>
plot = Instance(Component)<br/>
<br/>
# View - представление наших графиков.<br/>
traits_view = View(<br/>
Group(<br/>
Item('plot', editor=ComponentEditor(size=(800, 600)),<br/>
show_label=False),<br/>
orientation = "vertical"),<br/>
resizable=True<br/>
)<br/>
<br/>
def _plot_default(self):<br/>
# Контейнер/график, который отрисовывается по умолчанию<br/>
return _create_plot_component()<br/>
<br/>
# Закомментируем стандартное окно, в котором рисуются графики<br/>
#class PlotFrame(DemoFrame):<br/>
#<br/>
# def _create_window(self):<br/>
# # создает окно, в котором нужно отрисовать графики<br/>
# return Window(self, -1, component=_create_plot_component())<br/>
<br/>
class ChacoQWidget(QtGui.QWidget):<br/>
def __init__(self, parent=None):<br/>
QtGui.QWidget.__init__(self, parent)<br/>
layout = QtGui.QVBoxLayout(self)<br/>
frame = Demo()<br/>
# Теперь нам нужно создать виджет, для чего вызываем функцию control,<br/>
# без нее магия не работает :)<br/>
ui = frame.edit_traits(parent=self, kind='subpanel').control<br/>
layout.addWidget(ui)<br/>
layout.addWidget(QtGui.QPushButton("Hello Habrahabr"))<br/>
<br/>
if __name__ == "__main__":<br/>
#demo_main(PlotFrame, size=(800, 600), title="Financial plot example")<br/>
app = QtGui.QApplication.instance()<br/>
w = ChacoQWidget()<br/>
w.resize(800, 600)<br/>
w.show()<br/>
app.exec_()
Выборка цветов для графиков (приложу анимацию, но на самом деле все меняется по скроллу мыши):
from numpy import arange, exp, sort<br/>
from numpy.random import random<br/>
from enable.example_support import DemoFrame, demo_main<br/>
from enable.api import Component, ComponentEditor, Window<br/>
from traits.api import HasTraits, Instance<br/>
from traitsui.api import Item, Group, View<br/>
from chaco.api import ArrayPlotData, ColorBar, \<br/>
ColormappedSelectionOverlay, HPlotContainer, \<br/>
jet, LinearMapper, Plot, gist_earth<br/>
from chaco.tools.api import PanTool, ZoomTool, RangeSelection, \<br/>
RangeSelectionOverlay<br/>
<br/>
#===============================================================================<br/>
# # Create the Chaco plot.<br/>
#===============================================================================<br/>
def _create_plot_component():<br/>
<br/>
# Create some data<br/>
numpts = 1000<br/>
x = sort(random(numpts))<br/>
y = random(numpts)<br/>
color = exp(-(x**2 + y**2))<br/>
<br/>
# Create a plot data obect and give it this data<br/>
pd = ArrayPlotData()<br/>
pd.set_data("index", x)<br/>
pd.set_data("value", y)<br/>
pd.set_data("color", color)<br/>
<br/>
# Create the plot<br/>
plot = Plot(pd)<br/>
plot.plot(("index", "value", "color"),<br/>
type="cmap_scatter",<br/>
name="my_plot",<br/>
color_mapper=gist_earth,<br/>
marker = "square",<br/>
fill_alpha = 0.5,<br/>
marker_size = 8,<br/>
outline_color = "black",<br/>
border_visible = True,<br/>
bgcolor = "white")<br/>
<br/>
# Tweak some of the plot properties<br/>
plot.title = "Colormapped Scatter Plot with Pan/Zoom Color Bar"<br/>
plot.padding = 50<br/>
plot.x_grid.visible = False<br/>
plot.y_grid.visible = False<br/>
plot.x_axis.font = "modern 16"<br/>
plot.y_axis.font = "modern 16"<br/>
<br/>
# Add pan and zoom to the plot<br/>
plot.tools.append(PanTool(plot, constrain_key="shift"))<br/>
zoom = ZoomTool(plot)<br/>
plot.overlays.append(zoom)<br/>
<br/>
# Create the colorbar, handing in the appropriate range and colormap<br/>
colorbar = ColorBar(index_mapper=LinearMapper(range=plot.color_mapper.range),<br/>
color_mapper=plot.color_mapper,<br/>
orientation='v',<br/>
resizable='v',<br/>
width=30,<br/>
padding=20)<br/>
colorbar.plot = plot<br/>
colorbar.padding_top = plot.padding_top<br/>
colorbar.padding_bottom = plot.padding_bottom<br/>
<br/>
# Add pan and zoom tools to the colorbar<br/>
colorbar.tools.append(PanTool(colorbar, constrain_direction="y", constrain=True))<br/>
zoom_overlay = ZoomTool(colorbar, axis="index", tool_mode="range",<br/>
always_on=True, drag_button="right")<br/>
colorbar.overlays.append(zoom_overlay)<br/>
<br/>
# Create a container to position the plot and the colorbar side-by-side<br/>
container = HPlotContainer(plot, colorbar, use_backbuffer=True, bgcolor="lightgray")<br/>
<br/>
return container<br/>
<br/>
#===============================================================================<br/>
# Attributes to use for the plot view.<br/>
size=(650,650)<br/>
title="Colormapped scatter plot"<br/>
<br/>
#===============================================================================<br/>
# # Demo class that is used by the demo.py application.<br/>
#===============================================================================<br/>
class Demo(HasTraits):<br/>
plot = Instance(Component)<br/>
<br/>
traits_view = View(<br/>
Group(<br/>
Item('plot', editor=ComponentEditor(size=size),<br/>
show_label=False),<br/>
orientation = "vertical"),<br/>
resizable=True, title=title<br/>
)<br/>
<br/>
def _plot_default(self):<br/>
return _create_plot_component()<br/>
<br/>
demo = Demo()<br/>
<br/>
#===============================================================================<br/>
# Stand-alone frame to display the plot.<br/>
#===============================================================================<br/>
class PlotFrame(DemoFrame):<br/>
<br/>
def _create_window(self):<br/>
# Return a window containing our plots<br/>
return Window(self, -1, component=_create_plot_component())<br/>
<br/>
if __name__ == "__main__":<br/>
demo_main(PlotFrame, size=size, title=title)
Графики в полярных координатах:
Код:
from numpy import arange, pi, sin, cos<br/>
from enthought.enable.example_support import DemoFrame, demo_main<br/>
from enthought.enable.api import Window<br/>
from enthought.traits.api import false<br/>
from enthought.chaco.api import create_polar_plot<br/>
<br/>
class MyFrame(DemoFrame):<br/>
def _create_window(self):<br/>
numpoints = 5000<br/>
low = 0<br/>
high = pi*2<br/>
theta = arange(low, high, (high-low) / numpoints)<br/>
radius = sin(theta*3)<br/>
<br/>
plot = create_polar_plot((radius,theta),color=(0.0,0.0,1.0,1), width=4.0)<br/>
plot.bgcolor = "white"<br/>
return Window(self, -1, component=plot)<br/>
<br/>
if __name__ == "__main__":<br/>
demo_main(MyFrame, size=(600,600), title="Simple Polar Plot")
Различные полигоны:
import math<br/>
from numpy import array, transpose<br/>
from enable.example_support import DemoFrame, demo_main<br/>
from enable.api import Component, ComponentEditor, Window<br/>
from traits.api import HasTraits, Instance, Enum, CArray, Dict<br/>
from traitsui.api import Item, Group, View<br/>
from chaco.api import ArrayPlotData, HPlotContainer, Plot<br/>
from chaco.base import n_gon<br/>
from chaco.tools.api import PanTool, ZoomTool, DragTool<br/>
<br/>
class DataspaceMoveTool(DragTool):<br/>
"""<br/>
Modifies the data values of a plot. Only works on instances<br/>
of BaseXYPlot or its subclasses<br/>
"""<br/>
<br/>
event_state = Enum("normal", "dragging")<br/>
_prev_pt = CArray<br/>
<br/>
def is_draggable(self, x, y):<br/>
return self.component.hittest((x,y))<br/>
<br/>
def drag_start(self, event):<br/>
data_pt = self.component.map_data((event.x, event.y), all_values=True)<br/>
self._prev_pt = data_pt<br/>
event.handled = True<br/>
<br/>
def dragging(self, event):<br/>
plot = self.component<br/>
cur_pt = plot.map_data((event.x, event.y), all_values=True)<br/>
dx = cur_pt[0] - self._prev_pt[0]<br/>
dy = cur_pt[1] - self._prev_pt[1]<br/>
index = plot.index.get_data() + dx<br/>
value = plot.value.get_data() + dy<br/>
plot.index.set_data(index, sort_order=plot.index.sort_order)<br/>
plot.value.set_data(value, sort_order=plot.value.sort_order)<br/>
self._prev_pt = cur_pt<br/>
event.handled = True<br/>
plot.request_redraw()<br/>
<br/>
<br/>
#===============================================================================<br/>
# # Create the Chaco plot.<br/>
#===============================================================================<br/>
def _create_plot_component():<br/>
<br/>
# Use n_gon to compute center locations for our polygons<br/>
points = n_gon(center=(0,0), r=4, nsides=8)<br/>
<br/>
# Choose some colors for our polygons<br/>
colors = {3:0xaabbcc, 4:'orange', 5:'yellow', 6:'lightgreen',<br/>
7:'green', 8:'blue', 9:'lavender', 10:'purple'}<br/>
<br/>
# Create a PlotData object to store the polygon data<br/>
pd = ArrayPlotData()<br/>
<br/>
# Create a Polygon Plot to draw the regular polygons<br/>
polyplot = Plot(pd)<br/>
<br/>
# Store path data for each polygon, and plot<br/>
nsides = 3<br/>
for p in points:<br/>
npoints = n_gon(center=p, r=2, nsides=nsides)<br/>
nxarray, nyarray = transpose(npoints)<br/>
pd.set_data("x" + str(nsides), nxarray)<br/>
pd.set_data("y" + str(nsides), nyarray)<br/>
plot = polyplot.plot(("x"+str(nsides), "y"+str(nsides)),<br/>
type="polygon",<br/>
face_color=colors[nsides],<br/>
hittest_type="poly")[0]<br/>
plot.tools.append(DataspaceMoveTool(plot, drag_button="right"))<br/>
nsides = nsides + 1<br/>
<br/>
# Tweak some of the plot properties<br/>
polyplot.padding = 50<br/>
polyplot.title = "Polygon Plot"<br/>
<br/>
# Attach some tools to the plot<br/>
polyplot.tools.append(PanTool(polyplot))<br/>
zoom = ZoomTool(polyplot, tool_mode="box", always_on=False)<br/>
polyplot.overlays.append(zoom)<br/>
<br/>
return polyplot<br/>
<br/>
#===============================================================================<br/>
# Attributes to use for the plot view.<br/>
size=(800,800)<br/>
title="Polygon Plot"<br/>
<br/>
#===============================================================================<br/>
# # Demo class that is used by the demo.py application.<br/>
#===============================================================================<br/>
class Demo(HasTraits):<br/>
plot = Instance(Component)<br/>
<br/>
traits_view = View(<br/>
Group(<br/>
Item('plot', editor=ComponentEditor(size=size),<br/>
show_label=False),<br/>
orientation = "vertical"),<br/>
resizable=True, title=title<br/>
)<br/>
<br/>
def _plot_default(self):<br/>
return _create_plot_component()<br/>
<br/>
demo = Demo()<br/>
<br/>
#===============================================================================<br/>
# Stand-alone frame to display the plot.<br/>
#===============================================================================<br/>
class PlotFrame(DemoFrame):<br/>
<br/>
def _create_window(self):<br/>
# Return a window containing our plots<br/>
return Window(self, -1, component=_create_plot_component())<br/>
<br/>
if __name__ == "__main__":<br/>
demo_main(PlotFrame, size=size, title=title)
Рентгеновские лучи:
from __future__ import with_statement<br/>
import numpy<br/>
from traits.api import HasTraits, Instance, Enum<br/>
from traitsui.api import View, Item<br/>
from enable.api import ComponentEditor<br/>
from chaco.api import Plot, ArrayPlotData, AbstractOverlay<br/>
from enable.api import BaseTool<br/>
from enable.markers import DOT_MARKER, DotMarker<br/>
<br/>
class BoxSelectTool(BaseTool):<br/>
""" Tool for selecting all points within a box<br/>
<br/>
There are 2 states for this tool, normal and selecting. While the<br/>
left mouse button is down the metadata on the datasources will be<br/>
updated with the current selected bounds.<br/>
<br/>
Note that the tool does not actually store the selected point, but the<br/>
bounds of the box.<br/>
"""<br/>
<br/>
event_state = Enum("normal", "selecting")<br/>
<br/>
def normal_left_down(self, event):<br/>
self.event_state = "selecting"<br/>
self.selecting_mouse_move(event)<br/>
<br/>
def selecting_left_up(self, event):<br/>
self.event_state = "normal"<br/>
<br/>
def selecting_mouse_move(self, event):<br/>
x1, y1 = self.map_to_data(event.x-25, event.y-25)<br/>
x2, y2 = self.map_to_data(event.x+25, event.y+25)<br/>
<br/>
index_datasource = self.component.index<br/>
index_datasource.metadata['selections'] = (x1, x2)<br/>
<br/>
value_datasource = self.component.value<br/>
value_datasource.metadata['selections'] = (y1, y2)<br/>
<br/>
self.component.request_redraw()<br/>
<br/>
def map_to_data(self, x, y):<br/>
""" Returns the data space coordinates of the given x and y.<br/>
<br/>
Takes into account orientation of the plot and the axis setting.<br/>
"""<br/>
<br/>
plot = self.component<br/>
if plot.orientation == "h":<br/>
index = plot.x_mapper.map_data(x)<br/>
value = plot.y_mapper.map_data(y)<br/>
else:<br/>
index = plot.y_mapper.map_data(y)<br/>
value = plot.x_mapper.map_data(x)<br/>
<br/>
return index, value<br/>
<br/>
<br/>
class XRayOverlay(AbstractOverlay):<br/>
""" Overlay which draws scatter markers on top of plot data points.<br/>
<br/>
This overlay should be combined with a tool which updates the<br/>
datasources metadata with selection bounds.<br/>
"""<br/>
<br/>
marker = DotMarker()<br/>
<br/>
def overlay(self, component, gc, view_bounds=None, mode='normal'):<br/>
x_range = self._get_selection_index_screen_range()<br/>
y_range = self._get_selection_value_screen_range()<br/>
<br/>
if len(x_range) == 0:<br/>
return<br/>
<br/>
x1, x2 = x_range<br/>
y1, y2 = y_range<br/>
<br/>
with gc:<br/>
gc.set_alpha(0.8)<br/>
gc.set_fill_color((1.0,1.0,1.0))<br/>
gc.rect(x1, y1, x2-x1, y2-y1)<br/>
gc.draw_path()<br/>
<br/>
pts = self._get_selected_points()<br/>
if len(pts) == 0:<br/>
return<br/>
screen_pts = self.component.map_screen(pts)<br/>
if hasattr(gc, 'draw_marker_at_points'):<br/>
gc.draw_marker_at_points(screen_pts, 3, DOT_MARKER)<br/>
else:<br/>
gc.save_state()<br/>
for sx,sy in screen_pts:<br/>
gc.translate_ctm(sx, sy)<br/>
gc.begin_path()<br/>
self.marker.add_to_path(gc, 3)<br/>
gc.draw_path(self.marker.draw_mode)<br/>
gc.translate_ctm(-sx, -sy)<br/>
gc.restore_state()<br/>
<br/>
def _get_selected_points(self):<br/>
""" gets all the points within the bounds defined in the datasources<br/>
metadata<br/>
"""<br/>
index_datasource = self.component.index<br/>
index_selection = index_datasource.metadata['selections']<br/>
index = index_datasource.get_data()<br/>
<br/>
value_datasource = self.component.value<br/>
value_selection = value_datasource.metadata['selections']<br/>
value = value_datasource.get_data()<br/>
<br/>
x_indices = numpy.where((index > index_selection[0]) & (index < index_selection[-1]))<br/>
y_indices = numpy.where((value > value_selection[0]) & (value < value_selection[-1]))<br/>
<br/>
indices = list(set(x_indices[0]) & set(y_indices[0]))<br/>
<br/>
sel_index = index[indices]<br/>
sel_value = value[indices]<br/>
<br/>
return zip(sel_index, sel_value)<br/>
<br/>
def _get_selection_index_screen_range(self):<br/>
""" maps the selected bounds which were set by the tool into screen<br/>
space. The screen space points can be used for drawing the overlay<br/>
"""<br/>
index_datasource = self.component.index<br/>
index_mapper = self.component.index_mapper<br/>
index_selection = index_datasource.metadata['selections']<br/>
return tuple(index_mapper.map_screen(numpy.array(index_selection)))<br/>
<br/>
def _get_selection_value_screen_range(self):<br/>
""" maps the selected bounds which were set by the tool into screen<br/>
space. The screen space points can be used for drawing the overlay<br/>
"""<br/>
value_datasource = self.component.value<br/>
value_mapper = self.component.value_mapper<br/>
value_selection = value_datasource.metadata['selections']<br/>
return tuple(value_mapper.map_screen(numpy.array(value_selection)))<br/>
<br/>
class PlotExample(HasTraits):<br/>
<br/>
plot = Instance(Plot)<br/>
<br/>
traits_view = View(Item('plot', editor=ComponentEditor()),<br/>
width=600, height=600)<br/>
<br/>
def __init__(self, index, value, *args, **kw):<br/>
super(PlotExample, self).__init__(*args, **kw)<br/>
<br/>
plot_data = ArrayPlotData(index=index)<br/>
plot_data.set_data('value', value)<br/>
<br/>
self.plot = Plot(plot_data)<br/>
line = self.plot.plot(('index', 'value'))[0]<br/>
<br/>
line.overlays.append(XRayOverlay(line))<br/>
line.tools.append(BoxSelectTool(line))<br/>
<br/>
index = numpy.arange(0, 25, 0.25)<br/>
value = numpy.sin(index) + numpy.arange(0, 10, 0.1)<br/>
<br/>
example = PlotExample(index, value)<br/>
example.configure_traits()
Различные маркеры с выделением:
from numpy import arange, sort, compress, arange<br/>
from numpy.random import random<br/>
from enable.example_support import DemoFrame, demo_main<br/>
from enable.api import Component, ComponentEditor, Window<br/>
from traits.api import HasTraits, Instance<br/>
from traitsui.api import Item, Group, View<br/>
from chaco.api import AbstractDataSource, ArrayPlotData, Plot, \<br/>
HPlotContainer, LassoOverlay<br/>
from chaco.tools.api import LassoSelection, ScatterInspector<br/>
<br/>
#===============================================================================<br/>
# # Create the Chaco plot.<br/>
#===============================================================================<br/>
def _create_plot_component():<br/>
<br/>
# Create some data<br/>
npts = 2000<br/>
x = sort(random(npts))<br/>
y = random(npts)<br/>
<br/>
# Create a plot data obect and give it this data<br/>
pd = ArrayPlotData()<br/>
pd.set_data("index", x)<br/>
pd.set_data("value", y)<br/>
<br/>
# Create the plot<br/>
plot = Plot(pd)<br/>
plot.plot(("index", "value"),<br/>
type="scatter",<br/>
name="my_plot",<br/>
marker="circle",<br/>
index_sort="ascending",<br/>
color="red",<br/>
marker_size=4,<br/>
bgcolor="white")<br/>
<br/>
# Tweak some of the plot properties<br/>
plot.title = "Scatter Plot With Selection"<br/>
plot.line_width = 1<br/>
plot.padding = 50<br/>
<br/>
# Right now, some of the tools are a little invasive, and we need the<br/>
# actual ScatterPlot object to give to them<br/>
my_plot = plot.plots["my_plot"][0]<br/>
<br/>
# Attach some tools to the plot<br/>
lasso_selection = LassoSelection(component=my_plot,<br/>
selection_datasource=my_plot.index)<br/>
my_plot.active_tool = lasso_selection<br/>
my_plot.tools.append(ScatterInspector(my_plot))<br/>
lasso_overlay = LassoOverlay(lasso_selection=lasso_selection,<br/>
component=my_plot)<br/>
my_plot.overlays.append(lasso_overlay)<br/>
<br/>
# Uncomment this if you would like to see incremental updates:<br/>
#lasso_selection.incremental_select = True<br/>
<br/>
return plot<br/>
<br/>
<br/>
#===============================================================================<br/>
# Attributes to use for the plot view.<br/>
size=(650,650)<br/>
title="Scatter plot with selection"<br/>
bg_color="lightgray"<br/>
<br/>
#===============================================================================<br/>
# # Demo class that is used by the demo.py application.<br/>
#===============================================================================<br/>
class Demo(HasTraits):<br/>
plot = Instance(Component)<br/>
<br/>
traits_view = View(<br/>
Group(<br/>
Item('plot', editor=ComponentEditor(size=size),<br/>
show_label=False),<br/>
orientation = "vertical"),<br/>
resizable=True, title=title<br/>
)<br/>
<br/>
def _selection_changed(self):<br/>
mask = self.index_datasource.metadata['selection']<br/>
print "New selection: "<br/>
print compress(mask, arange(len(mask)))<br/>
print<br/>
<br/>
def _plot_default(self):<br/>
plot = _create_plot_component()<br/>
<br/>
# Retrieve the plot hooked to the LassoSelection tool.<br/>
my_plot = plot.plots["my_plot"][0]<br/>
lasso_selection = my_plot.active_tool<br/>
<br/>
# Set up the trait handler for the selection<br/>
self.index_datasource = my_plot.index<br/>
lasso_selection.on_trait_change(self._selection_changed,<br/>
'selection_changed')<br/>
<br/>
return plot<br/>
<br/>
demo = Demo()<br/>
<br/>
#===============================================================================<br/>
# Stand-alone frame to display the plot.<br/>
#===============================================================================<br/>
class PlotFrame(DemoFrame):<br/>
<br/>
index_datasource = Instance(AbstractDataSource)<br/>
<br/>
def _create_window(self):<br/>
<br/>
component = _create_plot_component()<br/>
<br/>
# Retrieve the plot hooked to the LassoSelection tool.<br/>
my_plot = component.plots["my_plot"][0]<br/>
lasso_selection = my_plot.active_tool<br/>
<br/>
# Set up the trait handler for the selection<br/>
self.index_datasource = my_plot.index<br/>
lasso_selection.on_trait_change(self._selection_changed,<br/>
'selection_changed')<br/>
<br/>
# Return a window containing our plots<br/>
return Window(self, -1, component=component, bg_color=bg_color)<br/>
<br/>
def _selection_changed(self):<br/>
mask = self.index_datasource.metadata['selection']<br/>
print "New selection: "<br/>
print compress(mask, arange(len(mask)))<br/>
print<br/>
<br/>
<br/>
if __name__ == "__main__":<br/>
demo_main(PlotFrame, size=size, title=title)
Чтобы посмотреть как Chaco реализует анимацию, загляните в папку ets/chaco/examples/updating_plot
Chaco в HPGL-GUI
В HPGL-GUI нужно было строить гистограммы. Для этого одинаково подходили Matplotlib и Chaco. Выбор пал на Chaco, т.к. Matplotlib не поддерживал интеграцию в PySide.
Выглядит окно статистики вот так:

Код можно посмотреть тут:
raw.github.com/Snegovikufa/HPGL-GUI/master/gui_widgets/statistics_window.py
P.S. Если нужно рассказать про встраивание в PyQt4 или PySide, то допишу.
UPD. Обновил пример финансового графика: добавил подробные комментарии и сделал встраивание в виджет PySide.