mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-17 13:53:57 -07:00
Add some profiler region attributes. Render events in profile visualiser. Change profiler from GPL to MIT. This was SVN commit r10492.
763 lines
22 KiB
JavaScript
763 lines
22 KiB
JavaScript
// TODO: this code needs a load of cleaning up and documenting,
|
|
// and feature additions and general improvement and unrubbishing
|
|
|
|
// Item types returned by the engine
|
|
var ITEM_EVENT = 1;
|
|
var ITEM_ENTER = 2;
|
|
var ITEM_LEAVE = 3;
|
|
var ITEM_ATTRIBUTE = 4;
|
|
|
|
|
|
function hslToRgb(h, s, l, a)
|
|
{
|
|
var r, g, b;
|
|
|
|
if (s == 0)
|
|
{
|
|
r = g = b = l;
|
|
}
|
|
else
|
|
{
|
|
function hue2rgb(p, q, t)
|
|
{
|
|
if (t < 0) t += 1;
|
|
if (t > 1) t -= 1;
|
|
if (t < 1/6) return p + (q - p) * 6 * t;
|
|
if (t < 1/2) return q;
|
|
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
|
return p;
|
|
}
|
|
|
|
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
var p = 2 * l - q;
|
|
r = hue2rgb(p, q, h + 1/3);
|
|
g = hue2rgb(p, q, h);
|
|
b = hue2rgb(p, q, h - 1/3);
|
|
}
|
|
|
|
return 'rgba(' + Math.floor(r * 255) + ',' + Math.floor(g * 255) + ',' + Math.floor(b * 255) + ',' + a + ')';
|
|
}
|
|
|
|
function new_colour(id)
|
|
{
|
|
var hs = [0, 1/3, 2/3, 1/4, 2/4, 3/4, 1/5, 3/5, 2/5, 4/5];
|
|
var ss = [1, 0.5];
|
|
var ls = [0.8, 0.6, 0.9, 0.7];
|
|
return hslToRgb(hs[id % hs.length], ss[Math.floor(id / hs.length) % ss.length], ls[Math.floor(id / (hs.length*ss.length)) % ls.length], 1);
|
|
}
|
|
|
|
var g_used_colours = {};
|
|
|
|
|
|
var g_data;
|
|
|
|
function refresh()
|
|
{
|
|
$.ajax({
|
|
url: 'http://127.0.0.1:8000/overview',
|
|
dataType: 'json',
|
|
success: function (data) {
|
|
var threads = [];
|
|
data.threads.forEach(function(thread) {
|
|
var canvas = $('<canvas width="1600" height="128"></canvas>');
|
|
threads.push({'name': thread.name, 'canvas': canvas.get(0)});
|
|
});
|
|
var callback_data = { 'threads': threads, 'completed': 0 };
|
|
threads.forEach(function(thread) {
|
|
refresh_thread(thread, callback_data);
|
|
});
|
|
},
|
|
error: function (jqXHR, textStatus, errorThrown) {
|
|
alert('Failed to connect to server ("'+textStatus+'")');
|
|
}
|
|
});
|
|
}
|
|
|
|
function refresh_thread(thread, callback_data)
|
|
{
|
|
$.ajax({
|
|
url: 'http://127.0.0.1:8000/query',
|
|
dataType: 'json',
|
|
data: { 'thread': thread.name },
|
|
success: function (data) {
|
|
data.events.pop();
|
|
|
|
thread.data = data;
|
|
|
|
if (++callback_data.completed == callback_data.threads.length)
|
|
{
|
|
g_data = { 'threads': callback_data.threads };
|
|
|
|
//var range = {'numframes': 5};
|
|
var range = {'seconds': 0.05};
|
|
|
|
rebuild_canvases();
|
|
update_display(range);
|
|
}
|
|
},
|
|
error: function (jqXHR, textStatus, errorThrown) {
|
|
alert('Failed to connect to server ("'+textStatus+'")');
|
|
}
|
|
});
|
|
}
|
|
|
|
function rebuild_canvases()
|
|
{
|
|
g_data.canvas_frames = $('<canvas width="1600" height="128"></canvas>').get(0);
|
|
g_data.canvas_zoom = $('<canvas width="1600" height="128"></canvas>').get(0);
|
|
g_data.text_output = $('<pre></pre>').get(0);
|
|
|
|
set_frames_zoom_handlers(g_data.canvas_frames);
|
|
set_tooltip_handlers(g_data.canvas_frames);
|
|
|
|
$('#timelines').empty();
|
|
$('#timelines').append(g_data.canvas_frames);
|
|
g_data.threads.forEach(function(thread) {
|
|
$('#timelines').append($(thread.canvas));
|
|
});
|
|
$('#timelines').append(g_data.canvas_zoom);
|
|
$('#timelines').append(g_data.text_output);
|
|
}
|
|
|
|
function update_display(range)
|
|
{
|
|
$(g_data.text_output).empty();
|
|
|
|
var main_events = g_data.threads[0].data.events;
|
|
|
|
var processed_main = compute_intervals(main_events, range);
|
|
|
|
// display_top_items(main_events, g_data.text_output);
|
|
|
|
display_frames(processed_main, g_data.canvas_frames);
|
|
display_events(processed_main, g_data.canvas_frames);
|
|
|
|
$(g_data.threads[0].canvas).unbind();
|
|
$(g_data.canvas_zoom).unbind();
|
|
display_hierarchy(processed_main, processed_main, g_data.threads[0].canvas, {}, undefined);
|
|
set_zoom_handlers(processed_main, processed_main, g_data.threads[0].canvas, g_data.canvas_zoom);
|
|
set_tooltip_handlers(g_data.threads[0].canvas);
|
|
set_tooltip_handlers(g_data.canvas_zoom);
|
|
|
|
g_data.threads.slice(1).forEach(function(thread) {
|
|
var processed_data = compute_intervals(thread.data.events, {'tmin': processed_main.tmin, 'tmax': processed_main.tmax});
|
|
|
|
$(thread.canvas).unbind();
|
|
display_hierarchy(processed_main, processed_data, thread.canvas, {}, undefined);
|
|
set_zoom_handlers(processed_main, processed_data, thread.canvas, g_data.canvas_zoom);
|
|
set_tooltip_handlers(thread.canvas);
|
|
});
|
|
}
|
|
|
|
function display_top_items(data, output)
|
|
{
|
|
var items = {};
|
|
for (var i = 0; i < data.length; ++i)
|
|
{
|
|
var type = data[i][0];
|
|
if (!(type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE))
|
|
continue;
|
|
var id = data[i][2];
|
|
if (!items[id])
|
|
items[id] = { 'count': 0 };
|
|
items[id].count++;
|
|
}
|
|
|
|
var topitems = [];
|
|
for (var k in items)
|
|
topitems.push([k, items[k].count]);
|
|
topitems.sort(function(a, b) {
|
|
return b[1] - a[1];
|
|
});
|
|
topitems.splice(16);
|
|
|
|
topitems.forEach(function(item) {
|
|
output.appendChild(document.createTextNode(item[1] + 'x -- ' + item[0] + '\n'));
|
|
});
|
|
output.appendChild(document.createTextNode('(' + data.length + ' items)'));
|
|
}
|
|
|
|
function compute_intervals(data, range)
|
|
{
|
|
var start, end;
|
|
var tmin, tmax;
|
|
|
|
var frames = [];
|
|
var last_frame_time = undefined;
|
|
for (var i = 0; i < data.length; ++i)
|
|
{
|
|
if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart')
|
|
{
|
|
var t = data[i][1];
|
|
if (last_frame_time)
|
|
frames.push({'t0': last_frame_time, 't1': t});
|
|
last_frame_time = t;
|
|
}
|
|
}
|
|
|
|
if (range.numframes)
|
|
{
|
|
for (var i = data.length - 1; i > 0; --i)
|
|
{
|
|
if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart')
|
|
{
|
|
end = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
var framesfound = 0;
|
|
for (var i = end - 1; i > 0; --i)
|
|
{
|
|
if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart')
|
|
{
|
|
start = i;
|
|
if (++framesfound == range.numframes)
|
|
break;
|
|
}
|
|
}
|
|
|
|
tmin = data[start][1];
|
|
tmax = data[end][1];
|
|
}
|
|
else if (range.seconds)
|
|
{
|
|
var end = data.length - 1;
|
|
for (var i = end; i > 0; --i)
|
|
{
|
|
var type = data[i][0];
|
|
if (type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE)
|
|
{
|
|
tmax = data[i][1];
|
|
break;
|
|
}
|
|
}
|
|
tmin = tmax - range.seconds;
|
|
|
|
for (var i = end; i > 0; --i)
|
|
{
|
|
var type = data[i][0];
|
|
if ((type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) && data[i][1] < tmin)
|
|
break;
|
|
start = i;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
start = 0;
|
|
end = data.length - 1;
|
|
tmin = range.tmin;
|
|
tmax = range.tmax;
|
|
|
|
for (var i = data.length-1; i > 0; --i)
|
|
{
|
|
var type = data[i][0];
|
|
if ((type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) && data[i][1] < tmax)
|
|
{
|
|
end = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (var i = end; i > 0; --i)
|
|
{
|
|
var type = data[i][0];
|
|
if ((type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) && data[i][1] < tmin)
|
|
break;
|
|
start = i;
|
|
}
|
|
|
|
// Move the start/end outwards by another frame, so we don't lose data at the edges
|
|
while (start > 0)
|
|
{
|
|
--start;
|
|
if (data[start][0] == ITEM_EVENT && data[start][2] == '__framestart')
|
|
break;
|
|
}
|
|
while (end < data.length-1)
|
|
{
|
|
++end;
|
|
if (data[end][0] == ITEM_EVENT && data[end][2] == '__framestart')
|
|
break;
|
|
}
|
|
}
|
|
|
|
var num_colours = 0;
|
|
|
|
var events = [];
|
|
|
|
// Read events for the entire data period (not just start..end)
|
|
var lastWasEvent = false;
|
|
for (var i = 0; i < data.length; ++i)
|
|
{
|
|
if (data[i][0] == ITEM_EVENT)
|
|
{
|
|
events.push({'t': data[i][1], 'id': data[i][2]});
|
|
lastWasEvent = true;
|
|
}
|
|
else if (data[i][0] == ITEM_ATTRIBUTE)
|
|
{
|
|
if (lastWasEvent)
|
|
{
|
|
if (!events[events.length-1].attrs)
|
|
events[events.length-1].attrs = [];
|
|
events[events.length-1].attrs.push(data[i][1]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lastWasEvent = false;
|
|
}
|
|
}
|
|
|
|
|
|
var intervals = [];
|
|
|
|
// Read intervals from the focused data period (start..end)
|
|
var stack = [];
|
|
var lastT = 0;
|
|
var lastWasEvent = false;
|
|
for (var i = start; i <= end; ++i)
|
|
{
|
|
if (data[i][0] == ITEM_EVENT)
|
|
{
|
|
// if (data[i][1] < lastT)
|
|
// console.log('Time went backwards: ' + (data[i][1] - lastT));
|
|
|
|
lastT = data[i][1];
|
|
lastWasEvent = true;
|
|
}
|
|
else if (data[i][0] == ITEM_ENTER)
|
|
{
|
|
// if (data[i][1] < lastT)
|
|
// console.log('Time went backwards: ' + (data[i][1] - lastT));
|
|
|
|
stack.push({'t0': data[i][1], 'id': data[i][2]});
|
|
|
|
lastT = data[i][1];
|
|
lastWasEvent = false;
|
|
}
|
|
else if (data[i][0] == ITEM_LEAVE)
|
|
{
|
|
// if (data[i][1] < lastT)
|
|
// console.log('Time went backwards: ' + (data[i][1] - lastT));
|
|
|
|
lastT = data[i][1];
|
|
lastWasEvent = false;
|
|
|
|
if (!stack.length)
|
|
continue;
|
|
var interval = stack.pop();
|
|
|
|
if (data[i][2] != interval.id)
|
|
alert('inconsistent interval ids ('+interval.id+' / '+data[i][2]+')');
|
|
|
|
if (!g_used_colours[interval.id])
|
|
g_used_colours[interval.id] = new_colour(num_colours++);
|
|
|
|
interval.colour = g_used_colours[interval.id];
|
|
|
|
interval.t1 = data[i][1];
|
|
interval.duration = interval.t1 - interval.t0;
|
|
interval.depth = stack.length;
|
|
|
|
intervals.push(interval);
|
|
}
|
|
else if (data[i][0] == ITEM_ATTRIBUTE)
|
|
{
|
|
if (!lastWasEvent && stack.length)
|
|
{
|
|
if (!stack[stack.length-1].attrs)
|
|
stack[stack.length-1].attrs = [];
|
|
stack[stack.length-1].attrs.push(data[i][1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return { 'frames': frames, 'events': events, 'intervals': intervals, 'tmin': tmin, 'tmax': tmax };
|
|
}
|
|
|
|
function time_label(t)
|
|
{
|
|
if (t > 1e-3)
|
|
return (t * 1e3).toFixed(2) + 'ms';
|
|
else
|
|
return (t * 1e6).toFixed(2) + 'us';
|
|
}
|
|
|
|
function display_frames(data, canvas)
|
|
{
|
|
canvas._tooltips = [];
|
|
|
|
var ctx = canvas.getContext('2d');
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
ctx.save();
|
|
|
|
var xpadding = 8;
|
|
var padding_top = 40;
|
|
var width = canvas.width - xpadding*2;
|
|
var height = canvas.height - padding_top - 4;
|
|
|
|
var tmin = data.frames[0].t0;
|
|
var tmax = data.frames[data.frames.length-1].t1;
|
|
var dx = width / (tmax-tmin);
|
|
|
|
canvas._zoomData = {
|
|
'x_to_t': function(x) {
|
|
return tmin + (x - xpadding) / dx;
|
|
},
|
|
't_to_x': function(t) {
|
|
return (t - tmin) * dx + xpadding;
|
|
}
|
|
};
|
|
|
|
ctx.strokeStyle = 'rgb(0, 0, 0)';
|
|
ctx.fillStyle = 'rgb(255, 255, 255)';
|
|
for (var i = 0; i < data.frames.length; ++i)
|
|
{
|
|
var frame = data.frames[i];
|
|
|
|
var duration = frame.t1 - frame.t0;
|
|
var x0 = xpadding + dx*(frame.t0 - tmin);
|
|
var x1 = x0 + dx*duration;
|
|
var y1 = canvas.height;
|
|
var y0 = y1 - duration*1000;
|
|
|
|
ctx.beginPath();
|
|
ctx.rect(x0, y0, x1-x0, y1-y0);
|
|
ctx.stroke();
|
|
|
|
canvas._tooltips.push({
|
|
'x0': x0, 'x1': x1,
|
|
'y0': y0, 'y1': y1,
|
|
'text': function(frame, duration) { return function() {
|
|
var t = '<b>Frame</b><br>';
|
|
t += 'Length: ' + time_label(duration) + '<br>';
|
|
if (frame.attrs)
|
|
{
|
|
frame.attrs.forEach(function(attr) {
|
|
t += attr + '<br>';
|
|
});
|
|
}
|
|
return t;
|
|
}} (frame, duration)
|
|
});
|
|
}
|
|
|
|
ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)';
|
|
ctx.fillStyle = 'rgba(128, 128, 255, 0.2)';
|
|
ctx.beginPath();
|
|
ctx.rect(xpadding + dx*(data.tmin - tmin), 0, dx*(data.tmax - data.tmin), canvas.height);
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
function display_events(data, canvas)
|
|
{
|
|
var ctx = canvas.getContext('2d');
|
|
ctx.save();
|
|
|
|
var x_to_time = canvas._zoomData.x_to_t;
|
|
var time_to_x = canvas._zoomData.t_to_x;
|
|
|
|
for (var i = 0; i < data.events.length; ++i)
|
|
{
|
|
var event = data.events[i];
|
|
|
|
if (event.id == '__framestart')
|
|
continue;
|
|
|
|
if (event.id == 'gui event' && event.attrs && event.attrs[0] == 'type: mousemove')
|
|
continue;
|
|
|
|
var x = time_to_x(event.t);
|
|
var y = 32;
|
|
|
|
var x0 = x;
|
|
var x1 = x;
|
|
var y0 = y-4;
|
|
var y1 = y+4;
|
|
|
|
ctx.strokeStyle = 'rgb(255, 0, 0)';
|
|
ctx.beginPath();
|
|
ctx.moveTo(x0, y0);
|
|
ctx.lineTo(x1, y1);
|
|
ctx.stroke();
|
|
canvas._tooltips.push({
|
|
'x0': x0, 'x1': x1,
|
|
'y0': y0, 'y1': y1,
|
|
'text': function(event) { return function() {
|
|
var t = '<b>' + event.id + '</b><br>';
|
|
if (event.attrs)
|
|
{
|
|
event.attrs.forEach(function(attr) {
|
|
t += attr + '<br>';
|
|
});
|
|
}
|
|
return t;
|
|
}} (event)
|
|
});
|
|
}
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
function display_hierarchy(main_data, data, canvas, range, zoom)
|
|
{
|
|
canvas._tooltips = [];
|
|
|
|
var ctx = canvas.getContext('2d');
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
ctx.save();
|
|
|
|
ctx.font = '12px sans-serif';
|
|
|
|
var xpadding = 8;
|
|
var padding_top = 40;
|
|
var width = canvas.width - xpadding*2;
|
|
var height = canvas.height - padding_top - 4;
|
|
|
|
var tmin, tmax, start, end;
|
|
|
|
if (range.tmin)
|
|
{
|
|
tmin = range.tmin;
|
|
tmax = range.tmax;
|
|
}
|
|
else
|
|
{
|
|
tmin = data.tmin;
|
|
tmax = data.tmax;
|
|
}
|
|
|
|
canvas._hierarchyData = { 'range': range, 'tmin': tmin, 'tmax': tmax };
|
|
|
|
function time_to_x(t)
|
|
{
|
|
return xpadding + (t - tmin) / (tmax - tmin) * width;
|
|
}
|
|
|
|
function x_to_time(x)
|
|
{
|
|
return tmin + (x - xpadding) * (tmax - tmin) / width;
|
|
}
|
|
|
|
ctx.save();
|
|
ctx.textAlign = 'center';
|
|
ctx.strokeStyle = 'rgb(192, 192, 192)';
|
|
ctx.beginPath();
|
|
var precision = -3;
|
|
while ((tmax-tmin)*Math.pow(10, 3+precision) < 25)
|
|
++precision;
|
|
var ticks_per_sec = Math.pow(10, 3+precision);
|
|
var major_tick_interval = 5;
|
|
for (var i = 0; i < (tmax-tmin)*ticks_per_sec; ++i)
|
|
{
|
|
var major = (i % major_tick_interval == 0);
|
|
var x = Math.floor(time_to_x(tmin + i/ticks_per_sec));
|
|
ctx.moveTo(x-0.5, padding_top - (major ? 4 : 2));
|
|
ctx.lineTo(x-0.5, padding_top + height);
|
|
if (major)
|
|
ctx.fillText((i*1000/ticks_per_sec).toFixed(precision), x, padding_top - 8);
|
|
}
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
|
|
var BAR_SPACING = 16;
|
|
|
|
for (var i = 0; i < data.intervals.length; ++i)
|
|
{
|
|
var interval = data.intervals[i];
|
|
|
|
if (interval.tmax <= tmin || interval.tmin > tmax)
|
|
continue;
|
|
|
|
var label = interval.id;
|
|
if (interval.attrs)
|
|
{
|
|
if (/^\d+$/.exec(interval.attrs[0]))
|
|
label += ' ' + interval.attrs[0];
|
|
else
|
|
label += ' [...]';
|
|
}
|
|
var x0 = Math.floor(time_to_x(interval.t0));
|
|
var x1 = Math.floor(time_to_x(interval.t1));
|
|
var y0 = padding_top + interval.depth * BAR_SPACING;
|
|
var y1 = y0 + BAR_SPACING;
|
|
|
|
ctx.fillStyle = interval.colour;
|
|
ctx.strokeStyle = 'black';
|
|
ctx.beginPath();
|
|
ctx.rect(x0-0.5, y0-0.5, x1-x0, y1-y0);
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
ctx.fillStyle = 'black';
|
|
ctx.fillText(label, x0+2, y0+BAR_SPACING-4, Math.max(1, x1-x0-4));
|
|
|
|
canvas._tooltips.push({
|
|
'x0': x0, 'x1': x1,
|
|
'y0': y0, 'y1': y1,
|
|
'text': function(interval) { return function() {
|
|
var t = '<b>' + interval.id + '</b><br>';
|
|
t += 'Length: ' + time_label(interval.duration) + '<br>';
|
|
if (interval.attrs)
|
|
{
|
|
interval.attrs.forEach(function(attr) {
|
|
t += attr + '<br>';
|
|
});
|
|
}
|
|
return t;
|
|
}} (interval)
|
|
});
|
|
|
|
}
|
|
|
|
for (var i = 0; i < main_data.frames.length; ++i)
|
|
{
|
|
var frame = main_data.frames[i];
|
|
|
|
if (frame.t0 < tmin || frame.t0 > tmax)
|
|
continue;
|
|
|
|
var x = Math.floor(time_to_x(frame.t0));
|
|
|
|
ctx.save();
|
|
ctx.lineWidth = 3;
|
|
ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)';
|
|
ctx.beginPath();
|
|
ctx.moveTo(x+0.5, 0);
|
|
ctx.lineTo(x+0.5, canvas.height);
|
|
ctx.stroke();
|
|
ctx.fillText(((frame.t1 - frame.t0) * 1000).toFixed(0)+'ms', x+2, padding_top - 24);
|
|
ctx.restore();
|
|
}
|
|
|
|
if (zoom)
|
|
{
|
|
var x0 = time_to_x(zoom.tmin);
|
|
var x1 = time_to_x(zoom.tmax);
|
|
ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)';
|
|
ctx.fillStyle = 'rgba(128, 128, 255, 0.2)';
|
|
ctx.beginPath();
|
|
ctx.moveTo(x0+0.5, 0.5);
|
|
ctx.lineTo(x1+0.5, 0.5);
|
|
ctx.lineTo(x1+0.5 + 4, canvas.height-0.5);
|
|
ctx.lineTo(x0+0.5 - 4, canvas.height-0.5);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
}
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
function set_frames_zoom_handlers(canvas0)
|
|
{
|
|
function do_zoom(event)
|
|
{
|
|
var zdata = canvas0._zoomData;
|
|
|
|
var relativeX = event.pageX - this.offsetLeft;
|
|
var relativeY = event.pageY - this.offsetTop;
|
|
|
|
var width = 0.001 + 0.5 * relativeY / canvas0.height;
|
|
|
|
var tavg = zdata.x_to_t(relativeX);
|
|
var tmax = tavg + width/2;
|
|
var tmin = tavg - width/2;
|
|
var range = {'tmin': tmin, 'tmax': tmax};
|
|
update_display(range);
|
|
}
|
|
|
|
var mouse_is_down = false;
|
|
$(canvas0).unbind();
|
|
$(canvas0).mousedown(function(event) {
|
|
mouse_is_down = true;
|
|
do_zoom.call(this, event);
|
|
});
|
|
$(canvas0).mouseup(function(event) {
|
|
mouse_is_down = false;
|
|
});
|
|
$(canvas0).mousemove(function(event) {
|
|
if (mouse_is_down)
|
|
do_zoom.call(this, event);
|
|
});
|
|
}
|
|
|
|
function set_zoom_handlers(main_data, data, canvas0, canvas1)
|
|
{
|
|
function do_zoom(event)
|
|
{
|
|
var hdata = canvas0._hierarchyData;
|
|
|
|
function x_to_time(x)
|
|
{
|
|
return hdata.tmin + x * (hdata.tmax - hdata.tmin) / canvas0.width;
|
|
}
|
|
|
|
var relativeX = event.pageX - this.offsetLeft;
|
|
var relativeY = event.pageY - this.offsetTop;
|
|
var width = 8 + 64 * relativeY / canvas0.height;
|
|
var zoom = { tmin: x_to_time(relativeX-width/2), tmax: x_to_time(relativeX+width/2) };
|
|
display_hierarchy(main_data, data, canvas0, hdata.range, zoom);
|
|
display_hierarchy(main_data, data, canvas1, zoom, undefined);
|
|
}
|
|
|
|
var mouse_is_down = false;
|
|
$(canvas0).mousedown(function(event) {
|
|
mouse_is_down = true;
|
|
do_zoom.call(this, event);
|
|
});
|
|
$(canvas0).mouseup(function(event) {
|
|
mouse_is_down = false;
|
|
});
|
|
$(canvas0).mousemove(function(event) {
|
|
if (mouse_is_down)
|
|
do_zoom.call(this, event);
|
|
});
|
|
}
|
|
|
|
function set_tooltip_handlers(canvas)
|
|
{
|
|
function do_tooltip(event)
|
|
{
|
|
var tooltips = canvas._tooltips;
|
|
if (!tooltips)
|
|
return;
|
|
|
|
var relativeX = event.pageX - this.offsetLeft;
|
|
var relativeY = event.pageY - this.offsetTop;
|
|
|
|
var text = undefined;
|
|
for (var i = 0; i < tooltips.length; ++i)
|
|
{
|
|
var t = tooltips[i];
|
|
if (t.x0-1 <= relativeX && relativeX <= t.x1+1 && t.y0 <= relativeY && relativeY <= t.y1)
|
|
{
|
|
text = t.text();
|
|
break;
|
|
}
|
|
}
|
|
if (text)
|
|
{
|
|
if (text.length > 512)
|
|
$('#tooltip').addClass('long');
|
|
else
|
|
$('#tooltip').removeClass('long');
|
|
$('#tooltip').css('left', (event.pageX+16)+'px');
|
|
$('#tooltip').css('top', (event.pageY+8)+'px');
|
|
$('#tooltip').html(text);
|
|
$('#tooltip').css('visibility', 'visible');
|
|
}
|
|
else
|
|
{
|
|
$('#tooltip').css('visibility', 'hidden');
|
|
}
|
|
}
|
|
|
|
$(canvas).mousemove(function(event) {
|
|
do_tooltip.call(this, event);
|
|
});
|
|
}
|