2
Demo of a javascript inspector tool that shows a black bar over the active
3
value axis, as well as the values at the current index, with the numbers
4
shown in the colors matching the legend.
8
Run Python on this file which will generate six files,
11
plot_hover2_coords.png
13
plot_hover_coords_png_hover_data.js
14
plot_hover2_coords_png_hover_data.js
16
Alternatively, if you pass in '-e' or '--embedded' on the command-line, then
17
a single HTML file will be created that has all JavaScript and images
18
directly embedded into it.
20
The script should automatically load your webbrowser on the output file,
21
but if it does not, then manually open the file hover_coords_plot.html in
22
your browser to see the output.
24
Author: Judah De Paula <judah@enthought.com>
25
Date: November 21, 2008.
27
# Standard library imports
28
import os, sys, webbrowser, cStringIO
29
from base64 import encodestring
31
# Major library imports
33
from numpy import arange, searchsorted, where, array, vstack, linspace
34
from scipy.special import jn
38
import ArrayPlotData, Plot, PlotGraphicsContext, LinePlot
39
from chaco.example_support import COLOR_PALETTE
42
#-- Constants -----------------------------------------------------------------
46
#------------------------------------------------------------------------------
48
# In a real application, these templates should be their own files,
49
# and a real templating engine (ex. Mako) would make things more flexible.
50
#------------------------------------------------------------------------------
51
html_template_keys = {'filename1' : 'plot_hover_coords.png',
52
'filename2' : 'plot_hover2_coords.png',
53
'file1_src' : 'plot_hover_coords.png',
54
'file2_src' : 'plot_hover2_coords.png',
55
'hover_coords' :'src="hover_coords.js">',
56
'data1' :'src="plot_hover_coords_png_hover_data.js">',
57
'data2' :'src="plot_hover2_coords_png_hover_data.js">' }
60
# Turns into index.html.
64
<script type="text/javascript" %(hover_coords)s </script>
65
<script type="text/javascript" %(data1)s </script>
66
<script type="text/javascript" %(data2)s </script>
69
<!------------------ What gets shown onmouseover. ------------------>
71
style="background-color: White; color: Black; padding:
72
5px; position: absolute; visibility: hidden;"> 0
76
style="background-color: White; color: Black; padding:
77
0px; position: absolute; visibility: hidden;">
78
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAxJREFUCNdjYGBgAAAABAABJzQnCgAAAABJRU5ErkJggg=="
81
alt="black_pixel.inline"
82
id="black_pixel.inline" />
84
<!------------------ Demo plot ------------------------------------->
85
<div class = "tabbertab">
87
<img src="%(file1_src)s"
90
onmouseover="javascript:ShowHovers('%(filename1)s',
91
'Hover', 'BlackPixel')"
92
onmouseout="javascript:HideHovers(event,
93
'Hover', 'BlackPixel')" />
95
<img src="%(file2_src)s"
98
onmouseover="javascript:ShowHovers('%(filename2)s',
99
'Hover', 'BlackPixel')"
100
onmouseout="javascript:HideHovers(event,
101
'Hover', 'BlackPixel')" />
108
# Turns into plot_hover_coords_png_hover_data.js
109
javascript_data_template = """
110
// Create a new "CursorData" object
112
obj.height = %(height)s;
113
obj.border_width = %(border_width)s;
114
obj.padding_top = %(padding_top)s + 1;
115
obj.padding_left = %(padding_left)s;
116
obj.padding_bottom = %(padding_bottom)s;
117
obj.colors = Array( %(colors)s );
119
// Store it in the global CursorData, using the unique array_id as a key.
120
GlobalCursorData["%(array_id)s"] = obj
122
// Store all the data in the array
123
obj.data = Array( %(data_s)s );
127
# Turns into hover_coords.js. Absolutely no string substitution is done.
128
hover_coords_js_file = """
129
<!------------------ Javascript hover functions ----------------->
130
<!-- Depends upon *_hover_data.js files. -->
131
<!--------------------------------------------------------------->
137
// Maps an image name to a CursorData object that contains an array of
138
// data and plot visual attributes like height, padding, and border width.
139
GlobalCursorData = Array();
141
// The hash key into GlobalCursorArray that corresponds to the the current
142
// plot that the mouse is over.
143
var CurrentPlotKey = "";
145
// The ID of the <div> box that contains the cursor line image.
148
// The ID of the <img> of the cursor line image.
151
// The number of pixels to offset the readout box from cursor position
157
Convert an array of floats into an HTML formatted string ready for
160
function FormatToHTML(data) {
161
var colors = GlobalCursorData[CurrentPlotKey].colors;
162
var s = '<font size=2 face="Courier New">';
163
for (var count = 0; count < data.length; count++) {
164
s = s + '<font color=' + colors[count] + '>' + data[count] + '</font><br>';
166
if (data.length > 0) {
167
s = s.substr(0, s.length-4);
175
Slightly more clever get x function than the default. Limited testing.
177
function GetMouseX(event) {
179
event = window.event;
182
return event.clientX +
183
(document.documentElement.scrollLeft
184
? document.documentElement.scrollLeft
185
: document.body.scrollLeft);
186
} else if (event.pageX) {
195
Slightly more clever get y function than the default. Limited testing.
197
function GetMouseY(event) {
199
event = window.event;
202
return event.clientY +
203
(document.documentElement.scrollTop
204
? document.documentElement.scrollTop
205
: document.body.scrollTop);
206
} else if (event.pageY) {
215
Uses data registered by external *hover_data.js files.
217
Cannot figure out how to get relative coordinates. Have to calculate
218
it instead using the reference image element registered with the
221
function ScreenXToImageX(mouseX) {
222
var referenceX = document.getElementById(CurrentPlotKey);
223
var cursor_data = GlobalCursorData[CurrentPlotKey];
224
var relativeX = mouseX - cursor_data.padding_left
225
- cursor_data.border_width
232
Uses data registered by external *hover_data.js files.
234
The Screen pixel coordinate that the data pixels in the plot start.
236
function ScreenYPlotStart(event) {
237
var referenceElement = document.getElementById(CurrentPlotKey);
238
var cursor_data = GlobalCursorData[CurrentPlotKey];
239
return referenceElement.y + cursor_data.padding_top
240
+ cursor_data.border_width;
245
Uses data registered by external *hover_data.js files.
247
Given the browser X coordinate compute the plot screen X coordinate
248
and use that value to index into the data array of plot values for
251
function ScreenXToDataY(mouseX) {
253
var relativeX = ScreenXToImageX(mouseX);
254
var cursor_data = GlobalCursorData[CurrentPlotKey];
256
if (relativeX >= 0 && relativeX < cursor_data.data.length) {
257
dataY = cursor_data.data[relativeX];
258
// Hack to have bar hidden on the left and right side of images.
259
document.getElementById(LineElementID).style.visibility = 'visible';
260
document.getElementById(BoxElementID).style.visibility = 'visible';
263
// Hack to have bar hidden on the left and right side of images.
264
document.getElementById(LineElementID).style.visibility = 'hidden';
265
document.getElementById(BoxElementID).style.visibility = 'hidden';
272
Uses data registered by external *hover_data.js files.
274
Every time the mouse moves over the image, Follow() changes the hovering
275
object attributes so they move to the new location.
277
function Follow(event) {
279
var referenceElement = document.getElementById(CurrentPlotKey);
280
var element = document.getElementById(BoxElementID);
281
if (element != null) {
282
var style = element.style;
283
// ScreenXToDataY() hacked to have better control of line visibility.
284
//style.visibility = 'visible';
285
style.left = (parseInt(GetMouseX(event)) + _xOffset) + 'px';
286
style.top = (parseInt(GetMouseY(event)) + _yOffset) + 'px';
287
element.innerHTML = FormatToHTML(ScreenXToDataY(GetMouseX(event)));
291
var element = document.getElementById(LineElementID);
292
var image_element = document.getElementById("black_pixel.inline");
293
if (element != null) {
294
var style = element.style;
295
// ScreenXToDataY() hacked to have better control of line visibility.
296
//style.visibility = 'visible';
297
style.left = parseInt(GetMouseX(event)) + 'px';
298
style.top = parseInt(ScreenYPlotStart()) + 'px';
299
element.height = GlobalCursorData[CurrentPlotKey].height - 1;
300
image_element.height = element.height;
306
Abstracted out Show() that assigns the Follow() to mouse movements.
308
function Show(referenceElementID) {
309
var referenceElement = document.getElementById(referenceElementID);
310
if (referenceElement) {
311
referenceElement.onmousemove = Follow;
317
Abstracted out Hide() that assigns the Follow() to mouse movements.
318
Turns the hover object invisible and disables the Follow() listener.
319
FIXME: The listener is turned off for both hover objects even though
320
only one object is hidden by this call.
322
function Hide(referenceElementID, elementID) {
323
var divStyle = document.getElementById(elementID).style;
324
var referenceElement = document.getElementById(referenceElementID);
325
divStyle.visibility = 'hidden';
326
referenceElement.onmousemove = '';
331
Main entry point for onmouseover event of images. Sets the globals
332
to the active image the mouse just entered and turns on the hovers.
334
function ShowHovers(referenceElementID, boxElementID, lineElementID) {
335
//_dataIndex = plot_names.indexOf(referenceElementID);
336
CurrentPlotKey = referenceElementID;
337
BoxElementID = boxElementID;
338
LineElementID = lineElementID;
339
Show(CurrentPlotKey);
340
Show(CurrentPlotKey);
345
Main event call point for onmouseout event for images
347
function HideHovers(event, boxElementID, lineElementID) {
348
var relatedTarget = event.relatedTarget;
349
if ((relatedTarget == null) ||
350
(relatedTarget.id != boxElementID) &&
351
(relatedTarget.id != lineElementID) &&
352
(relatedTarget.id != 'black_pixel.inline')) {
353
Hide(CurrentPlotKey, boxElementID);
354
Hide(CurrentPlotKey, lineElementID);
360
#------------------------------------------------------------------------------
361
# Data generation functions for *_hover_data.js files.
362
#------------------------------------------------------------------------------
364
def get_pixel_data(segment, renderer, screen_width):
366
Take a segment of points and use a renderer to map them to screen coords.
370
Fast little function that only works if the samples are sorted.
371
FIXME: Need more explanation.
373
screen_points = renderer.map_screen(segment)
375
if len(screen_points) == 0:
378
s_index = screen_points[:,0].astype(int)
379
d_value = segment[:,1]
380
if len(s_index) != len(d_value):
381
raise ValueError('Data-to-screen mapping not 1-to-1.')
382
indices = searchsorted(s_index, arange(0, screen_width))
383
indices[where(indices == 0)] = 1
384
return d_value[indices-1]
387
def write_hover_coords(container, array_id, script_filename=None):
389
Create a JavaScript formatted file of screen index to data values.
394
Chaco container object such as a plot that contain renderers
395
and border padding information.
397
An identifier that will be used as the key when assigning the
398
data array to the global list of data arrays. This identifier
399
should be unique among all the scripts that will be loaded in
401
script_filename : str
402
Full path to the desired output javascript file. If no name
403
is passed in, then no file is written.
407
Returns the JavaScript data file as a string.
411
To have a PNG plot show the values of the curves in a browser,
412
JavaScript is used to get the screen X/Y and convert the X into the
413
corresponding plot data values. This function creates that mapping
414
array. A couple of extra padding variables are also exported so the
415
JavaScript can do some additional offset calculations.
417
screen_width = container.width
418
# For every renderer in the plot, create an array of the same length
419
# as the screen width that contains the data value for each screen
420
# position. Stack all of these arrays together into the 2D 'pixel_data'
421
# array, then pass it into the template for the hover_data javascript file.
424
for renderer_name in sorted(container.legend.plots.keys()):
425
renderer = container.legend.plots[renderer_name][0]
426
data_points = renderer._cached_data_pts
427
if not isinstance(renderer, LinePlot):
428
data_points = [data_points]
429
for segment in data_points:
430
segment_data.append(get_pixel_data(segment, renderer, screen_width))
431
colors.append(renderer.color_)
433
if len(segment_data) > 0:
434
pixel_data = vstack(segment_data).T
436
pixel_data = array([]).reshape(0, 2)
438
# Fill out a template using the just-created data.
445
r, g, b = [int(component * alpha * 255) for component in color[:3]]
446
colstrings.append('"#%02x%02x%02x"' % (r,g,b))
447
colors = ",".join(colstrings);
449
line_template = ",".join(['"%1.2f"'] * pixel_data.shape[1])
451
for row in pixel_data[:-1]:
452
data_s += '[' + line_template % tuple(row) + '],'
453
if len(pixel_data) > 0:
454
data_s += '[' + line_template % tuple(row) + ']'
456
template_keys = dict(height = container.height,
457
padding_top = container.padding_top,
458
padding_left = container.padding_left,
459
padding_bottom = container.padding_bottom,
460
border_width = container.border_width,
462
pixel_data = pixel_data,
466
# Write out and return the result.
467
output = javascript_data_template % template_keys
469
f = open(script_filename, 'wt')
476
#------------------------------------------------------------------------------
477
# Plot and renderer generation functions.
478
#------------------------------------------------------------------------------
479
def create_plot(num_plots=8, type='line'):
480
""" Create a single plot object, with multiple renderers. """
481
# This is a bit of a hack to work around that line widths don't scale
482
# with the GraphicsContext's CTM.
483
dpi_scale = DPI / 72.0
487
x = linspace(low, high, numpoints)
488
pd = ArrayPlotData(index=x)
489
p = Plot(pd, bgcolor="white", padding=50, border_visible=True)
490
for i in range(1,num_plots+2):
491
pd.set_data("y" + str(i), jn(i,x))
492
p.plot(("index", "y" + str(i)), color=tuple(COLOR_PALETTE[i]),
493
width = 2.0 * dpi_scale, type=type)
494
p.x_grid.visible = True
495
p.x_grid.line_width *= dpi_scale
496
p.y_grid.visible = True
497
p.y_grid.line_width *= dpi_scale
498
p.legend.visible = True
502
def draw_plot(filename, size=(800,600), num_plots=8, type='line', key=''):
503
""" Save the plot, and generate the hover_data file. """
504
container = create_plot(num_plots, type)
505
container.outer_bounds = list(size)
506
container.do_layout(force=True)
507
gc = PlotGraphicsContext(size, dpi=DPI)
508
gc.render_component(container)
511
script_filename = filename[:-4] + "_png_hover_data.js"
513
script_filename = None
514
plot = make_palettized_png_str(gc)
515
script_data = write_hover_coords(container, key, script_filename)
516
return (plot, script_data)
519
def make_palettized_png_str(gc):
520
""" Generate a png file in a string, base64 encoded. """
521
format = gc.format()[:-2].upper()
523
gc = gc.convert_pixel_format("rgba32")
524
img = Image.fromstring("RGBA",
525
(gc.width(), gc.height()), gc.bmp_array.tostring())
526
img2 = img.convert("P")
527
output_buf = cStringIO.StringIO()
528
img2.save(output_buf, 'png')
529
output = encodestring(output_buf.getvalue())
534
#------------------------------------------------------------------------------
536
#------------------------------------------------------------------------------
537
def main(embedded=False):
539
Create the files and load the output in a webbrowser.
541
# 1. Create the JavaScript hover tool file.
542
# Only doing this to keep the demo file self-contained.
543
target_path = os.path.join(os.getcwd(), 'hover_coords.js')
545
html_template_keys['hover_coords'] = '>\n%s\n' % hover_coords_js_file
547
f = open(target_path, 'wt')
548
f.write(hover_coords_js_file)
551
# 2. Create the dynamically generated JavaScript data files.
553
html_template_keys['file1_src'] = None
554
html_template_keys['file2_src'] = None
555
file1_strs = draw_plot(html_template_keys['file1_src'],
557
key=html_template_keys['filename1'])
558
file2_strs = draw_plot(html_template_keys['file2_src'],
562
key=html_template_keys['filename2'])
564
# 3. Choose the correct src type for the HTML file if embedded.
566
src1 = 'data:image/png;base64,' + file1_strs[0]
567
html_template_keys['file1_src'] = src1
568
src2 = 'data:image/png;base64,' + file2_strs[0]
569
html_template_keys['file2_src'] = src2
570
html_template_keys['data1'] = '>\n%s\n' % file1_strs[1]
571
html_template_keys['data2'] = '>\n%s\n' % file2_strs[1]
573
# 4. Create the HTML file.
574
out_html = os.path.join(os.getcwd(), 'plot_hover_coords.html')
575
f = open(out_html, 'wt')
576
f.write(html_template % html_template_keys)
579
# 5. Load the finished product.
581
webbrowser.open(out_html)
583
print 'Browser did not open properly. Exception %s. The results' \
584
'can be viewed with the file plot_hover_coords.html.' % str(e)
588
#===============================================================================
589
# # Demo class that is used by the demo.py application.
590
#===============================================================================
591
# NOTE: The Demo class is being created for the purpose of running this
592
# example using a TraitsDemo-like app (see examples/demo/demo.py in Traits3).
593
# The demo.py file looks for a 'demo' or 'popup' or 'modal popup' keyword
594
# when it executes this file, and displays a view for it.
596
# NOTE2: In this case, Demo class is just a mock object. Essentially we want to
597
# execute main instead of displaying a UI for Demo: so we hack this by
598
# overriding configure_traits and edit_traits to return a blank UI.
600
from traits.api import HasTraits
601
from traitsui.api import UI, Handler
603
class Demo(HasTraits):
605
def configure_traits(self, *args, **kws):
609
def edit_traits(self, *args, **kws):
611
return UI(handler=Handler())
615
if __name__ == "__main__":
616
if '-e' in sys.argv or '--embedded' in sys.argv:
620
#-- eof -----------------------------------------------------------------------