Added hovertip support back, now with dragging.

This commit is contained in:
AreWeFastYet 2012-11-29 07:00:04 +00:00
Родитель 791bd03f72
Коммит a5bd6245d6
15 изменённых файлов: 11980 добавлений и 155 удалений

Просмотреть файл

@ -21,5 +21,5 @@ source = mozilla-inbound
source = mozilla-inbound
[im]
source = ionmonkey
source = mozilla-inbound

Просмотреть файл

@ -12,6 +12,61 @@ Machines = [8, 9, 10, 11, 12]
# Increment is currently 1 day.
TimeIncrement = 60 * 60 * 24
def condense(lines, timelist, timemap, historical):
recent = len(timelist) - historical
for line in lines:
line['newdata'] = []
newtimelist = []
newtimemap = {}
# We want to take the N historical points and condense them into
# R slices, where R is the number of recent points.
increment = historical // recent
pos = 0
while pos < historical:
limit = min(pos + increment, historical)
for line in lines:
data = line['data']
newdata = line['newdata']
average = 0.0
count = 0
firstCset = None
lastCset = None
for point in data[pos:limit]:
if not point or not point['first']:
continue
if not firstCset:
firstCset = point['first']
lastCset = point['last']
average = ((average * count) + point['score']) / (count + 1)
count += 1
point = { 'first': firstCset,
'last': lastCset,
'score': average
}
newdata.append(point)
newtimemap[timelist[pos]] = len(newtimelist)
newtimelist.append(timelist[pos])
pos += increment
# Fix up the old lines.
for line in lines:
data = line['data']
newdata = line['newdata']
newdata.extend(data[historical:])
line['data'] = newdata
del line['newdata']
for t in timelist[historical:]:
newtimemap[t] = len(newtimelist)
newtimelist.append(t)
return lines, newtimelist, newtimemap
# The aggregate view attempts to coalesce all runs from a 24-hour period into
# one data point, by taking an average of all non-zero scores in that run. In
# order to ensure that each line has the same x-axis points, we take the
@ -79,61 +134,6 @@ def aggregate(runs, rows):
return points
def condense(lines, timelist, timemap, historical):
recent = len(timelist) - historical
for line in lines:
line['newdata'] = []
newtimelist = []
newtimemap = {}
# We want to take the N historical points and condense them into
# R slices, where R is the number of recent points.
increment = historical // recent
pos = 0
while pos < historical:
limit = min(pos + increment, historical)
for line in lines:
data = line['data']
newdata = line['newdata']
average = 0.0
count = 0
firstCset = None
lastCset = None
for point in data[pos:limit]:
if not point or not point['first']:
continue
if not firstCset:
firstCset = point['first']
lastCset = point['last']
average = ((average * count) + point['score']) / (count + 1)
count += 1
point = { 'first': firstCset,
'last': lastCset,
'score': average
}
newdata.append(point)
newtimemap[timelist[pos]] = len(newtimelist)
newtimelist.append(timelist[pos])
pos += increment
# Fix up the old lines.
for line in lines:
data = line['data']
newdata = line['newdata']
newdata.extend(data[historical:])
line['data'] = newdata
del line['newdata']
for t in timelist[historical:]:
newtimemap[t] = len(newtimelist)
newtimelist.append(t)
return lines, newtimelist, newtimemap
def aggregate_suite(cx, runs, machine, suite):
lines = [ ]
timemap = { }

30
style.css Normal file
Просмотреть файл

@ -0,0 +1,30 @@
body {
background-color:#FFF;
font:.6875em Verdana, sans-serif;
color:#000;
}
a {
color:#000;
text-decoration:underline;
}
a:hover {
text-decoration:none;
}
h1 {
font-size:2.265em;
font-weight:700;
}
.graph {
width: 555px;
height: 340px;
}
.graph-row {
text-align: center;
}
.graph-container {
width: 600px;
height: 340px;
display: inline-block;
}

Просмотреть файл

@ -5,6 +5,7 @@ var AWFY = { };
AWFY.refreshTime = 1000 * 60 * 5;
AWFY.machineId = 0;
AWFY.refresh = true;
AWFY.panes = [];
AWFY.queryParams = { };
AWFY.drawLegend = function () {
}
@ -53,7 +54,8 @@ AWFY.compute = function (xhr) {
aggregate: blobgraph.aggregate,
timelist: blobgraph.timelist,
timemap: blobgraph.timemap,
earliest: blobgraph.earliest
earliest: blobgraph.earliest,
info: blobgraph.lines
};
graphs[name] = graph;
}
@ -61,7 +63,13 @@ AWFY.compute = function (xhr) {
var data = { graphs: graphs };
// Everything built successfully, so now we can send this to be drawn.
this.draw(this, data);
for (var i = 0; i < this.panes.length; i++) {
var id = this.panes[i];
var elt = $('#' + id + '-graph');
var graph = data.graphs[id];
var display = new Display(this, elt, data, graph);
display.draw();
}
if (this.refresh)
window.setTimeout(this.query.bind(this), this.refreshTime);
@ -85,17 +93,25 @@ AWFY.query = function(name, callback) {
return;
}
try {
callback(xhr);
} catch (e) {
AWFY.onQueryFail();
}
callback(xhr);
};
xhr.open('GET', url, true);
xhr.send();
}
AWFY.startup = function() {
AWFY.onGraphHover = function (event, pos, item) {
var elt = $(event.target);
var display = elt.data('awfy-display');
display.onHover(event, pos, item);
}
AWFY.onGraphClick = function (event, pos, item) {
var elt = $(event.target);
var display = elt.data('awfy-display');
display.onClick(event, pos, item);
}
AWFY.startup = function () {
var query = window.location.search.substring(1);
var items = query.split('&');
for (var i = 0; i < items.length; i++) {
@ -108,6 +124,12 @@ AWFY.startup = function() {
else
this.machineId = 11;
for (var i = 0; i < this.panes.length; i++) {
var id = this.panes[i];
$('#' + id + '-graph').bind("plothover", this.onGraphHover.bind(this));
$('#' + id + '-graph').bind("plotclick", this.onGraphClick.bind(this));
}
this.query('aggregate-' + this.machineId + '.json', this.compute.bind(this));
}

Просмотреть файл

@ -2483,7 +2483,7 @@
if (series.points.symbol == "circle")
octx.arc(x, y, radius, 0, 2 * Math.PI, false);
else
series.points.symbol(octx, x, y, radius, false);
series.points.symbol(octx, x, y, radius, false, plot);
octx.closePath();
octx.stroke();
}

Просмотреть файл

@ -1,7 +1,105 @@
// vim: set ts=4 sw=4 tw=99 et:
function DrawGraph(awfy, elt, data, graph)
function Display(awfy, elt, data, graph)
{
this.awfy = awfy;
this.data = data;
this.graph = graph;
this.elt = elt;
this.elt.data('awfy-display', this);
// The last hovering tooltip we displayed, that has not been clicked.
this.hovering = null;
if (graph.aggregate) {
// Find the range of historical points.
for (var i = 0; i < graph.timelist.length; i++) {
if (graph.timelist[i] >= graph.earliest)
break;
}
if (i && i != graph.timelist.length)
this.historical = i;
}
}
Display.Months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
// Copy flot's tick algorithm.
Display.prototype.tickSize = function (min, max) {
var noTicks = 0.3 * Math.sqrt(this.elt.width());
var delta = (max - min) / noTicks;
var dec = -Math.floor(Math.log(delta) / Math.LN10);
var magn = Math.pow(10, -dec);
var norm = delta / magn;
var size;
if (norm < 1.5) {
size = 1;
} else if (norm < 3) {
size = 2;
if (norm > 2.25) {
size = 2.5;
++dec;
}
} else if (norm < 7.5) {
size = 5;
} else {
size = 10;
}
size *= magn;
return size;
};
Display.prototype.aggregateTicks = function () {
// Draw historical ticks at a higher density.
var ticks = this.tickSize(0, this.graph.timelist.length);
var list = [];
// This is all a bunch of hardcoded hacks for now.
var preticks = (ticks == 5) ? 6 : ticks;
var last_year = undefined;
var current_year = (new Date()).getFullYear();
for (var i = 0; i < this.historical; i += preticks) {
var d = new Date(this.graph.timelist[i] * 1000);
var text = Display.Months[d.getMonth()];
// Some graphs span over a year, so add in a hint when the year
// changes.
if ((i == 0 && d.getFullYear() != current_year) ||
(last_year && d.getFullYear() != last_year))
{
text += " " + d.getFullYear();
last_year = d.getFullYear();
}
// Add the tick mark, then try to add some more empty ones to
// make the graph appear denser.
list.push([i, text]);
if (preticks == 6) {
if (i + 2 < this.historical) {
list.push([i + 2, ""]);
if (i + 4 < this.historical)
list.push([i + 4, ""]);
}
}
}
// Figure out where we should start placing sparser lines, since
// we don't want them too close to the historical lines.
i = list[list.length - 1][0] + ticks;
// If the aggregate graph has both historical and recent points,
for (; i < this.graph.timelist.length; i += ticks) {
var d = new Date(this.graph.timelist[Math.floor(i)] * 1000);
var text = Display.Months[d.getMonth()] + " " + d.getDate();
list.push([i, text]);
}
return list;
}
Display.prototype.draw = function () {
var options = { };
options.lines = { show: true };
@ -11,112 +109,244 @@ function DrawGraph(awfy, elt, data, graph)
options.legend = { show: false };
options.xaxis = { };
options.yaxis = { min: 0 };
options.grid = { };
options.grid = { hoverable: true, clickable: true };
var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
var historical;
if (graph.aggregate) {
// Find the range of historical points.
for (var i = 0; i < graph.timelist.length; i++) {
if (graph.timelist[i] >= graph.earliest)
break;
}
if (i && i != graph.timelist.length)
historical = i;
}
// If the graph has both historical and recent points, indicated by
// the "historical" midpoint, then we change some graph formatting
// to reflect that part of the graph has a greater time density.
if (historical) {
options.points.symbol = function (ctx, x, y, radius, shadow, plot) {
if (this.historical) {
// If the graph has both historical and recent points, indicated by
// the "historical" midpoint, then we change some graph formatting
// to reflect that part of the graph has a greater time density.
//
// To make this work, we modified flot to pass in its plot variable
// when invoking this callback, in order to use c2p().
options.points.symbol = (function (ctx, x, y, radius, shadow, plot) {
var axis = plot.getAxes();
var rx = Math.round(axis.xaxis.c2p(x));
if (graph.timelist[rx] < graph.earliest) {
if (this.graph.timelist[rx] < this.graph.earliest) {
ctx.strokeRect(x - radius / 2, y - radius / 2, radius, radius);
ctx.clearRect(x - radius / 4, y - radius / 4, radius / 2, radius / 2);
} else {
ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);
}
}
}).bind(this);
// Copy flot's tick algorithm.
var sizefn = function (min, max) {
var noTicks = 0.3 * Math.sqrt(elt.width());
var delta = (max - min) / noTicks;
var dec = -Math.floor(Math.log(delta) / Math.LN10);
var magn = Math.pow(10, -dec);
var norm = delta / magn;
var size;
if (norm < 1.5) {
size = 1;
} else if (norm < 3) {
size = 2;
if (norm > 2.25) {
size = 2.5;
++dec;
}
} else if (norm < 7.5) {
size = 5;
} else {
size = 10;
}
size *= magn;
return size;
}
// If the aggregate graph has both historical and recent points,
var ticks = sizefn(0, graph.timelist.length);
options.xaxis.ticks = [];
for (var i = 0; i < historical; i += ticks) {
var d = new Date(graph.timelist[Math.floor(i)] * 1000);
var text = months[d.getMonth()] + " " + d.getDate();
options.xaxis.ticks.push([i, text]);
}
// Draw the recent ticks, but try to increase the density. This is
// all a bunch of special cased hacks for now, since all our graphs
// appear to compute ticksize = 5.
if (ticks == 5) {
// Make sure we don't skip too many lines in between.
if (i - historical >= 3)
i = historical + 2;
ticks = 6;
}
for (; i < graph.timelist.length; i += ticks) {
var d = new Date(graph.timelist[Math.floor(i)] * 1000);
var text = months[d.getMonth()] + " " + d.getDate();
options.xaxis.ticks.push([i, text]);
if (ticks == 6) {
options.xaxis.ticks.push([i + 2, ""]);
options.xaxis.ticks.push([i + 4, ""]);
}
}
options.xaxis.ticks = this.aggregateTicks();
}
if (!options.xaxis.ticks) {
options.xaxis.tickFormatter = function (v, axis) {
v = Math.round(v);
if (v < 0 || v >= graph.timelist.length)
if (v < 0 || v >= this.graph.timelist.length)
return '';
var t = graph.timelist[v];
var t = this.graph.timelist[v];
var d = new Date(t * 1000);
return months[d.getMonth()] + " " + d.getDate();
return Display.Months[d.getMonth()] + " " + d.getDate();
}
}
var plot = $.plot(elt, graph.lines, options);
this.plot = $.plot(this.elt, this.graph.lines, options);
}
function DrawFrontPage(awfy, data)
{
var graphs = data.graphs;
// Display.prototype.checkHoverDelta(pos) {
// if (!this.lastPos)
// return false;
// if (pos.pageX - this.lastPos.pageX >= 0 &&
// pos.pageX - this.lastPos.pageX <= 5 &&
// pos.pageY - this.lastPos.pageY >= 0 &&
// pos.pageY - this.lastPos.pageY <= 5)
// {
// return true;
// }
// return false;
// }
DrawGraph(awfy, $("#kraken-graph"), data, graphs['kraken']);
DrawGraph(awfy, $("#sunspider-graph"), data, graphs['ss']);
DrawGraph(awfy, $("#v8-graph"), data, graphs['v8real']);
Display.prototype.createToolTip = function (item, extended) {
var so = extended ? '<strong>' : '';
var sc = extended ? '</strong>' : '';
var x = item.datapoint[0];
var y = item.datapoint[1];
var text = so + 'score: ' + sc + y.toFixed() + '<br>';
// Figure out the line this corresponds to.
var line = this.graph.info[item.seriesIndex];
if (!line)
return;
// Find the point previous to this one.
var prev = null;
for (var i = x - 1; i >= 0; i--) {
if (line.data[i] && line.data[i].score) {
prev = line.data[i];
break;
}
}
if (prev) {
// Compute a difference.
var diff = Math.round((y - prev.score) * 10) / 10;
var perc = -Math.round(((y - prev.score) / prev.score) * 1000) / 10;
var better;
if ((perc < 0 && this.graph.direction == -1) ||
(perc > 0 && this.graph.direction == 1))
{
better = 'worse';
} else {
better = 'better';
}
perc = Math.abs(perc);
if (diff === diff) {
if (extended)
text += so + 'delta' + sc + ': ' + diff;
else
text += String.fromCharCode(916) + ': ' + diff;
if (this.graph.direction == -1)
text += 'ms';
text += ' (' + perc + '% ' + better + ')<br>';
}
}
// Find the vendor.
var mode = AWFYMaster.modes[line.modeid];
var vendor;
if (mode)
vendor = AWFYMaster.vendors[mode.vendor_id];
if (vendor) {
text += so + 'source: ' + sc +
vendor.browser +
' (' + mode.name + ')'+
'<br>';
}
// Find the datapoint.
var point = line.data[x];
if (extended && vendor) {
if (point.last) {
text += so + 'revs: ' + sc +
'<a href="' + vendor.url + point.first + '">' + point.first + '</a>' +
' to ' +
'<a href="' + vendor.url + point.last + '">' + point.last + '</a>' +
'<br>';
} else {
text += so + 'rev: ' + sc +
'<a href="' + vendor.url + point.first + '">' + point.first + '</a>' +
'<br>';
}
}
var pad = function (d) {
if (d == 0)
return '00';
else if (d < 10)
return '0' + d;
else
return '' + d;
}
// Format a year, if we should.
if (extended) {
var current_year = (new Date()).getFullYear();
var datefmt = function (t, forceYear) {
var d = new Date(t * 1000);
var text = Display.Months[d.getMonth()] + ' ' + d.getDate();
if (d.getFullYear() != current_year || forceYear)
text += ', ' + d.getFullYear();
if (d.getHours() || d.getMinutes()) {
text += ' ';
text += pad(d.getHours()) + ':' +
pad(d.getMinutes());
}
return text;
}
if (point.last && x < this.graph.timelist.length - 1) {
text += so + 'tested: ' + sc +
datefmt(this.graph.timelist[x]) + ' to ' +
datefmt(this.graph.timelist[x + 1], true) + '<br>';
} else {
text += so + 'tested: ' + sc +
datefmt(this.graph.timelist[x]) + '<br>';
}
} else {
// Include a short timestamp if we're looking at recent changesets.
var d = new Date(this.graph.timelist[x] * 1000);
var now = new Date();
var set = false;
if (now.getMonth() == d.getMonth() &&
now.getFullYear() == d.getFullYear())
{
if (now.getDate() == d.getDate()) {
text += so + 'tested: ' + sc + 'Today, ';
set = true;
} else if (now.getDate() - 1 == d.getDate()) {
text += so + 'tested: ' + sc + 'Yesterday, ';
set = true;
}
}
if (!set) {
text += so + 'tested: ' + sc;
if (x < this.historical)
text += 'around ';
text += Display.Months[d.getMonth()] + ' ' + d.getDate();
if (now.getFullYear() != d.getFullYear())
text += ', ' + d.getFullYear() + ' ';
else
text += ' ';
}
if (x >= this.historical)
text += pad(d.getHours()) + ':' + pad(d.getMinutes()) + '<br>';
}
return new ToolTip(item.pageX, item.pageY, item, text);
}
Display.prototype.onClick = function (event, pos, item) {
// Remove the existing hovertip.
if (this.hovering) {
this.hovering.remove();
this.hovering = null;
}
if (!item)
return;
var tooltip = this.createToolTip(item, true);
tooltip.drawFloating();
// The color of the line will be the series color.
var line = this.graph.info[item.seriesIndex];
if (!line)
return;
var mode = AWFYMaster.modes[line.modeid];
if (!mode)
return;
tooltip.attachLine(mode.color);
}
Display.prototype.areItemsEqual = function (item1, item2) {
return item1.seriesIndex == item2.seriesIndex &&
item1.dataIndex == item2.dataIndex &&
item1.datapoint[0] == item2.datapoint[0];
}
Display.prototype.onHover = function (event, pos, item) {
// Are we already hovering over something?
if (this.hovering) {
// If we're hovering over the same point, don't do anything.
if (item && this.areItemsEqual(item, this.hovering.item))
return;
// Otherwise, remove the div since we will redraw.
this.hovering.remove();
this.hovering = null;
}
if (!item)
return;
this.hovering = this.createToolTip(item, false);
this.hovering.drawBasic();
}

Просмотреть файл

@ -5,18 +5,21 @@
<meta http-equiv="content-language" content="en">
<title>ARE WE FAST YET?</title>
<link rel="stylesheet" title="Default Stylesheet" type="text/css" href="style.css">
<link rel="stylesheet" title="Default Stylesheet" type="text/css" href="jquery/css/jquery-ui-1.9.2.custom.css">
<link rel="shortcut icon" href="http://www.arewefastyet.com/awfy_favicon.png">
<script type="text/javascript" src="flot/jquery.js"></script>
<script type="text/javascript" src="jquery/jquery-1.8.3.min.js"></script>
<script type="text/javascript" src="jquery/jquery-ui-1.9.2.custom.min.js"></script>
<script type="text/javascript" src="flot/jquery.flot.js"></script>
<script type="text/javascript" src="data/master.js"></script>
<script type="text/javascript" src="awfy.js"></script>
<script type="text/javascript" src="frontpage.js"></script>
<script type="text/javascript" src="tooltip.js"></script>
</head>
<body>
<script type="text/javascript">
$(document).ready(function () {
AWFY.refresh = true;
AWFY.draw = DrawFrontPage;
AWFY.panes = ['kraken', 'ss', 'v8real'];
AWFY.startup();
});
</script>
@ -28,7 +31,7 @@
</div>
<div class="graph-container">
<div id="sunspider-label">sunspider time</div>
<div class="graph" id="sunspider-graph"><h2>Loading...</h2></div>
<div class="graph" id="ss-graph"><h2>Loading...</h2></div>
</div>
</div>
<br>
@ -38,7 +41,7 @@
<div class="graph-row">
<div class="graph-container">
<div id="v8-label">v8bench score</div>
<div class="graph" id="v8-graph"><h2>Loading...</h2></div>
<div class="graph" id="v8real-graph"><h2>Loading...</h2></div>
</div>
</div>
<br>

33
website/jquery/css/jquery-ui-1.9.2.custom.css поставляемый Normal file
Просмотреть файл

@ -0,0 +1,33 @@
/*! jQuery UI - v1.9.2 - 2012-11-28
* http://jqueryui.com
* Includes: jquery.ui.core.css
* Copyright (c) 2012 jQuery Foundation and other contributors Licensed MIT */
/* Layout helpers
----------------------------------*/
.ui-helper-hidden { display: none; }
.ui-helper-hidden-accessible { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
.ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; }
.ui-helper-clearfix:after { clear: both; }
.ui-helper-clearfix { zoom: 1; }
.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
/* Interaction Cues
----------------------------------*/
.ui-state-disabled { cursor: default !important; }
/* Icons
----------------------------------*/
/* states and images */
.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
/* Misc visuals
----------------------------------*/
/* Overlays */
.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }

6
website/jquery/css/jquery-ui-1.9.2.custom.min.css поставляемый Normal file
Просмотреть файл

@ -0,0 +1,6 @@
/*! jQuery UI - v1.9.2 - 2012-11-28
* http://jqueryui.com
* Includes: jquery.ui.core.css
* Copyright (c) 2012 jQuery Foundation and other contributors Licensed MIT */
.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{zoom:1}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:absolute;top:0;left:0;width:100%;height:100%}

9472
website/jquery/jquery-1.8.3.js поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

2
website/jquery/jquery-1.8.3.min.js поставляемый Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

1846
website/jquery/jquery-ui-1.9.2.custom.js поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

6
website/jquery/jquery-ui-1.9.2.custom.min.js поставляемый Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

65
website/style.css Normal file
Просмотреть файл

@ -0,0 +1,65 @@
body {
background-color:#FFF;
font:.6875em Verdana, sans-serif;
color:#000;
}
a {
color:#000;
text-decoration:underline;
}
a:hover {
text-decoration:none;
}
h1 {
font-size:2.265em;
font-weight:700;
}
.graph {
width: 555px;
height: 340px;
}
.graph-row {
text-align: center;
}
.graph-container {
width: 600px;
height: 340px;
display: inline-block;
}
.tooltip {
-moz-border-radius:.35em;
-webkit-border-radius:.35em;
background-color:#000;
color:#FFF;
display:none;
opacity:0.8;
padding:.25em;
position:absolute;
}
.tooltip a:link, .tooltip a:active, .tooltip a:visited {
color:#FFF;
text-decoration: underline;
}
.tooltip a:hover {
color:#FFF;
text-decoration: none;
}
.closeable {
padding: 15px;
opacity: 1.0;
text-align: left;
}
.closeButton {
color:#CCC;
position: absolute;
top: 5px;
right: 5px;
font-family: monospace;
cursor: pointer;
display: inline-block;
}

110
website/tooltip.js Normal file
Просмотреть файл

@ -0,0 +1,110 @@
// vim: set ts=4 sw=4 tw=99 et:
function ToolTip(x, y, item, contents)
{
this.x = x;
this.y = y;
this.item = item;
this.contents = contents;
}
ToolTip.prototype.drawFloating = function () {
var text = '<div class="tooltip closeable"></div>';
this.elt = $(text);
var ruler = $('<div class="tooltipRuler"></div>');
ruler.appendTo(this.elt);
var button = $('<a class="closeButton" href="#"></a>');
button.text('[x]');
button.appendTo(this.elt);
button.css('text-decoration', 'none');
button.click(this.remove.bind(this));
var span = $('<span>' + this.contents + '</span>');
span.appendTo(this.elt);
this.draw();
this.elt.draggable({ drag: this.onDrag.bind(this) });
}
ToolTip.prototype.drawBasic = function () {
var text = '<div class="tooltip">' + this.contents + '</div>';
this.elt = $(text);
this.draw();
}
ToolTip.prototype.draw = function () {
var tipWidth = 165;
var tipHeight = 75;
var xOffset = 5;
var yOffset = 5;
var ie = document.all && !window.opera;
var iebody = (document.compatMode == 'CSS1Compat')
? document.documentElement
: document.body;
var scrollLeft = ie ? iebody.scrollLeft : window.pageXOffset;
var scrollTop = ie ? iebody.scrollTop : window.pageYOffset;
var docWidth = ie ? iebody.clientWidth - 15 : window.innerWidth - 15;
var docHeight = ie ? iebody.clientHeight - 15 : window.innerHeight - 8;
var y = (this.y + tipHeight - scrollTop > docHeight)
? this.y - tipHeight - 5 - (yOffset * 2)
: this.y; // account for bottom edge
// account for right edge
this.elt.css({ top: this.y + yOffset});
if (this.x + tipWidth - scrollLeft > docWidth)
this.elt.css({ right: docWidth - this.x + xOffset });
else
this.elt.css({ left: this.x + xOffset });
this.elt.appendTo('body').fadeIn(200);
}
ToolTip.prototype.remove = function () {
if (this.svg)
this.svg.remove();
this.elt.remove();
}
ToolTip.prototype.midpoint = function () {
var offset = this.elt.offset();
var width = this.elt.width();
return { x: offset.left + width / 2, y: offset.top };
}
ToolTip.prototype.onDrag = function (event, ui) {
if (!this.line)
return;
this.line.setAttribute('x2', this.midpoint().x);
this.line.setAttribute('y2', this.midpoint().y);
this.svg.height($('body').height());
}
ToolTip.prototype.attachLine = function (color) {
// Now overlay a line from the point to the tooltip, ya.
var ns = "http://www.w3.org/2000/svg";
var svg = $(document.createElementNS(ns, 'svg'));
svg.css({ position: 'absolute',
left: 0,
top: 0,
'z-index': -1,
overflow: 'visible'
});
var line = document.createElementNS(ns, 'line');
line.setAttribute('x1', this.item.pageX);
line.setAttribute('x2', this.midpoint().x);
line.setAttribute('y1', this.item.pageY);
line.setAttribute('y2', this.midpoint().y);
line.setAttribute('stroke', color);
line.setAttribute('stroke-width', 2);
$(line).appendTo(svg);
svg.height($('body').height());
svg.appendTo('body');
this.svg = svg;
this.line = line;
}