1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
|
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright (c) Vispy Development Team. All Rights Reserved.
# Distributed under the (new) BSD License. See LICENSE.txt for more info.
# -----------------------------------------------------------------------------
import numpy as np
from .visual import Visual
from .. import gloo
class ScrollingLinesVisual(Visual):
"""Displays many line strips of equal length, with the option to add new
vertex data to one end of the lines.
Parameters
----------
n_lines : int
The number of independent line strips to draw.
line_size : int
The number of samples in each line strip.
dx : float
The x distance between samples
color : array-like
An array of colors to assign to each line strip.
pos_offset : array-like
An array of x, y position offsets to apply to each line strip.
columns : int
Arrange line strips into a grid with this number of columns. This
option is not compatible with *pos_offset*.
cell_size : tuple
The x, y distance between cells in the grid.
"""
vertex_code = """
attribute vec2 index; // .x=line_n, .y=vertex_n
uniform sampler2D position;
uniform sampler1D pos_offset;
uniform sampler1D color_tex;
uniform vec2 pos_size; // x=n_lines, y=n_verts_per_line
uniform float offset; // rolling pointer into vertexes
uniform float dx; // x step per sample
varying vec2 v_index;
varying vec4 v_color;
void main() {
v_index = vec2(mod(index.y + offset, pos_size.y), index.x);
vec2 uv = (v_index + 0.5) / (pos_size.yx);
vec4 pos = vec4(index.y * dx, texture2D(position, uv).r, 0, 1);
// fetch starting position from texture lookup:
pos += vec4(texture1D(pos_offset, (index.x + 0.5) / pos_size.x).rg,
0, 0);
gl_Position = $transform(pos);
v_color = texture1D(color_tex, (index.x + 0.5) / pos_size.x);
}
"""
fragment_code = """
varying vec2 v_index;
varying vec4 v_color;
void main() {
if (v_index.y - floor(v_index.y) > 0) {
discard;
}
gl_FragColor = $color;
}
"""
def __init__(self, n_lines, line_size, dx, color=None, pos_offset=None,
columns=None, cell_size=None):
self._pos_data = None
self._offset = 0
self._dx = dx
data = np.zeros((n_lines, line_size), dtype='float32')
self._pos_tex = gloo.Texture2D(data, format='luminance',
internalformat='r32f')
self._index_buf = gloo.VertexBuffer()
self._data_shape = data.shape
Visual.__init__(self, vcode=self.vertex_code, fcode=self.fragment_code)
self.shared_program['position'] = self._pos_tex
self.shared_program['index'] = self._index_buf
self.shared_program['dx'] = dx
self.shared_program['pos_size'] = data.shape
self.shared_program['offset'] = self._offset
# set an array giving the x/y origin for each plot
if pos_offset is None:
# construct positions as a grid
rows = int(np.ceil(n_lines / columns))
pos_offset = np.empty((rows, columns, 3), dtype='float32')
pos_offset[..., 0] = (np.arange(columns)[np.newaxis, :] *
cell_size[0])
pos_offset[..., 1] = np.arange(rows)[:, np.newaxis] * cell_size[1]
# limit position texture to the number of lines in case there are
# more row/column cells than lines
pos_offset = pos_offset.reshape((rows*columns), 3)[:n_lines, :]
self._pos_offset = gloo.Texture1D(pos_offset, internalformat='rgb32f',
interpolation='nearest')
self.shared_program['pos_offset'] = self._pos_offset
if color is None:
# default to white (1, 1, 1, 1)
self._color_tex = gloo.Texture1D(
np.ones((n_lines, 4), dtype=np.float32))
self.shared_program['color_tex'] = self._color_tex
self.shared_program.frag['color'] = 'v_color'
else:
self._color_tex = gloo.Texture1D(color)
self.shared_program['color_tex'] = self._color_tex
self.shared_program.frag['color'] = 'v_color'
# construct a vertex buffer index containing (plot_n, vertex_n) for
# each vertex
index = np.empty((data.shape[0], data.shape[1], 2), dtype='float32')
index[..., 0] = np.arange(data.shape[0])[:, np.newaxis]
index[..., 1] = np.arange(data.shape[1])[np.newaxis, :]
index = index.reshape((index.shape[0]*index.shape[1], index.shape[2]))
self._index_buf.set_data(index)
self._draw_mode = 'line_strip'
self.set_gl_state('translucent', line_width=1)
self.freeze()
def set_pos_offset(self, po):
"""Set the array of position offsets for each line strip.
Parameters
----------
po : array-like
An array of xy offset values.
"""
self._pos_offset.set_data(po)
def set_color(self, color):
"""Set the array of colors for each line strip.
Parameters
----------
color : array-like
An array of rgba values.
"""
self._color_tex.set_data(color)
def _prepare_transforms(self, view):
view.view_program.vert['transform'] = view.get_transform().simplified
def _prepare_draw(self, view):
pass
def _compute_bounds(self, axis, view):
if self._pos_data is None:
return None
return self._pos_data[..., axis].min(), self.pos_data[..., axis].max()
def roll_data(self, data):
"""Append new data to the right side of every line strip and remove
as much data from the left.
Parameters
----------
data : array-like
A data array to append.
"""
data = data.astype('float32')[..., np.newaxis]
s1 = self._data_shape[1] - self._offset
if data.shape[1] > s1:
self._pos_tex[:, self._offset:] = data[:, :s1]
self._pos_tex[:, :data.shape[1] - s1] = data[:, s1:]
self._offset = (self._offset + data.shape[1]) % self._data_shape[1]
else:
self._pos_tex[:, self._offset:self._offset+data.shape[1]] = data
self._offset += data.shape[1]
self.shared_program['offset'] = self._offset
self.update()
def set_data(self, index, data):
"""Set the complete data for a single line strip.
Parameters
----------
index : int
The index of the line strip to be replaced.
data : array-like
The data to assign to the selected line strip.
"""
self._pos_tex[index, :] = data
self.update()
|