File: gridlines.py

package info (click to toggle)
python-vispy 0.16.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 9,112 kB
  • sloc: python: 61,648; javascript: 6,800; ansic: 2,104; makefile: 141; sh: 6
file content (161 lines) | stat: -rw-r--r-- 5,217 bytes parent folder | download
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
# -*- coding: utf-8 -*-
# Copyright (c) Vispy Development Team. All Rights Reserved.
# Distributed under the (new) BSD License. See LICENSE.txt for more info.

from __future__ import division

import numpy as np

from .image import ImageVisual
from ..color import Color
from .shaders import Function


_GRID_COLOR = """
uniform vec4 u_gridlines_bounds;
uniform float u_border_width;

vec4 grid_color(vec2 pos) {
    vec4 px_pos = $map_to_doc(vec4(pos, 0, 1));
    px_pos /= px_pos.w;

    // Compute vectors representing width, height of pixel in local coords
    vec4 local_pos = $map_doc_to_local(px_pos);
    vec4 dx = $map_doc_to_local(px_pos + vec4(1.0, 0, 0, 0));
    vec4 dy = $map_doc_to_local(px_pos + vec4(0, 1.0, 0, 0));
    local_pos /= local_pos.w;
    dx = dx / dx.w - local_pos;
    dy = dy / dy.w - local_pos;

    // Pixel length along each axis, rounded to the nearest power of 10
    vec2 px = vec2(abs(dx.x) + abs(dy.x), abs(dx.y) + abs(dy.y));
    float log10 = log(10.0);
    float sx = pow(10.0, floor(log(px.x) / log10) + 1.) * $scale.x;
    float sy = pow(10.0, floor(log(px.y) / log10) + 1.) * $scale.y;

    float max_alpha = 0.6;
    float x_alpha = 0.0;

    if (mod(local_pos.x, 1000. * sx) < px.x) {
        x_alpha = clamp(1. * sx/px.x, 0., max_alpha);
    }
    else if (mod(local_pos.x, 100. * sx) < px.x) {
        x_alpha = clamp(.1 * sx/px.x, 0., max_alpha);
    }
    else if (mod(local_pos.x, 10. * sx) < px.x) {
        x_alpha = clamp(0.01 * sx/px.x, 0., max_alpha);
    }

    float y_alpha = 0.0;
    if (mod(local_pos.y, 1000. * sy) < px.y) {
        y_alpha = clamp(1. * sy/px.y, 0., max_alpha);
    }
    else if (mod(local_pos.y, 100. * sy) < px.y) {
        y_alpha = clamp(.1 * sy/px.y, 0., max_alpha);
    }
    else if (mod(local_pos.y, 10. * sy) < px.y) {
        y_alpha = clamp(0.01 * sy/px.y, 0., max_alpha);
    }

    float alpha = (((log(max(x_alpha, y_alpha))/log(10.)) + 2.) / 3.);
    if (alpha == 0.) {
        discard;
    }

    if (any(lessThan(local_pos.xy + u_border_width / 2, u_gridlines_bounds.xz)) ||
        any(greaterThan(local_pos.xy - u_border_width / 2, u_gridlines_bounds.yw))) {
        discard;
    }

    if (any(lessThan(local_pos.xy - u_gridlines_bounds.xz, vec2(u_border_width / 2))) ||
        any(lessThan(u_gridlines_bounds.yw - local_pos.xy, vec2(u_border_width / 2)))) {
        alpha = 1;
    }

    return vec4($color.rgb, $color.a * alpha);
}
"""


class GridLinesVisual(ImageVisual):
    """Displays regularly spaced grid lines in any coordinate system and at
    any scale.

    Parameters
    ----------
    scale : tuple
        The scale factors to apply when determining the spacing of grid lines.
    color : Color
        The base color for grid lines. The final color may have its alpha
        channel modified.
    grid_bounds : tuple or None
        The lower and upper bound for each axis beyond which no grid is rendered.
        In the form of (minx, maxx, miny, maxy).
    border_width : float
        Tickness of the border rendered at the bounds of the grid.
    """

    def __init__(self, scale=(1, 1), color='w',
                 grid_bounds=None,
                 border_width=2):
        # todo: PlaneVisual should support subdivide/impostor methods from
        # image and gridlines should inherit from plane instead.
        self._grid_color_fn = Function(_GRID_COLOR)
        self._grid_color_fn['color'] = Color(color).rgba
        self._grid_color_fn['scale'] = scale
        ImageVisual.__init__(self, method='impostor')
        self.set_gl_state('additive', cull_face=False)
        self.shared_program.frag['get_data'] = self._grid_color_fn
        cfun = Function('vec4 null(vec4 x) { return x; }')
        self.shared_program.frag['color_transform'] = cfun
        self.unfreeze()
        self.grid_bounds = grid_bounds
        self.border_width = border_width
        self.freeze()

    @property
    def grid_bounds(self):
        return self._grid_bounds

    @grid_bounds.setter
    def grid_bounds(self, value):
        if value is None:
            value = (None,) * 4
        grid_bounds = []
        for i, v in enumerate(value):
            if v is None:
                if i % 2:
                    v = -np.inf
                else:
                    v = np.inf
            grid_bounds.append(v)
        self.shared_program['u_gridlines_bounds'] = value
        self._grid_bounds = grid_bounds
        self.update()

    @property
    def border_width(self):
        return self._border_width

    @border_width.setter
    def border_width(self, value):
        self.shared_program['u_border_width'] = value
        self._border_width = value
        self.update()

    @property
    def size(self):
        return (1, 1)

    def _prepare_transforms(self, view):
        fn = self._grid_color_fn
        fn['map_to_doc'] = self.get_transform('visual', 'document')
        fn['map_doc_to_local'] = self.get_transform('document', 'visual')
        ImageVisual._prepare_transforms(self, view)

    def _prepare_draw(self, view):
        if self._need_vertex_update:
            self._build_vertex_data()

        if view._need_method_update:
            self._update_method(view)