2 # Copyright 2009 Free Software Foundation, Inc.
4 # This file is part of GNU Radio
6 # GNU Radio is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3, or (at your option)
11 # GNU Radio is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with GNU Radio; see the file COPYING. If not, write to
18 # the Free Software Foundation, Inc., 51 Franklin Street,
19 # Boston, MA 02110-1301, USA.
26 from plotter_base import plotter_base
30 GRID_LINE_COLOR_SPEC = (.7, .7, .7) #gray
31 GRID_BORDER_COLOR_SPEC = (0, 0, 0) #black
32 TICK_TEXT_FONT_SIZE = 9
33 TITLE_TEXT_FONT_SIZE = 13
34 UNITS_TEXT_FONT_SIZE = 9
35 AXIS_LABEL_PADDING = 5
36 TICK_LABEL_PADDING = 5
37 TITLE_LABEL_PADDING = 7
38 POINT_LABEL_FONT_SIZE = 8
39 POINT_LABEL_COLOR_SPEC = (1, 1, .5)
40 POINT_LABEL_PADDING = 3
41 GRID_LINE_DASH_LEN = 4
43 ##################################################
44 # Grid Plotter Base Class
45 ##################################################
46 class grid_plotter_base(plotter_base):
48 def __init__(self, parent, min_padding=(0, 0, 0, 0)):
49 plotter_base.__init__(self, parent)
51 self._grid_cache = self.new_gl_cache(self._draw_grid, 25)
52 self.enable_grid_lines(True)
54 self.padding_top_min, self.padding_right_min, self.padding_bottom_min, self.padding_left_min = min_padding
55 #store title and unit strings
56 self.set_title('Title')
57 self.set_x_label('X Label')
58 self.set_y_label('Y Label')
59 #init the grid to some value
60 self.set_x_grid(-1, 1, 1)
61 self.set_y_grid(-1, 1, 1)
62 #setup point label cache
63 self._point_label_cache = self.new_gl_cache(self._draw_point_label, 75)
64 self.enable_point_label(False)
65 self.enable_grid_aspect_ratio(False)
66 self.set_point_label_coordinate(None)
67 common.point_label_thread(self)
69 self.register_init(self._init_grid_plotter)
71 def _init_grid_plotter(self):
73 Run gl initialization tasks.
75 GL.glEnableClientState(GL.GL_VERTEX_ARRAY)
77 def set_point_label_coordinate(self, coor):
79 Set the point label coordinate.
80 @param coor the coordinate x, y tuple or None
83 self._point_label_coordinate = coor
84 self._point_label_cache.changed(True)
88 def enable_grid_aspect_ratio(self, enable=None):
90 Enable/disable the grid aspect ratio.
91 If enabled, enforce the aspect ratio on the padding:
92 horizontal_padding:vertical_padding == width:height
93 @param enable true to enable
94 @return the enable state when None
96 if enable is None: return self._enable_grid_aspect_ratio
98 self._enable_grid_aspect_ratio = enable
99 for cache in self._gl_caches: cache.changed(True)
102 def enable_point_label(self, enable=None):
104 Enable/disable the point label.
105 @param enable true to enable
106 @return the enable state when None
108 if enable is None: return self._enable_point_label
110 self._enable_point_label = enable
111 self._point_label_cache.changed(True)
114 def set_title(self, title):
117 @param title the title string
121 self._grid_cache.changed(True)
124 def set_x_label(self, x_label, x_units=''):
126 Set the x label and units.
127 @param x_label the x label string
128 @param x_units the x units string
131 self.x_label = x_label
132 self.x_units = x_units
133 self._grid_cache.changed(True)
136 def set_y_label(self, y_label, y_units=''):
138 Set the y label and units.
139 @param y_label the y label string
140 @param y_units the y units string
143 self.y_label = y_label
144 self.y_units = y_units
145 self._grid_cache.changed(True)
148 def set_x_grid(self, minimum, maximum, step, scale=False):
150 Set the x grid parameters.
151 @param minimum the left-most value
152 @param maximum the right-most value
153 @param step the grid spacing
154 @param scale true to scale the x grid
157 self.x_min = float(minimum)
158 self.x_max = float(maximum)
159 self.x_step = float(step)
161 coeff, exp, prefix = common.get_si_components(max(abs(self.x_min), abs(self.x_max)))
162 self.x_scalar = 10**(-exp)
163 self.x_prefix = prefix
167 for cache in self._gl_caches: cache.changed(True)
170 def set_y_grid(self, minimum, maximum, step, scale=False):
172 Set the y grid parameters.
173 @param minimum the bottom-most value
174 @param maximum the top-most value
175 @param step the grid spacing
176 @param scale true to scale the y grid
179 self.y_min = float(minimum)
180 self.y_max = float(maximum)
181 self.y_step = float(step)
183 coeff, exp, prefix = common.get_si_components(max(abs(self.y_min), abs(self.y_max)))
184 self.y_scalar = 10**(-exp)
185 self.y_prefix = prefix
189 for cache in self._gl_caches: cache.changed(True)
192 def _draw_grid(self):
194 Create the x, y, tick, and title labels.
195 Resize the padding for the labels.
196 Draw the border, grid, title, and labels.
198 ##################################################
199 # Create GL text labels
200 ##################################################
201 #create x tick labels
202 x_tick_labels = [(tick, self._get_tick_label(tick, self.x_units))
203 for tick in self._get_ticks(self.x_min, self.x_max, self.x_step, self.x_scalar)]
204 #create x tick labels
205 y_tick_labels = [(tick, self._get_tick_label(tick, self.y_units))
206 for tick in self._get_ticks(self.y_min, self.y_max, self.y_step, self.y_scalar)]
208 x_label_str = self.x_units and "%s (%s%s)"%(self.x_label, self.x_prefix, self.x_units) or self.x_label
209 x_label = gltext.Text(x_label_str, bold=True, font_size=UNITS_TEXT_FONT_SIZE, centered=True)
211 y_label_str = self.y_units and "%s (%s%s)"%(self.y_label, self.y_prefix, self.y_units) or self.y_label
212 y_label = gltext.Text(y_label_str, bold=True, font_size=UNITS_TEXT_FONT_SIZE, centered=True)
214 title_label = gltext.Text(self.title, bold=True, font_size=TITLE_TEXT_FONT_SIZE, centered=True)
215 ##################################################
217 ##################################################
218 self.padding_top = max(2*TITLE_LABEL_PADDING + title_label.get_size()[1], self.padding_top_min)
219 self.padding_right = max(2*TICK_LABEL_PADDING, self.padding_right_min)
220 self.padding_bottom = max(2*AXIS_LABEL_PADDING + TICK_LABEL_PADDING + x_label.get_size()[1] + max([label.get_size()[1] for tick, label in x_tick_labels]), self.padding_bottom_min)
221 self.padding_left = max(2*AXIS_LABEL_PADDING + TICK_LABEL_PADDING + y_label.get_size()[1] + max([label.get_size()[0] for tick, label in y_tick_labels]), self.padding_left_min)
222 #enforce padding aspect ratio if enabled
223 if self.enable_grid_aspect_ratio():
224 w_over_h_ratio = float(self.width)/float(self.height)
225 horizontal_padding = float(self.padding_right + self.padding_left)
226 veritical_padding = float(self.padding_top + self.padding_bottom)
227 if w_over_h_ratio > horizontal_padding/veritical_padding:
228 #increase the horizontal padding
229 new_padding = veritical_padding*w_over_h_ratio - horizontal_padding
230 #distribute the padding to left and right
231 self.padding_left += int(round(new_padding/2))
232 self.padding_right += int(round(new_padding/2))
234 #increase the vertical padding
235 new_padding = horizontal_padding/w_over_h_ratio - veritical_padding
236 #distribute the padding to top and bottom
237 self.padding_top += int(round(new_padding/2))
238 self.padding_bottom += int(round(new_padding/2))
239 ##################################################
241 ##################################################
242 for tick, label in x_tick_labels:
243 scaled_tick = (self.width-self.padding_left-self.padding_right)*\
244 (tick/self.x_scalar-self.x_min)/(self.x_max-self.x_min) + self.padding_left
245 self._draw_grid_line(
246 (scaled_tick, self.padding_top),
247 (scaled_tick, self.height-self.padding_bottom),
249 w, h = label.get_size()
250 label.draw_text(wx.Point(scaled_tick-w/2, self.height-self.padding_bottom+TICK_LABEL_PADDING))
251 ##################################################
253 ##################################################
254 for tick, label in y_tick_labels:
255 scaled_tick = (self.height-self.padding_top-self.padding_bottom)*\
256 (1 - (tick/self.y_scalar-self.y_min)/(self.y_max-self.y_min)) + self.padding_top
257 self._draw_grid_line(
258 (self.padding_left, scaled_tick),
259 (self.width-self.padding_right, scaled_tick),
261 w, h = label.get_size()
262 label.draw_text(wx.Point(self.padding_left-w-TICK_LABEL_PADDING, scaled_tick-h/2))
263 ##################################################
265 ##################################################
266 GL.glColor3f(*GRID_BORDER_COLOR_SPEC)
270 self.width - self.padding_right - self.padding_left,
271 self.height - self.padding_top - self.padding_bottom,
274 ##################################################
276 ##################################################
278 title_label.draw_text(wx.Point(self.width/2.0, TITLE_LABEL_PADDING + title_label.get_size()[1]/2))
280 x_label.draw_text(wx.Point(
281 (self.width-self.padding_left-self.padding_right)/2.0 + self.padding_left,
282 self.height-(AXIS_LABEL_PADDING + x_label.get_size()[1]/2),
286 y_label.draw_text(wx.Point(
287 AXIS_LABEL_PADDING + y_label.get_size()[1]/2,
288 (self.height-self.padding_top-self.padding_bottom)/2.0 + self.padding_top,
292 def _get_tick_label(self, tick, unit):
294 Format the tick value and create a gl text.
295 @param tick the floating point tick value
296 @param unit the axis unit
297 @return the tick label text
299 if unit: tick_str = common.sci_format(tick)
300 else: tick_str = common.eng_format(tick)
301 return gltext.Text(tick_str, font_size=TICK_TEXT_FONT_SIZE)
303 def _get_ticks(self, min, max, step, scalar):
305 Determine the positions for the ticks.
306 @param min the lower bound
307 @param max the upper bound
308 @param step the grid spacing
309 @param scalar the grid scaling
310 @return a list of tick positions between min and max
316 #check for valid numbers
320 assert max - min > step
321 except AssertionError: return [-1, 1]
322 #determine the start and stop value
323 start = int(math.ceil(min/step))
324 stop = int(math.floor(max/step))
325 return [i*step*scalar for i in range(start, stop+1)]
327 def enable_grid_lines(self, enable=None):
329 Enable/disable the grid lines.
330 @param enable true to enable
331 @return the enable state when None
333 if enable is None: return self._enable_grid_lines
335 self._enable_grid_lines = enable
336 self._grid_cache.changed(True)
339 def _draw_grid_line(self, coor1, coor2):
341 Draw a dashed line from coor1 to coor2.
342 @param corr1 a tuple of x, y
343 @param corr2 a tuple of x, y
345 if not self.enable_grid_lines(): return
346 length = math.sqrt((coor1[0] - coor2[0])**2 + (coor1[1] - coor2[1])**2)
347 num_points = int(length/GRID_LINE_DASH_LEN)
348 #calculate points array
350 coor1[0] + i*(coor2[0]-coor1[0])/(num_points - 1),
351 coor1[1] + i*(coor2[1]-coor1[1])/(num_points - 1)
352 ) for i in range(num_points)]
354 GL.glColor3f(*GRID_LINE_COLOR_SPEC)
355 GL.glVertexPointerf(points)
356 GL.glDrawArrays(GL.GL_LINES, 0, len(points))
358 def _draw_rect(self, x, y, width, height, fill=True):
360 Draw a rectangle on the x, y plane.
361 X and Y are the top-left corner.
362 @param x the left position of the rectangle
363 @param y the top position of the rectangle
364 @param width the width of the rectangle
365 @param height the height of the rectangle
366 @param fill true to color inside of rectangle
368 GL.glBegin(fill and GL.GL_QUADS or GL.GL_LINE_LOOP)
370 GL.glVertex2f(x+width, y)
371 GL.glVertex2f(x+width, y+height)
372 GL.glVertex2f(x, y+height)
375 def _draw_point_label(self):
377 Draw the point label for the last mouse motion coordinate.
378 The mouse coordinate must be an X, Y tuple.
379 The label will be drawn at the X, Y coordinate.
380 The values of the X, Y coordinate will be scaled to the current X, Y bounds.
382 if not self.enable_point_label(): return
383 if not self._point_label_coordinate: return
384 x, y = self._point_label_coordinate
385 if x < self.padding_left or x > self.width-self.padding_right: return
386 if y < self.padding_top or y > self.height-self.padding_bottom: return
387 #scale to window bounds
388 x_win_scalar = float(x - self.padding_left)/(self.width-self.padding_left-self.padding_right)
389 y_win_scalar = float((self.height - y) - self.padding_bottom)/(self.height-self.padding_top-self.padding_bottom)
390 #scale to grid bounds
391 x_val = x_win_scalar*(self.x_max-self.x_min) + self.x_min
392 y_val = y_win_scalar*(self.y_max-self.y_min) + self.y_min
394 label_str = self._populate_point_label(x_val, y_val)
395 if not label_str: return
396 txt = gltext.Text(label_str, font_size=POINT_LABEL_FONT_SIZE)
397 w, h = txt.get_size()
399 GL.glColor3f(*POINT_LABEL_COLOR_SPEC)
400 if x > self.width/2: x -= w+2*POINT_LABEL_PADDING
401 self._draw_rect(x, y-h-2*POINT_LABEL_PADDING, w+2*POINT_LABEL_PADDING, h+2*POINT_LABEL_PADDING)
402 txt.draw_text(wx.Point(x+POINT_LABEL_PADDING, y-h-POINT_LABEL_PADDING))