- It's possible to embed the mobile tracker on any page. If you are developing a HAB project, you can add the tracker
- to your website. You can customize the tracker to fit. There are options to limit the visible vehicles to
- a specific callsign. Or two, or three. Many other options to play with. It's easy. Just visit the page below and check it out.
-
- Want to track with Google Earth instead? Just click here
-
+
+ Welcome to the Habitat Mobile Tracker beta. This webpage is specifically crafted with mobile devices in mind.
+ Just point the browser on your platform of choice - Android or iOS. The mobile tracker aims to provide all the
+ tracking goodness on your phone while you are out there chasing. You can run it just as well on your desktop!
+
Contribute
-
-
- Did you know the tracker is open-source? Check it out on
- github/habitat-mobile-tracker.
- Bug reports, suggestions and pull requests are welcome. You can also
- find us on IRC in #highaltitude at irc.freenode.org.
-
+
+
+ If you find any bugs or have a suggestion just find us on #highaltitude at irc.freenode.org.
+ Or on github/habitat-mobile-tracker
+
-
+
-
Settings
-
-
- Interpolate gaps in telemetry
-
-
-
-
-
-
- Hide welcome on start-up
-
-
-
-
-
-
- Imperial units
-
-
-
-
-
-
- Horizontal speed in hours
-
-
-
-
-
-
- Hide time display
-
-
-
-
-
-
- Hide receivers from the map
-
-
-
-
-
-
- Highlight selected vehicle
-
-
-
-
-
-
Overlays
-
+
Settings
+
Daylight overlay
@@ -164,14 +71,12 @@
Overlays
- APRS coverage
-
+ Imperial units
+
-
+
-
Other
-
Availability offline
@@ -188,11 +93,11 @@
Other
- Chase car equipped with radio receiver
+ Chase car equipped with radio reciever
- Force check for new version
+ Force refresh cache
@@ -200,119 +105,40 @@
Other
- Reloads appcache if necessary.
- The size of cache files is 1.2 MB.
- This does not include map tiles.
+ The size of cache files is around 600 KB.
+
-
-
-
Weather
-
-
-
-
Here you can access various weather overlays. This an experimental feature. Mobile users be aware that this can quickly eat your data allowance.
-
-
-
-
-
Chase car mode
-
-
- Enable
-
-
-
-
-
-
- Callsign
-
-
-
- Notice: If you enable this, your location will be uploaded to habitat; making it publicly visible on the map.
-
-
-
- Last updated
- never
-
-
- Latitude
- 0.000000
-
-
- Longitude
- 0.000000
-
-
- Altitude
- none
-
-
- Accuracy
- none
-
-
- Speed
- none
-
-
-
-
-
-
-
- UTC: ???
- Local: ???
-
-
-
-
- Azimuth: 360.0000
- 0° N
- Elevation: 90.0000
- 10000 km
-
No position available
-
No vehicle selected
-
-
-
-
-
-
Telemetry Graph
-
-
-
-
+
+
-
+
No vehicles :(
-
+
No vehicles :(
-
-
-
+
+
diff --git a/js/_jquery.flot.js b/js/_jquery.flot.js
deleted file mode 100644
index cd2849d..0000000
--- a/js/_jquery.flot.js
+++ /dev/null
@@ -1,3140 +0,0 @@
-(function(B){B.color={};B.color.make=function(F,E,C,D){var G={};G.r=F||0;G.g=E||0;G.b=C||0;G.a=D!=null?D:1;G.add=function(J,I){for(var H=0;H=1){return"rgb("+[G.r,G.g,G.b].join(",")+")"}else{return"rgba("+[G.r,G.g,G.b,G.a].join(",")+")"}};G.normalize=function(){function H(J,K,I){return KI?I:K)}G.r=H(0,parseInt(G.r),255);G.g=H(0,parseInt(G.g),255);G.b=H(0,parseInt(G.b),255);G.a=H(0,G.a,1);return G};G.clone=function(){return B.color.make(G.r,G.b,G.g,G.a)};return G.normalize()};B.color.extract=function(D,C){var E;do{E=D.css(C).toLowerCase();if(E!=""&&E!="transparent"){break}D=D.parent()}while(!B.nodeName(D.get(0),"body"));if(E=="rgba(0, 0, 0, 0)"){E="transparent"}return B.color.parse(E)};B.color.parse=function(F){var E,C=B.color.make;if(E=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10))}if(E=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10),parseFloat(E[4]))}if(E=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55)}if(E=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55,parseFloat(E[4]))}if(E=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)){return C(parseInt(E[1],16),parseInt(E[2],16),parseInt(E[3],16))}if(E=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)){return C(parseInt(E[1]+E[1],16),parseInt(E[2]+E[2],16),parseInt(E[3]+E[3],16))}var D=B.trim(F).toLowerCase();if(D=="transparent"){return C(255,255,255,0)}else{E=A[D]||[0,0,0];return C(E[0],E[1],E[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);
-
-// the actual Flot code
-(function($) {
- function Plot(placeholder, data_, options_, plugins) {
- // data is on the form:
- // [ series1, series2 ... ]
- // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
- // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }
-
- var series = [],
- options = {
- // the color theme used for graphs
- colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
- legend: {
- show: true,
- noColumns: 1, // number of colums in legend table
- labelFormatter: null, // fn: string -> string
- labelBoxBorderColor: "#ccc", // border color for the little label boxes
- container: null, // container (as jQuery object) to put legend in, null means default on top of graph
- position: "ne", // position of default legend container within plot
- margin: 5, // distance from grid edge to default legend container within plot
- backgroundColor: null, // null means auto-detect
- backgroundOpacity: 0.85 // set to 0 to avoid background
- },
- xaxis: {
- show: null, // null = auto-detect, true = always, false = never
- position: "bottom", // or "top"
- mode: null, // null or "time"
- color: null, // base color, labels, ticks
- tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)"
- transform: null, // null or f: number -> number to transform axis
- inverseTransform: null, // if transform is set, this should be the inverse function
- min: null, // min. value to show, null means set automatically
- max: null, // max. value to show, null means set automatically
- autoscaleMargin: null, // margin in % to add if auto-setting min/max
- ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
- tickFormatter: null, // fn: number -> string
- labelWidth: null, // size of tick labels in pixels
- labelHeight: null,
- reserveSpace: null, // whether to reserve space even if axis isn't shown
- tickLength: null, // size in pixels of ticks, or "full" for whole line
- alignTicksWithAxis: null, // axis number or null for no sync
-
- // mode specific options
- tickDecimals: null, // no. of decimals, null means auto
- tickSize: null, // number or [number, "unit"]
- minTickSize: null, // number or [number, "unit"]
- monthNames: null, // list of names of months
- timeformat: null, // format string to use
- twelveHourClock: false // 12 or 24 time in time mode
- },
- yaxis: {
- autoscaleMargin: 0.02,
- position: "left" // or "right"
- },
- xaxes: [],
- yaxes: [],
- series: {
- points: {
- show: false,
- radius: 3,
- lineWidth: 2, // in pixels
- fill: true,
- fillColor: "#ffffff",
- symbol: "circle" // or callback
- },
- lines: {
- // we don't put in show: false so we can see
- // whether lines were actively disabled
- lineWidth: 2, // in pixels
- fill: false,
- fillColor: null,
- steps: false
- },
- bars: {
- show: false,
- lineWidth: 2, // in pixels
- barWidth: 1, // in units of the x axis
- fill: true,
- fillColor: null,
- align: "left", // or "center"
- horizontal: false
- },
- shadowSize: 3
- },
- grid: {
- show: true,
- aboveData: false,
- color: "#545454", // primary color used for outline and labels
- backgroundColor: null, // null for transparent, else color
- borderColor: null, // set if different from the grid color
- tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)"
- labelMargin: 5, // in pixels
- axisMargin: 8, // in pixels
- borderWidth: 2, // in pixels
- minBorderMargin: null, // in pixels, null means taken from points radius
- markings: null, // array of ranges or fn: axes -> array of ranges
- markingsColor: "#f4f4f4",
- markingsLineWidth: 2,
- // interactive stuff
- clickable: false,
- hoverable: false,
- autoHighlight: true, // highlight in case mouse is near
- mouseActiveRadius: 10 // how far the mouse can be away to activate an item
- },
- hooks: {}
- },
- canvas = null, // the canvas for the plot itself
- overlay = null, // canvas for interactive stuff on top of plot
- eventHolder = null, // jQuery object that events should be bound to
- ctx = null, octx = null,
- xaxes = [], yaxes = [],
- plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
- canvasWidth = 0, canvasHeight = 0,
- plotWidth = 0, plotHeight = 0,
- hooks = {
- processOptions: [],
- processRawData: [],
- processDatapoints: [],
- drawSeries: [],
- draw: [],
- bindEvents: [],
- drawOverlay: [],
- shutdown: []
- },
- plot = this;
-
- // public functions
- plot.setData = setData;
- plot.setupGrid = setupGrid;
- plot.draw = draw;
- plot.getPlaceholder = function() { return placeholder; };
- plot.getCanvas = function() { return canvas; };
- plot.getPlotOffset = function() { return plotOffset; };
- plot.width = function () { return plotWidth; };
- plot.height = function () { return plotHeight; };
- plot.offset = function () {
- var o = eventHolder.offset();
- o.left += plotOffset.left;
- o.top += plotOffset.top;
- return o;
- };
- plot.getData = function () { return series; };
- plot.getAxes = function () {
- var res = {}, i;
- $.each(xaxes.concat(yaxes), function (_, axis) {
- if (axis)
- res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis;
- });
- return res;
- };
- plot.getXAxes = function () { return xaxes; };
- plot.getYAxes = function () { return yaxes; };
- plot.c2p = canvasToAxisCoords;
- plot.p2c = axisToCanvasCoords;
- plot.getOptions = function () { return options; };
- plot.highlight = highlight;
- plot.unhighlight = unhighlight;
- plot.triggerRedrawOverlay = triggerRedrawOverlay;
- plot.pointOffset = function(point) {
- return {
- left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left),
- top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top)
- };
- };
- plot.shutdown = shutdown;
- plot.resize = function () {
- getCanvasDimensions();
- resizeCanvas(canvas);
- resizeCanvas(overlay);
- };
-
- // public attributes
- plot.hooks = hooks;
-
- // initialize
- initPlugins(plot);
- parseOptions(options_);
- setupCanvases();
- setData(data_);
- setupGrid();
- draw();
- bindEvents();
-
-
- function executeHooks(hook, args) {
- args = [plot].concat(args);
- for (var i = 0; i < hook.length; ++i)
- hook[i].apply(this, args);
- }
-
- function initPlugins() {
- for (var i = 0; i < plugins.length; ++i) {
- var p = plugins[i];
- p.init(plot);
- if (p.options)
- $.extend(true, options, p.options);
- }
- }
-
- function parseOptions(opts) {
- var i;
-
- $.extend(true, options, opts);
-
- if (options.xaxis.color == null)
- options.xaxis.color = options.grid.color;
- if (options.yaxis.color == null)
- options.yaxis.color = options.grid.color;
-
- if (options.xaxis.tickColor == null) // backwards-compatibility
- options.xaxis.tickColor = options.grid.tickColor;
- if (options.yaxis.tickColor == null) // backwards-compatibility
- options.yaxis.tickColor = options.grid.tickColor;
-
- if (options.grid.borderColor == null)
- options.grid.borderColor = options.grid.color;
- if (options.grid.tickColor == null)
- options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString();
-
- // fill in defaults in axes, copy at least always the
- // first as the rest of the code assumes it'll be there
- for (i = 0; i < Math.max(1, options.xaxes.length); ++i)
- options.xaxes[i] = $.extend(true, {}, options.xaxis, options.xaxes[i]);
- for (i = 0; i < Math.max(1, options.yaxes.length); ++i)
- options.yaxes[i] = $.extend(true, {}, options.yaxis, options.yaxes[i]);
-
- // backwards compatibility, to be removed in future
- if (options.xaxis.noTicks && options.xaxis.ticks == null)
- options.xaxis.ticks = options.xaxis.noTicks;
- if (options.yaxis.noTicks && options.yaxis.ticks == null)
- options.yaxis.ticks = options.yaxis.noTicks;
- if (options.x2axis) {
- options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis);
- options.xaxes[1].position = "top";
- }
- if (options.y2axis) {
- options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis);
- options.yaxes[1].position = "right";
- }
- if (options.grid.coloredAreas)
- options.grid.markings = options.grid.coloredAreas;
- if (options.grid.coloredAreasColor)
- options.grid.markingsColor = options.grid.coloredAreasColor;
- if (options.lines)
- $.extend(true, options.series.lines, options.lines);
- if (options.points)
- $.extend(true, options.series.points, options.points);
- if (options.bars)
- $.extend(true, options.series.bars, options.bars);
- if (options.shadowSize != null)
- options.series.shadowSize = options.shadowSize;
-
- // save options on axes for future reference
- for (i = 0; i < options.xaxes.length; ++i)
- getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i];
- for (i = 0; i < options.yaxes.length; ++i)
- getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i];
-
- // add hooks from options
- for (var n in hooks)
- if (options.hooks[n] && options.hooks[n].length)
- hooks[n] = hooks[n].concat(options.hooks[n]);
-
- executeHooks(hooks.processOptions, [options]);
- }
-
- function setData(d) {
- series = parseData(d);
- fillInSeriesOptions();
- processData();
- }
-
- function parseData(d) {
- var res = [];
- for (var i = 0; i < d.length; ++i) {
- var s = $.extend(true, {}, options.series);
-
- if (d[i].data != null) {
- s.data = d[i].data; // move the data instead of deep-copy
- delete d[i].data;
-
- $.extend(true, s, d[i]);
-
- d[i].data = s.data;
- }
- else
- s.data = d[i];
- res.push(s);
- }
-
- return res;
- }
-
- function axisNumber(obj, coord) {
- var a = obj[coord + "axis"];
- if (typeof a == "object") // if we got a real axis, extract number
- a = a.n;
- if (typeof a != "number")
- a = 1; // default to first axis
- return a;
- }
-
- function allAxes() {
- // return flat array without annoying null entries
- return $.grep(xaxes.concat(yaxes), function (a) { return a; });
- }
-
- function canvasToAxisCoords(pos) {
- // return an object with x/y corresponding to all used axes
- var res = {}, i, axis;
- for (i = 0; i < xaxes.length; ++i) {
- axis = xaxes[i];
- if (axis && axis.used)
- res["x" + axis.n] = axis.c2p(pos.left);
- }
-
- for (i = 0; i < yaxes.length; ++i) {
- axis = yaxes[i];
- if (axis && axis.used)
- res["y" + axis.n] = axis.c2p(pos.top);
- }
-
- if (res.x1 !== undefined)
- res.x = res.x1;
- if (res.y1 !== undefined)
- res.y = res.y1;
-
- return res;
- }
-
- function axisToCanvasCoords(pos) {
- // get canvas coords from the first pair of x/y found in pos
- var res = {}, i, axis, key;
-
- for (i = 0; i < xaxes.length; ++i) {
- axis = xaxes[i];
- if (axis && axis.used) {
- key = "x" + axis.n;
- if (pos[key] == null && axis.n == 1)
- key = "x";
-
- if (pos[key] != null) {
- res.left = axis.p2c(pos[key]);
- break;
- }
- }
- }
-
- for (i = 0; i < yaxes.length; ++i) {
- axis = yaxes[i];
- if (axis && axis.used) {
- key = "y" + axis.n;
- if (pos[key] == null && axis.n == 1)
- key = "y";
-
- if (pos[key] != null) {
- res.top = axis.p2c(pos[key]);
- break;
- }
- }
- }
-
- return res;
- }
-
- function getOrCreateAxis(axes, number) {
- if (!axes[number - 1])
- axes[number - 1] = {
- n: number, // save the number for future reference
- direction: axes == xaxes ? "x" : "y",
- options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis)
- };
-
- return axes[number - 1];
- }
-
- function fillInSeriesOptions() {
- var i;
-
- // collect what we already got of colors
- var neededColors = series.length,
- usedColors = [],
- assignedColors = [];
- for (i = 0; i < series.length; ++i) {
- var sc = series[i].color;
- if (sc != null) {
- --neededColors;
- if (typeof sc == "number")
- assignedColors.push(sc);
- else
- usedColors.push($.color.parse(series[i].color));
- }
- }
-
- // we might need to generate more colors if higher indices
- // are assigned
- for (i = 0; i < assignedColors.length; ++i) {
- neededColors = Math.max(neededColors, assignedColors[i] + 1);
- }
-
- // produce colors as needed
- var colors = [], variation = 0;
- i = 0;
- while (colors.length < neededColors) {
- var c;
- if (options.colors.length == i) // check degenerate case
- c = $.color.make(100, 100, 100);
- else
- c = $.color.parse(options.colors[i]);
-
- // vary color if needed
- var sign = variation % 2 == 1 ? -1 : 1;
- c.scale('rgb', 1 + sign * Math.ceil(variation / 2) * 0.2)
-
- // FIXME: if we're getting to close to something else,
- // we should probably skip this one
- colors.push(c);
-
- ++i;
- if (i >= options.colors.length) {
- i = 0;
- ++variation;
- }
- }
-
- // fill in the options
- var colori = 0, s;
- for (i = 0; i < series.length; ++i) {
- s = series[i];
-
- // assign colors
- if (s.color == null) {
- s.color = colors[colori].toString();
- ++colori;
- }
- else if (typeof s.color == "number")
- s.color = colors[s.color].toString();
-
- // turn on lines automatically in case nothing is set
- if (s.lines.show == null) {
- var v, show = true;
- for (v in s)
- if (s[v] && s[v].show) {
- show = false;
- break;
- }
- if (show)
- s.lines.show = true;
- }
-
- // setup axes
- s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x"));
- s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y"));
- }
- }
-
- function processData() {
- var topSentry = Number.POSITIVE_INFINITY,
- bottomSentry = Number.NEGATIVE_INFINITY,
- fakeInfinity = Number.MAX_VALUE,
- i, j, k, m, length,
- s, points, ps, x, y, axis, val, f, p;
-
- function updateAxis(axis, min, max) {
- if (min < axis.datamin && min != -fakeInfinity)
- axis.datamin = min;
- if (max > axis.datamax && max != fakeInfinity)
- axis.datamax = max;
- }
-
- $.each(allAxes(), function (_, axis) {
- // init axis
- axis.datamin = topSentry;
- axis.datamax = bottomSentry;
- axis.used = false;
- });
-
- for (i = 0; i < series.length; ++i) {
- s = series[i];
- s.datapoints = { points: [] };
-
- executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]);
- }
-
- // first pass: clean and copy data
- for (i = 0; i < series.length; ++i) {
- s = series[i];
-
- var data = s.data, format = s.datapoints.format;
-
- if (!format) {
- format = [];
- // find out how to copy
- format.push({ x: true, number: true, required: true });
- format.push({ y: true, number: true, required: true });
-
- if (s.bars.show || (s.lines.show && s.lines.fill)) {
- format.push({ y: true, number: true, required: false, defaultValue: 0 });
- if (s.bars.horizontal) {
- delete format[format.length - 1].y;
- format[format.length - 1].x = true;
- }
- }
-
- s.datapoints.format = format;
- }
-
- if (s.datapoints.pointsize != null)
- continue; // already filled in
-
- s.datapoints.pointsize = format.length;
-
- ps = s.datapoints.pointsize;
- points = s.datapoints.points;
-
- insertSteps = s.lines.show && s.lines.steps;
- s.xaxis.used = s.yaxis.used = true;
-
- for (j = k = 0; j < data.length; ++j, k += ps) {
- p = data[j];
-
- var nullify = p == null;
- if (!nullify) {
- for (m = 0; m < ps; ++m) {
- val = p[m];
- f = format[m];
-
- if (f) {
- if (f.number && val != null) {
- val = +val; // convert to number
- if (isNaN(val))
- val = null;
- else if (val == Infinity)
- val = fakeInfinity;
- else if (val == -Infinity)
- val = -fakeInfinity;
- }
-
- if (val == null) {
- if (f.required)
- nullify = true;
-
- if (f.defaultValue != null)
- val = f.defaultValue;
- }
- }
-
- points[k + m] = val;
- }
- }
-
- if (nullify) {
- for (m = 0; m < ps; ++m) {
- val = points[k + m];
- if (val != null) {
- f = format[m];
- // extract min/max info
- if (f.x)
- updateAxis(s.xaxis, val, val);
- if (f.y)
- updateAxis(s.yaxis, val, val);
- }
- points[k + m] = null;
- }
- }
- else {
- // a little bit of line specific stuff that
- // perhaps shouldn't be here, but lacking
- // better means...
- if (insertSteps && k > 0
- && points[k - ps] != null
- && points[k - ps] != points[k]
- && points[k - ps + 1] != points[k + 1]) {
- // copy the point to make room for a middle point
- for (m = 0; m < ps; ++m)
- points[k + ps + m] = points[k + m];
-
- // middle point has same y
- points[k + 1] = points[k - ps + 1];
-
- // we've added a point, better reflect that
- k += ps;
- }
- }
- }
- }
-
- // give the hooks a chance to run
- for (i = 0; i < series.length; ++i) {
- s = series[i];
-
- executeHooks(hooks.processDatapoints, [ s, s.datapoints]);
- }
-
- // second pass: find datamax/datamin for auto-scaling
- for (i = 0; i < series.length; ++i) {
- s = series[i];
- points = s.datapoints.points,
- ps = s.datapoints.pointsize;
-
- var xmin = topSentry, ymin = topSentry,
- xmax = bottomSentry, ymax = bottomSentry;
-
- for (j = 0; j < points.length; j += ps) {
- if (points[j] == null)
- continue;
-
- for (m = 0; m < ps; ++m) {
- val = points[j + m];
- f = format[m];
- if (!f || val == fakeInfinity || val == -fakeInfinity)
- continue;
-
- if (f.x) {
- if (val < xmin)
- xmin = val;
- if (val > xmax)
- xmax = val;
- }
- if (f.y) {
- if (val < ymin)
- ymin = val;
- if (val > ymax)
- ymax = val;
- }
- }
- }
-
- if (s.bars.show) {
- // make sure we got room for the bar on the dancing floor
- var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2;
- if (s.bars.horizontal) {
- ymin += delta;
- ymax += delta + s.bars.barWidth;
- }
- else {
- xmin += delta;
- xmax += delta + s.bars.barWidth;
- }
- }
-
- updateAxis(s.xaxis, xmin, xmax);
- updateAxis(s.yaxis, ymin, ymax);
- }
-
- $.each(allAxes(), function (_, axis) {
- if (axis.datamin == topSentry)
- axis.datamin = null;
- if (axis.datamax == bottomSentry)
- axis.datamax = null;
- });
- }
-
- function makeCanvas(skipPositioning, cls) {
- var c = document.createElement('canvas');
- c.className = cls;
- c.width = canvasWidth;
- c.height = canvasHeight;
-
- if (!skipPositioning)
- $(c).css({ position: 'absolute', left: 0, top: 0 });
-
- $(c).appendTo(placeholder);
-
- if (!c.getContext) // excanvas hack
- c = window.G_vmlCanvasManager.initElement(c);
-
- // used for resetting in case we get replotted
- c.getContext("2d").save();
-
- return c;
- }
-
- function getCanvasDimensions() {
- canvasWidth = placeholder.width();
- canvasHeight = placeholder.height();
-
- if (canvasWidth <= 0 || canvasHeight <= 0)
- throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight;
- }
-
- function resizeCanvas(c) {
- // resizing should reset the state (excanvas seems to be
- // buggy though)
- if (c.width != canvasWidth)
- c.width = canvasWidth;
-
- if (c.height != canvasHeight)
- c.height = canvasHeight;
-
- // so try to get back to the initial state (even if it's
- // gone now, this should be safe according to the spec)
- var cctx = c.getContext("2d");
- cctx.restore();
-
- // and save again
- cctx.save();
- }
-
- function setupCanvases() {
- var reused,
- existingCanvas = placeholder.children("canvas.base"),
- existingOverlay = placeholder.children("canvas.overlay");
-
- if (existingCanvas.length == 0 || existingOverlay == 0) {
- // init everything
-
- placeholder.html(""); // make sure placeholder is clear
-
- placeholder.css({ padding: 0 }); // padding messes up the positioning
-
- if (placeholder.css("position") == 'static')
- placeholder.css("position", "relative"); // for positioning labels and overlay
-
- getCanvasDimensions();
-
- canvas = makeCanvas(true, "base");
- overlay = makeCanvas(false, "overlay"); // overlay canvas for interactive features
-
- reused = false;
- }
- else {
- // reuse existing elements
-
- canvas = existingCanvas.get(0);
- overlay = existingOverlay.get(0);
-
- reused = true;
- }
-
- ctx = canvas.getContext("2d");
- octx = overlay.getContext("2d");
-
- // we include the canvas in the event holder too, because IE 7
- // sometimes has trouble with the stacking order
- eventHolder = $([overlay, canvas]);
-
- if (reused) {
- // run shutdown in the old plot object
- placeholder.data("plot").shutdown();
-
- // reset reused canvases
- plot.resize();
-
- // make sure overlay pixels are cleared (canvas is cleared when we redraw)
- octx.clearRect(0, 0, canvasWidth, canvasHeight);
-
- // then whack any remaining obvious garbage left
- eventHolder.unbind();
- placeholder.children().not([canvas, overlay]).remove();
- }
-
- // save in case we get replotted
- placeholder.data("plot", plot);
- }
-
- function bindEvents() {
- // bind events
- if (options.grid.hoverable) {
- eventHolder.mousemove(onMouseMove);
- eventHolder.mouseleave(onMouseLeave);
- }
-
- if (options.grid.clickable)
- eventHolder.click(onClick);
-
- executeHooks(hooks.bindEvents, [eventHolder]);
- }
-
- function shutdown() {
- if (redrawTimeout)
- clearTimeout(redrawTimeout);
-
- eventHolder.unbind("mousemove", onMouseMove);
- eventHolder.unbind("mouseleave", onMouseLeave);
- eventHolder.unbind("click", onClick);
-
- executeHooks(hooks.shutdown, [eventHolder]);
- }
-
- function setTransformationHelpers(axis) {
- // set helper functions on the axis, assumes plot area
- // has been computed already
-
- function identity(x) { return x; }
-
- var s, m, t = axis.options.transform || identity,
- it = axis.options.inverseTransform;
-
- // precompute how much the axis is scaling a point
- // in canvas space
- if (axis.direction == "x") {
- s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min));
- m = Math.min(t(axis.max), t(axis.min));
- }
- else {
- s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min));
- s = -s;
- m = Math.max(t(axis.max), t(axis.min));
- }
-
- // data point to canvas coordinate
- if (t == identity) // slight optimization
- axis.p2c = function (p) { return (p - m) * s; };
- else
- axis.p2c = function (p) { return (t(p) - m) * s; };
- // canvas coordinate to data point
- if (!it)
- axis.c2p = function (c) { return m + c / s; };
- else
- axis.c2p = function (c) { return it(m + c / s); };
- }
-
- function measureTickLabels(axis) {
- var opts = axis.options, i, ticks = axis.ticks || [], labels = [],
- l, w = opts.labelWidth, h = opts.labelHeight, dummyDiv;
-
- function makeDummyDiv(labels, width) {
- return $('
' +
- '
'
- + labels.join("") + '
')
- .appendTo(placeholder);
- }
-
- if (axis.direction == "x") {
- // to avoid measuring the widths of the labels (it's slow), we
- // construct fixed-size boxes and put the labels inside
- // them, we don't need the exact figures and the
- // fixed-size box content is easy to center
- if (w == null)
- w = Math.floor(canvasWidth / (ticks.length > 0 ? ticks.length : 1));
-
- // measure x label heights
- if (h == null) {
- labels = [];
- for (i = 0; i < ticks.length; ++i) {
- l = ticks[i].label;
- if (l)
- labels.push('
' + l + '
');
- }
-
- if (labels.length > 0) {
- // stick them all in the same div and measure
- // collective height
- labels.push('');
- dummyDiv = makeDummyDiv(labels, "width:10000px;");
- h = dummyDiv.height();
- dummyDiv.remove();
- }
- }
- }
- else if (w == null || h == null) {
- // calculate y label dimensions
- for (i = 0; i < ticks.length; ++i) {
- l = ticks[i].label;
- if (l)
- labels.push('
' + l + '
');
- }
-
- if (labels.length > 0) {
- dummyDiv = makeDummyDiv(labels, "");
- if (w == null)
- w = dummyDiv.children().width();
- if (h == null)
- h = dummyDiv.find("div.tickLabel").height();
- dummyDiv.remove();
- }
- }
-
- if (w == null)
- w = 0;
- if (h == null)
- h = 0;
-
- axis.labelWidth = w;
- axis.labelHeight = h;
- }
-
- function allocateAxisBoxFirstPhase(axis) {
- // find the bounding box of the axis by looking at label
- // widths/heights and ticks, make room by diminishing the
- // plotOffset
-
- var lw = axis.labelWidth,
- lh = axis.labelHeight,
- pos = axis.options.position,
- tickLength = axis.options.tickLength,
- axismargin = options.grid.axisMargin,
- padding = options.grid.labelMargin,
- all = axis.direction == "x" ? xaxes : yaxes,
- index;
-
- // determine axis margin
- var samePosition = $.grep(all, function (a) {
- return a && a.options.position == pos && a.reserveSpace;
- });
- if ($.inArray(axis, samePosition) == samePosition.length - 1)
- axismargin = 0; // outermost
-
- // determine tick length - if we're innermost, we can use "full"
- if (tickLength == null)
- tickLength = "full";
-
- var sameDirection = $.grep(all, function (a) {
- return a && a.reserveSpace;
- });
-
- var innermost = $.inArray(axis, sameDirection) == 0;
- if (!innermost && tickLength == "full")
- tickLength = 5;
-
- if (!isNaN(+tickLength))
- padding += +tickLength;
-
- // compute box
- if (axis.direction == "x") {
- lh += padding;
-
- if (pos == "bottom") {
- plotOffset.bottom += lh + axismargin;
- axis.box = { top: canvasHeight - plotOffset.bottom, height: lh };
- }
- else {
- axis.box = { top: plotOffset.top + axismargin, height: lh };
- plotOffset.top += lh + axismargin;
- }
- }
- else {
- lw += padding;
-
- if (pos == "left") {
- axis.box = { left: plotOffset.left + axismargin, width: lw };
- plotOffset.left += lw + axismargin;
- }
- else {
- plotOffset.right += lw + axismargin;
- axis.box = { left: canvasWidth - plotOffset.right, width: lw };
- }
- }
-
- // save for future reference
- axis.position = pos;
- axis.tickLength = tickLength;
- axis.box.padding = padding;
- axis.innermost = innermost;
- }
-
- function allocateAxisBoxSecondPhase(axis) {
- // set remaining bounding box coordinates
- if (axis.direction == "x") {
- axis.box.left = plotOffset.left;
- axis.box.width = plotWidth;
- }
- else {
- axis.box.top = plotOffset.top;
- axis.box.height = plotHeight;
- }
- }
-
- function setupGrid() {
- var i, axes = allAxes();
-
- // first calculate the plot and axis box dimensions
-
- $.each(axes, function (_, axis) {
- axis.show = axis.options.show;
- if (axis.show == null)
- axis.show = axis.used; // by default an axis is visible if it's got data
-
- axis.reserveSpace = axis.show || axis.options.reserveSpace;
-
- setRange(axis);
- });
-
- allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace; });
-
- plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = 0;
- if (options.grid.show) {
- $.each(allocatedAxes, function (_, axis) {
- // make the ticks
- setupTickGeneration(axis);
- setTicks(axis);
- snapRangeToTicks(axis, axis.ticks);
-
- // find labelWidth/Height for axis
- measureTickLabels(axis);
- });
-
- // with all dimensions in house, we can compute the
- // axis boxes, start from the outside (reverse order)
- for (i = allocatedAxes.length - 1; i >= 0; --i)
- allocateAxisBoxFirstPhase(allocatedAxes[i]);
-
- // make sure we've got enough space for things that
- // might stick out
- var minMargin = options.grid.minBorderMargin;
- if (minMargin == null) {
- minMargin = 0;
- for (i = 0; i < series.length; ++i)
- minMargin = Math.max(minMargin, series[i].points.radius + series[i].points.lineWidth/2);
- }
-
- for (var a in plotOffset) {
- plotOffset[a] += options.grid.borderWidth;
- plotOffset[a] = Math.max(minMargin, plotOffset[a]);
- }
- }
-
- plotWidth = canvasWidth - plotOffset.left - plotOffset.right;
- plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top;
-
- // now we got the proper plotWidth/Height, we can compute the scaling
- $.each(axes, function (_, axis) {
- setTransformationHelpers(axis);
- });
-
- if (options.grid.show) {
- $.each(allocatedAxes, function (_, axis) {
- allocateAxisBoxSecondPhase(axis);
- });
-
- insertAxisLabels();
- }
-
- insertLegend();
- }
-
- function setRange(axis) {
- var opts = axis.options,
- min = +(opts.min != null ? opts.min : axis.datamin),
- max = +(opts.max != null ? opts.max : axis.datamax),
- delta = max - min;
-
- if (delta == 0.0) {
- // degenerate case
- var widen = max == 0 ? 1 : 0.01;
-
- if (opts.min == null)
- min -= widen;
- // always widen max if we couldn't widen min to ensure we
- // don't fall into min == max which doesn't work
- if (opts.max == null || opts.min != null)
- max += widen;
- }
- else {
- // consider autoscaling
- var margin = opts.autoscaleMargin;
- if (margin != null) {
- if (opts.min == null) {
- min -= delta * margin;
- // make sure we don't go below zero if all values
- // are positive
- if (min < 0 && axis.datamin != null && axis.datamin >= 0)
- min = 0;
- }
- if (opts.max == null) {
- max += delta * margin;
- if (max > 0 && axis.datamax != null && axis.datamax <= 0)
- max = 0;
- }
- }
- }
- axis.min = min;
- axis.max = max;
- }
-
- function setupTickGeneration(axis) {
- var opts = axis.options;
-
- // estimate number of ticks
- var noTicks;
- if (typeof opts.ticks == "number" && opts.ticks > 0)
- noTicks = opts.ticks;
- else
- // heuristic based on the model a*sqrt(x) fitted to
- // some data points that seemed reasonable
- noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? canvasWidth : canvasHeight);
-
- var delta = (axis.max - axis.min) / noTicks,
- size, generator, unit, formatter, i, magn, norm;
-
- if (opts.mode == "time") {
- // pretty handling of time
-
- // map of app. size of time units in milliseconds
- var timeUnitSize = {
- "second": 1000,
- "minute": 60 * 1000,
- "hour": 60 * 60 * 1000,
- "day": 24 * 60 * 60 * 1000,
- "month": 30 * 24 * 60 * 60 * 1000,
- "year": 365.2425 * 24 * 60 * 60 * 1000
- };
-
-
- // the allowed tick sizes, after 1 year we use
- // an integer algorithm
- var spec = [
- [1, "second"], [2, "second"], [5, "second"], [10, "second"],
- [30, "second"],
- [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
- [30, "minute"],
- [1, "hour"], [2, "hour"], [4, "hour"],
- [8, "hour"], [12, "hour"],
- [1, "day"], [2, "day"], [3, "day"],
- [0.25, "month"], [0.5, "month"], [1, "month"],
- [2, "month"], [3, "month"], [6, "month"],
- [1, "year"]
- ];
-
- var minSize = 0;
- if (opts.minTickSize != null) {
- if (typeof opts.tickSize == "number")
- minSize = opts.tickSize;
- else
- minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
- }
-
- for (var i = 0; i < spec.length - 1; ++i)
- if (delta < (spec[i][0] * timeUnitSize[spec[i][1]]
- + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
- && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize)
- break;
- size = spec[i][0];
- unit = spec[i][1];
-
- // special-case the possibility of several years
- if (unit == "year") {
- magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10));
- norm = (delta / timeUnitSize.year) / magn;
- if (norm < 1.5)
- size = 1;
- else if (norm < 3)
- size = 2;
- else if (norm < 7.5)
- size = 5;
- else
- size = 10;
-
- size *= magn;
- }
-
- axis.tickSize = opts.tickSize || [size, unit];
-
- generator = function(axis) {
- var ticks = [],
- tickSize = axis.tickSize[0], unit = axis.tickSize[1],
- d = new Date(axis.min);
-
- var step = tickSize * timeUnitSize[unit];
-
- if (unit == "second")
- d.setSeconds(floorInBase(d.getSeconds(), tickSize));
- if (unit == "minute")
- d.setMinutes(floorInBase(d.getMinutes(), tickSize));
- if (unit == "hour")
- d.setHours(floorInBase(d.getHours(), tickSize));
- if (unit == "month")
- d.setMonth(floorInBase(d.getMonth(), tickSize));
- if (unit == "year")
- d.setFullYear(floorInBase(d.getFullYear(), tickSize));
-
- // reset smaller components
- d.setMilliseconds(0);
- if (step >= timeUnitSize.minute)
- d.setSeconds(0);
- if (step >= timeUnitSize.hour)
- d.setMinutes(0);
- if (step >= timeUnitSize.day)
- d.setHours(0);
- if (step >= timeUnitSize.day * 4)
- d.setDate(1);
- if (step >= timeUnitSize.year)
- d.setMonth(0);
-
-
- var carry = 0, v = Number.NaN, prev;
- do {
- prev = v;
- v = d.getTime();
- ticks.push(v);
- if (unit == "month") {
- if (tickSize < 1) {
- // a bit complicated - we'll divide the month
- // up but we need to take care of fractions
- // so we don't end up in the middle of a day
- d.setDate(1);
- var start = d.getTime();
- d.setMonth(d.getMonth() + 1);
- var end = d.getTime();
- d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
- carry = d.getHours();
- d.setHours(0);
- }
- else
- d.setMonth(d.getMonth() + tickSize);
- }
- else if (unit == "year") {
- d.setFullYear(d.getFullYear() + tickSize);
- }
- else
- d.setTime(v + step);
- } while (v < axis.max && v != prev);
-
- return ticks;
- };
-
- formatter = function (v, axis) {
- var d = new Date(v);
-
- // first check global format
- if (opts.timeformat != null)
- return $.plot.formatDate(d, opts.timeformat, opts.monthNames);
-
- var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
- var span = axis.max - axis.min;
- var suffix = (opts.twelveHourClock) ? " %p" : "";
-
- if (t < timeUnitSize.minute)
- fmt = "%h:%M:%S" + suffix;
- else if (t < timeUnitSize.day) {
- if (span < 2 * timeUnitSize.day)
- fmt = "%h:%M" + suffix;
- else
- fmt = "%b %d %h:%M" + suffix;
- }
- else if (t < timeUnitSize.month)
- fmt = "%b %d";
- else if (t < timeUnitSize.year) {
- if (span < timeUnitSize.year)
- fmt = "%b";
- else
- fmt = "%b %y";
- }
- else
- fmt = "%y";
-
- return $.plot.formatDate(d, fmt, opts.monthNames);
- };
- }
- else {
- // pretty rounding of base-10 numbers
- var maxDec = opts.tickDecimals;
- var dec = -Math.floor(Math.log(delta) / Math.LN10);
- if (maxDec != null && dec > maxDec)
- dec = maxDec;
-
- magn = Math.pow(10, -dec);
- norm = delta / magn; // norm is between 1.0 and 10.0
-
- if (norm < 1.5)
- size = 1;
- else if (norm < 3) {
- size = 2;
- // special case for 2.5, requires an extra decimal
- if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
- size = 2.5;
- ++dec;
- }
- }
- else if (norm < 7.5)
- size = 5;
- else
- size = 10;
-
- size *= magn;
-
- if (opts.minTickSize != null && size < opts.minTickSize)
- size = opts.minTickSize;
-
- axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
- axis.tickSize = opts.tickSize || size;
-
- generator = function (axis) {
- var ticks = [];
-
- // spew out all possible ticks
- var start = floorInBase(axis.min, axis.tickSize),
- i = 0, v = Number.NaN, prev;
- do {
- prev = v;
- v = start + i * axis.tickSize;
- ticks.push(v);
- ++i;
- } while (v < axis.max && v != prev);
- return ticks;
- };
-
- formatter = function (v, axis) {
- return v.toFixed(axis.tickDecimals);
- };
- }
-
- if (opts.alignTicksWithAxis != null) {
- var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1];
- if (otherAxis && otherAxis.used && otherAxis != axis) {
- // consider snapping min/max to outermost nice ticks
- var niceTicks = generator(axis);
- if (niceTicks.length > 0) {
- if (opts.min == null)
- axis.min = Math.min(axis.min, niceTicks[0]);
- if (opts.max == null && niceTicks.length > 1)
- axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]);
- }
-
- generator = function (axis) {
- // copy ticks, scaled to this axis
- var ticks = [], v, i;
- for (i = 0; i < otherAxis.ticks.length; ++i) {
- v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min);
- v = axis.min + v * (axis.max - axis.min);
- ticks.push(v);
- }
- return ticks;
- };
-
- // we might need an extra decimal since forced
- // ticks don't necessarily fit naturally
- if (axis.mode != "time" && opts.tickDecimals == null) {
- var extraDec = Math.max(0, -Math.floor(Math.log(delta) / Math.LN10) + 1),
- ts = generator(axis);
-
- // only proceed if the tick interval rounded
- // with an extra decimal doesn't give us a
- // zero at end
- if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec))))
- axis.tickDecimals = extraDec;
- }
- }
- }
-
- axis.tickGenerator = generator;
- if ($.isFunction(opts.tickFormatter))
- axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); };
- else
- axis.tickFormatter = formatter;
- }
-
- function setTicks(axis) {
- var oticks = axis.options.ticks, ticks = [];
- if (oticks == null || (typeof oticks == "number" && oticks > 0))
- ticks = axis.tickGenerator(axis);
- else if (oticks) {
- if ($.isFunction(oticks))
- // generate the ticks
- ticks = oticks({ min: axis.min, max: axis.max });
- else
- ticks = oticks;
- }
-
- // clean up/labelify the supplied ticks, copy them over
- var i, v;
- axis.ticks = [];
- for (i = 0; i < ticks.length; ++i) {
- var label = null;
- var t = ticks[i];
- if (typeof t == "object") {
- v = +t[0];
- if (t.length > 1)
- label = t[1];
- }
- else
- v = +t;
- if (label == null)
- label = axis.tickFormatter(v, axis);
- if (!isNaN(v))
- axis.ticks.push({ v: v, label: label });
- }
- }
-
- function snapRangeToTicks(axis, ticks) {
- if (axis.options.autoscaleMargin && ticks.length > 0) {
- // snap to ticks
- if (axis.options.min == null)
- axis.min = Math.min(axis.min, ticks[0].v);
- if (axis.options.max == null && ticks.length > 1)
- axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);
- }
- }
-
- function draw() {
- ctx.clearRect(0, 0, canvasWidth, canvasHeight);
-
- var grid = options.grid;
-
- // draw background, if any
- if (grid.show && grid.backgroundColor)
- drawBackground();
-
- if (grid.show && !grid.aboveData)
- drawGrid();
-
- for (var i = 0; i < series.length; ++i) {
- executeHooks(hooks.drawSeries, [ctx, series[i]]);
- drawSeries(series[i]);
- }
-
- executeHooks(hooks.draw, [ctx]);
-
- if (grid.show && grid.aboveData)
- drawGrid();
- }
-
- function extractRange(ranges, coord) {
- var axis, from, to, key, axes = allAxes();
-
- for (i = 0; i < axes.length; ++i) {
- axis = axes[i];
- if (axis.direction == coord) {
- key = coord + axis.n + "axis";
- if (!ranges[key] && axis.n == 1)
- key = coord + "axis"; // support x1axis as xaxis
- if (ranges[key]) {
- from = ranges[key].from;
- to = ranges[key].to;
- break;
- }
- }
- }
-
- // backwards-compat stuff - to be removed in future
- if (!ranges[key]) {
- axis = coord == "x" ? xaxes[0] : yaxes[0];
- from = ranges[coord + "1"];
- to = ranges[coord + "2"];
- }
-
- // auto-reverse as an added bonus
- if (from != null && to != null && from > to) {
- var tmp = from;
- from = to;
- to = tmp;
- }
-
- return { from: from, to: to, axis: axis };
- }
-
- function drawBackground() {
- ctx.save();
- ctx.translate(plotOffset.left, plotOffset.top);
-
- ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
- ctx.fillRect(0, 0, plotWidth, plotHeight);
- ctx.restore();
- }
-
- function drawGrid() {
- var i;
-
- ctx.save();
- ctx.translate(plotOffset.left, plotOffset.top);
-
- // draw markings
- var markings = options.grid.markings;
- if (markings) {
- if ($.isFunction(markings)) {
- var axes = plot.getAxes();
- // xmin etc. is backwards compatibility, to be
- // removed in the future
- axes.xmin = axes.xaxis.min;
- axes.xmax = axes.xaxis.max;
- axes.ymin = axes.yaxis.min;
- axes.ymax = axes.yaxis.max;
-
- markings = markings(axes);
- }
-
- for (i = 0; i < markings.length; ++i) {
- var m = markings[i],
- xrange = extractRange(m, "x"),
- yrange = extractRange(m, "y");
-
- // fill in missing
- if (xrange.from == null)
- xrange.from = xrange.axis.min;
- if (xrange.to == null)
- xrange.to = xrange.axis.max;
- if (yrange.from == null)
- yrange.from = yrange.axis.min;
- if (yrange.to == null)
- yrange.to = yrange.axis.max;
-
- // clip
- if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
- yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)
- continue;
-
- xrange.from = Math.max(xrange.from, xrange.axis.min);
- xrange.to = Math.min(xrange.to, xrange.axis.max);
- yrange.from = Math.max(yrange.from, yrange.axis.min);
- yrange.to = Math.min(yrange.to, yrange.axis.max);
-
- if (xrange.from == xrange.to && yrange.from == yrange.to)
- continue;
-
- // then draw
- xrange.from = xrange.axis.p2c(xrange.from);
- xrange.to = xrange.axis.p2c(xrange.to);
- yrange.from = yrange.axis.p2c(yrange.from);
- yrange.to = yrange.axis.p2c(yrange.to);
-
- if (xrange.from == xrange.to || yrange.from == yrange.to) {
- // draw line
- ctx.beginPath();
- ctx.strokeStyle = m.color || options.grid.markingsColor;
- ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth;
- ctx.moveTo(xrange.from, yrange.from);
- ctx.lineTo(xrange.to, yrange.to);
- ctx.stroke();
- }
- else {
- // fill area
- ctx.fillStyle = m.color || options.grid.markingsColor;
- ctx.fillRect(xrange.from, yrange.to,
- xrange.to - xrange.from,
- yrange.from - yrange.to);
- }
- }
- }
-
- // draw the ticks
- var axes = allAxes(), bw = options.grid.borderWidth;
-
- for (var j = 0; j < axes.length; ++j) {
- var axis = axes[j], box = axis.box,
- t = axis.tickLength, x, y, xoff, yoff;
- if (!axis.show || axis.ticks.length == 0)
- continue
-
- ctx.strokeStyle = axis.options.tickColor || $.color.parse(axis.options.color).scale('a', 0.22).toString();
- ctx.lineWidth = 1;
-
- // find the edges
- if (axis.direction == "x") {
- x = 0;
- if (t == "full")
- y = (axis.position == "top" ? 0 : plotHeight);
- else
- y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0);
- }
- else {
- y = 0;
- if (t == "full")
- x = (axis.position == "left" ? 0 : plotWidth);
- else
- x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0);
- }
-
- // draw tick bar
- if (!axis.innermost) {
- ctx.beginPath();
- xoff = yoff = 0;
- if (axis.direction == "x")
- xoff = plotWidth;
- else
- yoff = plotHeight;
-
- if (ctx.lineWidth == 1) {
- x = Math.floor(x) + 0.5;
- y = Math.floor(y) + 0.5;
- }
-
- ctx.moveTo(x, y);
- ctx.lineTo(x + xoff, y + yoff);
- ctx.stroke();
- }
-
- // draw ticks
- ctx.beginPath();
- for (i = 0; i < axis.ticks.length; ++i) {
- var v = axis.ticks[i].v;
-
- xoff = yoff = 0;
-
- if (v < axis.min || v > axis.max
- // skip those lying on the axes if we got a border
- || (t == "full" && bw > 0
- && (v == axis.min || v == axis.max)))
- continue;
-
- if (axis.direction == "x") {
- x = axis.p2c(v);
- yoff = t == "full" ? -plotHeight : t;
-
- if (axis.position == "top")
- yoff = -yoff;
- }
- else {
- y = axis.p2c(v);
- xoff = t == "full" ? -plotWidth : t;
-
- if (axis.position == "left")
- xoff = -xoff;
- }
-
- if (ctx.lineWidth == 1) {
- if (axis.direction == "x")
- x = Math.floor(x) + 0.5;
- else
- y = Math.floor(y) + 0.5;
- }
-
- ctx.moveTo(x, y);
- ctx.lineTo(x + xoff, y + yoff);
- }
-
- ctx.stroke();
- }
-
-
- // draw border
- if (bw) {
- ctx.lineWidth = bw;
- ctx.strokeStyle = options.grid.borderColor;
- ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
- }
-
- ctx.restore();
- }
-
- function insertAxisLabels() {
- placeholder.find(".tickLabels").remove();
-
- var html = ['
'];
-
- var axes = allAxes();
- for (var j = 0; j < axes.length; ++j) {
- var axis = axes[j], box = axis.box;
- if (!axis.show)
- continue;
- //debug: html.push('')
- html.push('
').appendTo(placeholder);
- if (options.legend.backgroundOpacity != 0.0) {
- // put in the transparent background
- // separately to avoid blended labels and
- // label boxes
- var c = options.legend.backgroundColor;
- if (c == null) {
- c = options.grid.backgroundColor;
- if (c && typeof c == "string")
- c = $.color.parse(c);
- else
- c = $.color.extract(legend, 'background-color');
- c.a = 1;
- c = c.toString();
- }
- var div = legend.children();
- $('
').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
- }
- }
- }
-
-
- // interactive features
-
- var highlights = [],
- redrawTimeout = null;
-
- // returns the data item the mouse is over, or null if none is found
- function findNearbyItem(mouseX, mouseY, seriesFilter) {
- var maxDistance = options.grid.mouseActiveRadius,
- smallestDistance = maxDistance * maxDistance + 1,
- item = null, foundPoint = false, i, j;
-
- for (i = series.length - 1; i >= 0; --i) {
- if (!seriesFilter(series[i]))
- continue;
-
- var s = series[i],
- axisx = s.xaxis,
- axisy = s.yaxis,
- points = s.datapoints.points,
- ps = s.datapoints.pointsize,
- mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
- my = axisy.c2p(mouseY),
- maxx = maxDistance / axisx.scale,
- maxy = maxDistance / axisy.scale;
-
- // with inverse transforms, we can't use the maxx/maxy
- // optimization, sadly
- if (axisx.options.inverseTransform)
- maxx = Number.MAX_VALUE;
- if (axisy.options.inverseTransform)
- maxy = Number.MAX_VALUE;
-
- if (s.lines.show || s.points.show) {
- for (j = 0; j < points.length; j += ps) {
- var x = points[j], y = points[j + 1];
- if (x == null)
- continue;
-
- // For points and lines, the cursor must be within a
- // certain distance to the data point
- if (x - mx > maxx || x - mx < -maxx ||
- y - my > maxy || y - my < -maxy)
- continue;
-
- // We have to calculate distances in pixels, not in
- // data units, because the scales of the axes may be different
- var dx = Math.abs(axisx.p2c(x) - mouseX),
- dy = Math.abs(axisy.p2c(y) - mouseY),
- dist = dx * dx + dy * dy; // we save the sqrt
-
- // use <= to ensure last point takes precedence
- // (last generally means on top of)
- if (dist < smallestDistance) {
- smallestDistance = dist;
- item = [i, j / ps];
- }
- }
- }
-
- if (s.bars.show && !item) { // no other point can be nearby
- var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2,
- barRight = barLeft + s.bars.barWidth;
-
- for (j = 0; j < points.length; j += ps) {
- var x = points[j], y = points[j + 1], b = points[j + 2];
- if (x == null)
- continue;
-
- // for a bar graph, the cursor must be inside the bar
- if (series[i].bars.horizontal ?
- (mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
- my >= y + barLeft && my <= y + barRight) :
- (mx >= x + barLeft && mx <= x + barRight &&
- my >= Math.min(b, y) && my <= Math.max(b, y)))
- item = [i, j / ps];
- }
- }
- }
-
- if (item) {
- i = item[0];
- j = item[1];
- ps = series[i].datapoints.pointsize;
-
- return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
- dataIndex: j,
- series: series[i],
- seriesIndex: i };
- }
-
- return null;
- }
-
- function onMouseMove(e) {
- if (options.grid.hoverable)
- triggerClickHoverEvent("plothover", e,
- function (s) { return s["hoverable"] != false; });
- }
-
- function onMouseLeave(e) {
- if (options.grid.hoverable)
- triggerClickHoverEvent("plothover", e,
- function (s) { return false; });
- }
-
- function onClick(e) {
- triggerClickHoverEvent("plotclick", e,
- function (s) { return s["clickable"] != false; });
- }
-
- // trigger click or hover event (they send the same parameters
- // so we share their code)
- function triggerClickHoverEvent(eventname, event, seriesFilter) {
- var offset = eventHolder.offset(),
- canvasX = event.pageX - offset.left - plotOffset.left,
- canvasY = event.pageY - offset.top - plotOffset.top,
- pos = canvasToAxisCoords({ left: canvasX, top: canvasY });
-
- pos.pageX = event.pageX;
- pos.pageY = event.pageY;
-
- var item = findNearbyItem(canvasX, canvasY, seriesFilter);
-
- if (item) {
- // fill in mouse pos for any listeners out there
- item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left);
- item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top);
- }
-
- if (options.grid.autoHighlight) {
- // clear auto-highlights
- for (var i = 0; i < highlights.length; ++i) {
- var h = highlights[i];
- if (h.auto == eventname &&
- !(item && h.series == item.series &&
- h.point[0] == item.datapoint[0] &&
- h.point[1] == item.datapoint[1]))
- unhighlight(h.series, h.point);
- }
-
- if (item)
- highlight(item.series, item.datapoint, eventname);
- }
-
- placeholder.trigger(eventname, [ pos, item ]);
- }
-
- function triggerRedrawOverlay() {
- if (!redrawTimeout)
- redrawTimeout = setTimeout(drawOverlay, 30);
- }
-
- function drawOverlay() {
- redrawTimeout = null;
-
- // draw highlights
- octx.save();
- octx.clearRect(0, 0, canvasWidth, canvasHeight);
- octx.translate(plotOffset.left, plotOffset.top);
-
- var i, hi;
- for (i = 0; i < highlights.length; ++i) {
- hi = highlights[i];
-
- if (hi.series.bars.show)
- drawBarHighlight(hi.series, hi.point);
- else
- drawPointHighlight(hi.series, hi.point);
- }
- octx.restore();
-
- executeHooks(hooks.drawOverlay, [octx]);
- }
-
- function highlight(s, point, auto) {
- if (typeof s == "number")
- s = series[s];
-
- if (typeof point == "number") {
- var ps = s.datapoints.pointsize;
- point = s.datapoints.points.slice(ps * point, ps * (point + 1));
- }
-
- var i = indexOfHighlight(s, point);
- if (i == -1) {
- highlights.push({ series: s, point: point, auto: auto });
-
- triggerRedrawOverlay();
- }
- else if (!auto)
- highlights[i].auto = false;
- }
-
- function unhighlight(s, point) {
- if (s == null && point == null) {
- highlights = [];
- triggerRedrawOverlay();
- }
-
- if (typeof s == "number")
- s = series[s];
-
- if (typeof point == "number")
- point = s.data[point];
-
- var i = indexOfHighlight(s, point);
- if (i != -1) {
- highlights.splice(i, 1);
-
- triggerRedrawOverlay();
- }
- }
-
- function indexOfHighlight(s, p) {
- for (var i = 0; i < highlights.length; ++i) {
- var h = highlights[i];
- if (h.series == s && h.point[0] == p[0]
- && h.point[1] == p[1])
- return i;
- }
- return -1;
- }
-
- function drawPointHighlight(series, point) {
- var x = point[0], y = point[1],
- axisx = series.xaxis, axisy = series.yaxis;
-
- if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
- return;
-
- var pointRadius = series.points.radius + series.points.lineWidth / 2;
- octx.lineWidth = pointRadius;
- octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
- var radius = 1.5 * pointRadius,
- x = axisx.p2c(x),
- y = axisy.p2c(y);
-
- octx.beginPath();
- if (series.points.symbol == "circle")
- octx.arc(x, y, radius, 0, 2 * Math.PI, false);
- else
- series.points.symbol(octx, x, y, radius, false);
- octx.closePath();
- octx.stroke();
- }
-
- function drawBarHighlight(series, point) {
- octx.lineWidth = series.bars.lineWidth;
- octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
- var fillStyle = $.color.parse(series.color).scale('a', 0.5).toString();
- var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
- drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
- 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);
- }
-
- function getColorOrGradient(spec, bottom, top, defaultColor) {
- if (typeof spec == "string")
- return spec;
- else {
- // assume this is a gradient spec; IE currently only
- // supports a simple vertical gradient properly, so that's
- // what we support too
- var gradient = ctx.createLinearGradient(0, top, 0, bottom);
-
- for (var i = 0, l = spec.colors.length; i < l; ++i) {
- var c = spec.colors[i];
- if (typeof c != "string") {
- var co = $.color.parse(defaultColor);
- if (c.brightness != null)
- co = co.scale('rgb', c.brightness)
- if (c.opacity != null)
- co.a *= c.opacity;
- c = co.toString();
- }
- gradient.addColorStop(i / (l - 1), c);
- }
-
- return gradient;
- }
- }
- }
-
- $.plot = function(placeholder, data, options) {
- //var t0 = new Date();
- var plot = new Plot($(placeholder), data, options, $.plot.plugins);
- //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime()));
- return plot;
- };
-
- $.plot.version = "0.7";
-
- $.plot.plugins = [];
-
- // returns a string with the date d formatted according to fmt
- $.plot.formatDate = function(d, fmt, monthNames) {
- var leftPad = function(n) {
- n = "" + n;
- return n.length == 1 ? "0" + n : n;
- };
-
- var r = [];
- var escape = false, padNext = false;
- var hours = d.getHours();
- var isAM = hours < 12;
- if (monthNames == null)
- monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
-
- if (fmt.search(/%p|%P/) != -1) {
- if (hours > 12) {
- hours = hours - 12;
- } else if (hours == 0) {
- hours = 12;
- }
- }
- for (var i = 0; i < fmt.length; ++i) {
- var c = fmt.charAt(i);
-
- if (escape) {
- switch (c) {
- case 'h': c = "" + hours; break;
- case 'H': c = leftPad(hours); break;
- case 'M': c = leftPad(d.getMinutes()); break;
- case 'S': c = leftPad(d.getSeconds()); break;
- case 'd': c = "" + d.getDate(); break;
- case 'm': c = "" + (d.getMonth() + 1); break;
- case 'y': c = "" + d.getFullYear(); break;
- case 'b': c = "" + monthNames[d.getMonth()]; break;
- case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
- case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
- case '0': c = ""; padNext = true; break;
- }
- if (c && padNext) {
- c = leftPad(c);
- padNext = false;
- }
- r.push(c);
- if (!padNext)
- escape = false;
- }
- else {
- if (c == "%")
- escape = true;
- else
- r.push(c);
- }
- }
- return r.join("");
- };
-
- // round to nearby lower multiple of base
- function floorInBase(n, base) {
- return base * Math.floor(n / base);
- }
-
-})(jQuery);
-/*
-Flot plugin for showing crosshairs, thin lines, when the mouse hovers
-over the plot.
-
- crosshair: {
- mode: null or "x" or "y" or "xy"
- color: color
- lineWidth: number
- }
-
-Set the mode to one of "x", "y" or "xy". The "x" mode enables a
-vertical crosshair that lets you trace the values on the x axis, "y"
-enables a horizontal crosshair and "xy" enables them both. "color" is
-the color of the crosshair (default is "rgba(170, 0, 0, 0.80)"),
-"lineWidth" is the width of the drawn lines (default is 1).
-
-The plugin also adds four public methods:
-
- - setCrosshair(pos)
-
- Set the position of the crosshair. Note that this is cleared if
- the user moves the mouse. "pos" is in coordinates of the plot and
- should be on the form { x: xpos, y: ypos } (you can use x2/x3/...
- if you're using multiple axes), which is coincidentally the same
- format as what you get from a "plothover" event. If "pos" is null,
- the crosshair is cleared.
-
- - clearCrosshair()
-
- Clear the crosshair.
-
- - lockCrosshair(pos)
-
- Cause the crosshair to lock to the current location, no longer
- updating if the user moves the mouse. Optionally supply a position
- (passed on to setCrosshair()) to move it to.
-
- Example usage:
- var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } };
- $("#graph").bind("plothover", function (evt, position, item) {
- if (item) {
- // Lock the crosshair to the data point being hovered
- myFlot.lockCrosshair({ x: item.datapoint[0], y: item.datapoint[1] });
- }
- else {
- // Return normal crosshair operation
- myFlot.unlockCrosshair();
- }
- });
-
- - unlockCrosshair()
-
- Free the crosshair to move again after locking it.
-*/
-
-(function ($) {
- var options = {
- crosshair: {
- mode: null, // one of null, "x", "y" or "xy",
- color: "rgba(170, 0, 0, 0.80)",
- lineWidth: 1
- }
- };
-
- function init(plot) {
- // position of crosshair in pixels
- var crosshair = { x: -1, y: -1, locked: false };
-
- plot.setCrosshair = function setCrosshair(pos) {
- if (!pos)
- crosshair.x = -1;
- else {
- var o = plot.p2c(pos);
- crosshair.x = Math.max(0, Math.min(o.left, plot.width()));
- crosshair.y = Math.max(0, Math.min(o.top, plot.height()));
- }
-
- plot.triggerRedrawOverlay();
- };
-
- plot.clearCrosshair = plot.setCrosshair; // passes null for pos
-
- plot.lockCrosshair = function lockCrosshair(pos) {
- if (pos)
- plot.setCrosshair(pos);
- crosshair.locked = true;
- }
-
- plot.unlockCrosshair = function unlockCrosshair() {
- crosshair.locked = false;
- }
-
- function onMouseOut(e) {
- if (crosshair.locked)
- return;
-
- if (crosshair.x != -1) {
- crosshair.x = -1;
- plot.triggerRedrawOverlay();
- }
- }
-
- function onMouseMove(e) {
- if (crosshair.locked)
- return;
-
- if (plot.getSelection && plot.getSelection()) {
- crosshair.x = -1; // hide the crosshair while selecting
- return;
- }
-
- var offset = plot.offset();
- crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));
- crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));
- plot.triggerRedrawOverlay();
- }
-
- plot.hooks.bindEvents.push(function (plot, eventHolder) {
- if (!plot.getOptions().crosshair.mode)
- return;
-
- eventHolder.mouseout(onMouseOut);
- eventHolder.mousemove(onMouseMove);
- });
-
- plot.hooks.drawOverlay.push(function (plot, ctx) {
- var c = plot.getOptions().crosshair;
- if (!c.mode)
- return;
-
- var plotOffset = plot.getPlotOffset();
-
- ctx.save();
- ctx.translate(plotOffset.left, plotOffset.top);
-
- if (crosshair.x != -1) {
- ctx.strokeStyle = c.color;
- ctx.lineWidth = c.lineWidth;
- ctx.lineJoin = "round";
-
- ctx.beginPath();
- if (c.mode.indexOf("x") != -1) {
- ctx.moveTo(crosshair.x, 0);
- ctx.lineTo(crosshair.x, plot.height());
- }
- if (c.mode.indexOf("y") != -1) {
- ctx.moveTo(0, crosshair.y);
- ctx.lineTo(plot.width(), crosshair.y);
- }
- ctx.stroke();
- }
- ctx.restore();
- });
-
- plot.hooks.shutdown.push(function (plot, eventHolder) {
- eventHolder.unbind("mouseout", onMouseOut);
- eventHolder.unbind("mousemove", onMouseMove);
- });
- }
-
- $.plot.plugins.push({
- init: init,
- options: options,
- name: 'crosshair',
- version: '1.0'
- });
-})(jQuery);
-/*
-Flot plugin for automatically redrawing plots when the placeholder
-size changes, e.g. on window resizes.
-
-It works by listening for changes on the placeholder div (through the
-jQuery resize event plugin) - if the size changes, it will redraw the
-plot.
-
-There are no options. If you need to disable the plugin for some
-plots, you can just fix the size of their placeholders.
-*/
-
-
-/* Inline dependency:
- * jQuery resize event - v1.1 - 3/14/2010
- * http://benalman.com/projects/jquery-resize-plugin/
- *
- * Copyright (c) 2010 "Cowboy" Ben Alman
- * Dual licensed under the MIT and GPL licenses.
- * http://benalman.com/about/license/
- */
-(function($,h,c){var a=$([]),e=$.resize=$.extend($.resize,{}),i,k="setTimeout",j="resize",d=j+"-special-event",b="delay",f="throttleWindow";e[b]=250;e[f]=true;$.event.special[j]={setup:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.add(l);$.data(this,d,{w:l.width(),h:l.height()});if(a.length===1){g()}},teardown:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.not(l);l.removeData(d);if(!a.length){clearTimeout(i)}},add:function(l){if(!e[f]&&this[k]){return false}var n;function m(s,o,p){var q=$(this),r=$.data(this,d);r.w=o!==c?o:q.width();r.h=p!==c?p:q.height();n.apply(this,arguments)}if($.isFunction(l)){n=l;return m}else{n=l.handler;l.handler=m}}};function g(){i=h[k](function(){a.each(function(){var n=$(this),m=n.width(),l=n.height(),o=$.data(this,d);if(m!==o.w||l!==o.h){n.trigger(j,[o.w=m,o.h=l])}});g()},e[b])}})(jQuery,this);
-
-
-(function ($) {
- var options = { }; // no options
-
- function init(plot) {
- function onResize() {
- var placeholder = plot.getPlaceholder();
-
- // somebody might have hidden us and we can't plot
- // when we don't have the dimensions
- if (placeholder.width() == 0 || placeholder.height() == 0)
- return;
-
- plot.resize();
- plot.setupGrid();
- plot.draw();
- }
-
- function bindEvents(plot, eventHolder) {
- plot.getPlaceholder().resize(onResize);
- }
-
- function shutdown(plot, eventHolder) {
- plot.getPlaceholder().unbind("resize", onResize);
- }
-
- plot.hooks.bindEvents.push(bindEvents);
- plot.hooks.shutdown.push(shutdown);
- }
-
- $.plot.plugins.push({
- init: init,
- options: options,
- name: 'resize',
- version: '1.0'
- });
-})(jQuery);
-/*
-Flot plugin for selecting regions.
-
-The plugin defines the following options:
-
- selection: {
- mode: null or "x" or "y" or "xy",
- color: color
- }
-
-Selection support is enabled by setting the mode to one of "x", "y" or
-"xy". In "x" mode, the user will only be able to specify the x range,
-similarly for "y" mode. For "xy", the selection becomes a rectangle
-where both ranges can be specified. "color" is color of the selection
-(if you need to change the color later on, you can get to it with
-plot.getOptions().selection.color).
-
-When selection support is enabled, a "plotselected" event will be
-emitted on the DOM element you passed into the plot function. The
-event handler gets a parameter with the ranges selected on the axes,
-like this:
-
- placeholder.bind("plotselected", function(event, ranges) {
- alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to)
- // similar for yaxis - with multiple axes, the extra ones are in
- // x2axis, x3axis, ...
- });
-
-The "plotselected" event is only fired when the user has finished
-making the selection. A "plotselecting" event is fired during the
-process with the same parameters as the "plotselected" event, in case
-you want to know what's happening while it's happening,
-
-A "plotunselected" event with no arguments is emitted when the user
-clicks the mouse to remove the selection.
-
-The plugin allso adds the following methods to the plot object:
-
-- setSelection(ranges, preventEvent)
-
- Set the selection rectangle. The passed in ranges is on the same
- form as returned in the "plotselected" event. If the selection mode
- is "x", you should put in either an xaxis range, if the mode is "y"
- you need to put in an yaxis range and both xaxis and yaxis if the
- selection mode is "xy", like this:
-
- setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } });
-
- setSelection will trigger the "plotselected" event when called. If
- you don't want that to happen, e.g. if you're inside a
- "plotselected" handler, pass true as the second parameter. If you
- are using multiple axes, you can specify the ranges on any of those,
- e.g. as x2axis/x3axis/... instead of xaxis, the plugin picks the
- first one it sees.
-
-- clearSelection(preventEvent)
-
- Clear the selection rectangle. Pass in true to avoid getting a
- "plotunselected" event.
-
-- getSelection()
-
- Returns the current selection in the same format as the
- "plotselected" event. If there's currently no selection, the
- function returns null.
-
-*/
-
-(function ($) {
- function init(plot) {
- var selection = {
- first: { x: -1, y: -1}, second: { x: -1, y: -1},
- show: false,
- active: false
- };
-
- // FIXME: The drag handling implemented here should be
- // abstracted out, there's some similar code from a library in
- // the navigation plugin, this should be massaged a bit to fit
- // the Flot cases here better and reused. Doing this would
- // make this plugin much slimmer.
- var savedhandlers = {};
-
- var mouseUpHandler = null;
-
- function onMouseMove(e) {
- if (selection.active) {
- updateSelection(e);
-
- plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]);
- }
- }
-
- function onMouseDown(e) {
- if (e.which != 1) // only accept left-click
- return;
-
- // cancel out any text selections
- document.body.focus();
-
- // prevent text selection and drag in old-school browsers
- if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) {
- savedhandlers.onselectstart = document.onselectstart;
- document.onselectstart = function () { return false; };
- }
- if (document.ondrag !== undefined && savedhandlers.ondrag == null) {
- savedhandlers.ondrag = document.ondrag;
- document.ondrag = function () { return false; };
- }
-
- setSelectionPos(selection.first, e);
-
- selection.active = true;
-
- // this is a bit silly, but we have to use a closure to be
- // able to whack the same handler again
- mouseUpHandler = function (e) { onMouseUp(e); };
-
- $(document).one("mouseup", mouseUpHandler);
- }
-
- function onMouseUp(e) {
- mouseUpHandler = null;
-
- // revert drag stuff for old-school browsers
- if (document.onselectstart !== undefined)
- document.onselectstart = savedhandlers.onselectstart;
- if (document.ondrag !== undefined)
- document.ondrag = savedhandlers.ondrag;
-
- // no more dragging
- selection.active = false;
- updateSelection(e);
-
- if (selectionIsSane())
- triggerSelectedEvent();
- else {
- // this counts as a clear
- plot.getPlaceholder().trigger("plotunselected", [ ]);
- plot.getPlaceholder().trigger("plotselecting", [ null ]);
- }
-
- return false;
- }
-
- function getSelection() {
- if (!selectionIsSane())
- return null;
-
- var r = {}, c1 = selection.first, c2 = selection.second;
- $.each(plot.getAxes(), function (name, axis) {
- if (axis.used) {
- var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]);
- r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) };
- }
- });
- return r;
- }
-
- function triggerSelectedEvent() {
- var r = getSelection();
-
- plot.getPlaceholder().trigger("plotselected", [ r ]);
-
- // backwards-compat stuff, to be removed in future
- if (r.xaxis && r.yaxis)
- plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]);
- }
-
- function clamp(min, value, max) {
- return value < min ? min: (value > max ? max: value);
- }
-
- function setSelectionPos(pos, e) {
- var o = plot.getOptions();
- var offset = plot.getPlaceholder().offset();
- var plotOffset = plot.getPlotOffset();
- pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width());
- pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height());
-
- if (o.selection.mode == "y")
- pos.x = pos == selection.first ? 0 : plot.width();
-
- if (o.selection.mode == "x")
- pos.y = pos == selection.first ? 0 : plot.height();
- }
-
- function updateSelection(pos) {
- if (pos.pageX == null)
- return;
-
- setSelectionPos(selection.second, pos);
- if (selectionIsSane()) {
- selection.show = true;
- plot.triggerRedrawOverlay();
- }
- else
- clearSelection(true);
- }
-
- function clearSelection(preventEvent) {
- if (selection.show) {
- selection.show = false;
- plot.triggerRedrawOverlay();
- if (!preventEvent)
- plot.getPlaceholder().trigger("plotunselected", [ ]);
- }
- }
-
- // function taken from markings support in Flot
- function extractRange(ranges, coord) {
- var axis, from, to, key, axes = plot.getAxes();
-
- for (var k in axes) {
- axis = axes[k];
- if (axis.direction == coord) {
- key = coord + axis.n + "axis";
- if (!ranges[key] && axis.n == 1)
- key = coord + "axis"; // support x1axis as xaxis
- if (ranges[key]) {
- from = ranges[key].from;
- to = ranges[key].to;
- break;
- }
- }
- }
-
- // backwards-compat stuff - to be removed in future
- if (!ranges[key]) {
- axis = coord == "x" ? plot.getXAxes()[0] : plot.getYAxes()[0];
- from = ranges[coord + "1"];
- to = ranges[coord + "2"];
- }
-
- // auto-reverse as an added bonus
- if (from != null && to != null && from > to) {
- var tmp = from;
- from = to;
- to = tmp;
- }
-
- return { from: from, to: to, axis: axis };
- }
-
- function setSelection(ranges, preventEvent) {
- var axis, range, o = plot.getOptions();
-
- if (o.selection.mode == "y") {
- selection.first.x = 0;
- selection.second.x = plot.width();
- }
- else {
- range = extractRange(ranges, "x");
-
- selection.first.x = range.axis.p2c(range.from);
- selection.second.x = range.axis.p2c(range.to);
- }
-
- if (o.selection.mode == "x") {
- selection.first.y = 0;
- selection.second.y = plot.height();
- }
- else {
- range = extractRange(ranges, "y");
-
- selection.first.y = range.axis.p2c(range.from);
- selection.second.y = range.axis.p2c(range.to);
- }
-
- selection.show = true;
- plot.triggerRedrawOverlay();
- if (!preventEvent && selectionIsSane())
- triggerSelectedEvent();
- }
-
- function selectionIsSane() {
- var minSize = 5;
- return Math.abs(selection.second.x - selection.first.x) >= minSize &&
- Math.abs(selection.second.y - selection.first.y) >= minSize;
- }
-
- plot.clearSelection = clearSelection;
- plot.setSelection = setSelection;
- plot.getSelection = getSelection;
-
- plot.hooks.bindEvents.push(function(plot, eventHolder) {
- var o = plot.getOptions();
- if (o.selection.mode != null) {
- eventHolder.mousemove(onMouseMove);
- eventHolder.mousedown(onMouseDown);
- }
- });
-
-
- plot.hooks.drawOverlay.push(function (plot, ctx) {
- // draw selection
- if (selection.show && selectionIsSane()) {
- var plotOffset = plot.getPlotOffset();
- var o = plot.getOptions();
-
- ctx.save();
- ctx.translate(plotOffset.left, plotOffset.top);
-
- var c = $.color.parse(o.selection.color);
-
- ctx.strokeStyle = c.scale('a', 0.8).toString();
- ctx.lineWidth = 1;
- ctx.lineJoin = "round";
- ctx.fillStyle = c.scale('a', 0.4).toString();
-
- var x = Math.min(selection.first.x, selection.second.x),
- y = Math.min(selection.first.y, selection.second.y),
- w = Math.abs(selection.second.x - selection.first.x),
- h = Math.abs(selection.second.y - selection.first.y);
-
- ctx.fillRect(x, y, w, h);
- ctx.strokeRect(x, y, w, h);
-
- ctx.restore();
- }
- });
-
- plot.hooks.shutdown.push(function (plot, eventHolder) {
- eventHolder.unbind("mousemove", onMouseMove);
- eventHolder.unbind("mousedown", onMouseDown);
-
- if (mouseUpHandler)
- $(document).unbind("mouseup", mouseUpHandler);
- });
-
- }
-
- $.plot.plugins.push({
- init: init,
- options: {
- selection: {
- mode: null, // one of null, "x", "y" or "xy"
- color: "#e8cfac"
- }
- },
- name: 'selection',
- version: '1.1'
- });
-})(jQuery);
diff --git a/js/app.js b/js/app.js
index 1a5f0f1..2f67882 100644
--- a/js/app.js
+++ b/js/app.js
@@ -1,310 +1,11 @@
-// detect if mobile
-var is_mobile = false;
-
-(function(a,b){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) is_mobile = true;})(navigator.userAgent||navigator.vendor||window.opera);
-
-// hash url
-var history_supported = (typeof history !== 'undefined');
-
-function lhash_update(history_step) {
- history_step = !!history_step;
-
- var url = document.location.href.split("#",1)[0];
- var hash = "";
-
- // generate hash
- hash += "mt=" + map.getMapTypeId();
- hash += "&mz=" + map.getZoom();
-
- if(!/^[a-z0-9]{32}$/ig.exec(wvar.query)) {
- hash += "&qm=" + wvar.mode.replace(/ /g, '_');
- }
-
- if(follow_vehicle === null || manual_pan) {
- var latlng = map.getCenter();
- hash += "&mc=" + roundNumber(latlng.lat(), 5) +
- "," + roundNumber(latlng.lng(), 5);
- }
-
- if(follow_vehicle !== null) {
- hash += "&f=" + follow_vehicle;
- }
-
- if(wvar.query !== "") {
- hash += "&q=" + wvar.query;
- $("header .search input[type='text']").val(wvar.query);
- }
-
- // other vars
- if(wvar.nyan) {
- hash += "&nyan=1";
- }
-
- hash = encodeURI(hash);
- // set state
- if(history_supported) {
- if(!history_step) {
- history.replaceState(null, null, url + "#!" + hash);
- } else {
- history.pushState(null, null, url + "#!" + hash);
- }
- } else {
- document.location.hash = "!" + hash;
- }
-}
-
-// wvar detection
-var wvar = {
- enabled: false,
- vlist: true,
- graph: true,
- graph_exapnded: false,
- focus: "",
- mode: (is_mobile) ? modeDefaultMobile : modeDefault,
- zoom: true,
- query: "!RS_*;",
- nyan: false,
-};
-
-
-function load_hash(no_refresh) {
- no_refresh = (no_refresh === null);
- var hash = window.location.hash.slice(2);
-
- if(hash === "") return;
-
- var parms = hash.split('&');
- var refresh = false;
- var refocus = false;
-
- // defaults
- manual_pan = false;
-
- var def = {
- mode: wvar.mode,
- zoom: true,
- focus: "",
- query: "",
- nyan: false,
- };
-
- parms.forEach(function(v) {
- v = v.split('=');
- k = v[0];
- v = decodeURIComponent(v[1]);
-
- switch(k) {
- case "mt":
- map.setMapTypeId(v);
- break;
- case "mz":
- map.setZoom(parseInt(v));
- break;
- case "mc":
- def.zoom = false;
- manual_pan = true;
- v = v.split(',');
- var latlng = new google.maps.LatLng(v[0], v[1]);
- map.setCenter(latlng);
- break;
- case "f":
- refocus = (follow_vehicle != v);
- follow_vehicle = v;
- def.focus = v;
- break;
- case "qm":
- def.mode = v.replace(/_/g, ' ');
- if(modeList.indexOf(def.mode) == -1) def.mode = (is_mobile) ? modeDefaultMobile : modeDefault;
- break;
- case "q":
- def.query = v;
- $("header .search input[type='text']").val(v);
- break;
- case "nyan":
- def[k] = !!parseInt(v);
- break;
- }
- });
-
- // check if we should force refresh
- ['mode','query','nyan'].forEach(function(k) {
- if(wvar[k] != def[k]) refresh = true;
- });
-
- $.extend(true, wvar, def);
-
- // force refresh
- if(!no_refresh) {
- if(refresh) {
- zoomed_in = false;
- clean_refresh(wvar.mode, true);
- }
- else if(refocus) {
- $(".row.active").removeClass('active');
- $(".vehicle"+vehicles[wvar.focus].uuid).addClass('active');
- followVehicle(wvar.focus, manual_pan, true);
- }
- }
-
- lhash_update();
-}
-window.onhashchange = load_hash;
-
-var params = window.location.search.substring(1).split('&');
-
-for(var idx in params) {
- var line = params[idx].split('=');
- if(line.length < 2) continue;
-
- switch(line[0]) {
- case "embed":
- if(line[1] == "1") {
- wvar.enabled = true;
- if(!is_mobile) wvar.mode = 'All';
- }
- break;
- case "hidelist": if(line[1] == "1") wvar.vlist = false; break;
- case "hidegraph": if(line[1] == "1") wvar.graph = false; break;
- case "expandgraph": if(line[1] == "1") wvar.graph_expanded = true; break;
- case "filter":
- wvar.query = decodeURIComponent(line[1]);
- $("header .search input[type='text']").val(wvar.query);
- break;
- case "nyan": wvar.nyan = true; break;
- case "focus": wvar.focus = decodeURIComponent(line[1]); break;
- case "docid": wvar.docid = line[1]; break;
- case "mode": wvar.mode = decodeURIComponent(line[1]); break;
- }
-}
-
-if(wvar.enabled) {
- //analytics
- if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'Functionality', 'Embed Opts', window.location.search]);
-}
-
-$.ajaxSetup({ cache: true });
-
-var force_check_cache = false;
-
-// handle cachin events and display a loading bar
-var loadComplete = function(e) {
- clearTimeout(initTimer);
-
- if(e.type == 'updateready') {
- // swapCache may throw exception if the isn't a previous cache
- try {
- window.applicationCache.swapCache();
- } catch(v) {}
-
- window.location.reload();
- return;
- }
-
- $('#loading .complete').stop(true,true).animate({width: 200}, {complete: trackerInit });
-};
-
-var hysplit = {};
-var hysplit_data = {};
-var refresh_hysplit = function() {
- $.getJSON("//spacenear.us/tracker/datanew.php?type=hysplit&format=json", function(data) {
- var refresh = false;
-
- for(var k in data) {
- if(k in hysplit_data) {
- // if the jobid is the same, skip to next one
- if(hysplit_data[k].jobid == data[k].jobid) continue;
-
- // otherwise update the url
- hysplit_data[k] = data[k];
- hysplit[k].setUrl(hysplit_data[k].url_kmz);
- } else {
- hysplit_data[k] = data[k];
- hysplit[k] = new google.maps.KmlLayer({url: hysplit_data[k].url_kmz, preserveViewport:true });
- refresh = true;
- }
- }
-
- if(refresh) refreshUI();
- });
-};
-
-// loads the tracker interface
-function trackerInit() {
- $('#loading,#settingsbox,#aboutbox,#chasebox').hide(); // welcome screen
- $('header,#main').show(); // interface elements
- checkSize();
-
- if(map) return;
-
- if(is_mobile || wvar.enabled) $(".nav .wvar").hide();
-
- if(!is_mobile) {
- $.getScript("js/init_plot.js", function() { checkSize(); if(!map) load(); });
- if(wvar.graph) $('#telemetry_graph').attr('style','');
-
- // fetch hysplit jobs
- setInterval(refresh_hysplit, 60 * 1000);
- refresh_hysplit();
-
- return;
- }
- if(!map) load();
-}
-
-// if for some reason, applicationCache is not working, load the app after a 3s timeout
-var initTimer = setTimeout(trackerInit, 3000);
-
-var cache = window.applicationCache;
-cache.addEventListener('noupdate', loadComplete, false);
-cache.addEventListener('updateready', loadComplete, false);
-cache.addEventListener('cached', loadComplete, false);
-cache.addEventListener('error', loadComplete, false);
-
-// if the browser supports progress events, display a loading bar
-cache.addEventListener('checking', function() { if(map && !force_check_cache) return; force_check_cache = false; clearTimeout(initTimer); $('#loading .bar,#loading').show(); $('#loading .complete').css({width: 0}); }, false);
-cache.addEventListener('progress', function(e) { $('#loading .complete').stop(true,true).animate({width: (200/e.total)*e.loaded}); }, false);
-
-var listScroll;
-var GPS_ts = null;
-var GPS_lat = null;
-var GPS_lon = null;
-var GPS_alt = null;
-var GPS_speed = null;
-var CHASE_enabled = null;
-var CHASE_listenerSent = false;
-var CHASE_timer = 0;
-var callsign = "";
-
function checkSize() {
// we are in landscape mode
- w = window.innerWidth;
-
- // this is hacky fix for off by 1px that makes the vechicle list disappear
- wrect = document.body.getBoundingClientRect();
- // chrome seems to calculate the body bounding box differently from every other browser
- if (!!window.chrome) {
- w_fix = (w >= wrect.width) ? 1 : 0;
- } else {
- w_fix = (w === Math.floor(wrect.width)) ? 0 : 1;
- }
-
+ w = $(window).width();
w = (w < 320) ? 320 : w; // absolute minimum 320px
- h = window.innerHeight;
- //h = (h < 300) ? 300 : h; // absolute minimum 320px minus 20px for the iphone bar
+ h = $(window).height();
+ h = (h < 300) ? 300 : h; // absolute minimum 320px minus 20px for the iphone bar
hh = $('header').height();
-
- ph = 0;
-
- if(w > 900 && $('.flatpage:visible').length) {
- $('.flatpage').addClass('topanel');
- ph = $('.flatpage:visible').width()+30;
- } else {
- $('.flatpage.topanel').removeClass('topanel');
- }
-
- $("#mapscreen,.flatpage").height(h-hh-5);
-
- sw = (wvar.vlist) ? 200 : 0;
+ sw = $('#main').width();
$('.container').width(w-20);
@@ -316,722 +17,26 @@ function checkSize() {
$('#map').height(h-hh-5);
}
$('body,#loading').height(h);
- $('#mapscreen,#map,#telemetry_graph,#telemetry_graph .holder').width(w-sw-ph-w_fix);
- $('#main').width(sw);
+ $('#map,#telemetry_graph,#telemetry_graph .holder').width(w-sw-1);
} else { // portrait mode
- //if(h < 420) h = 420;
- var mh = (wvar.vlist) ? 150 : 0;
-
+ if(h < 420) h = 420;
$('body,#loading').height(h);
- $('#map,#mapscreen').height(h-hh-5-mh);
- $('#map,#mapscreen').width(w);
- $('#main').height(mh); // 180px is just enough to hold one expanded vehicle
- $('#main').width(w);
+ $('#map').height(h-hh-5-180);
+ $('#map').width(w);
+ $('#main').height(180); // 180px is just enough to hold one expanded vehicle
}
- // this should hide the address bar on mobile phones, when possible
+ // this should hide the address bar on some mobile phones
window.scrollTo(0,1);
-
- if(map) google.maps.event.trigger(map, 'resize');
}
window.onresize = checkSize;
window.onchangeorientation = checkSize;
-
-// functions
-
-function positionUpdateError(error) {
- switch(error.code)
- {
- case error.PERMISSION_DENIED:
- alert("no permission to use your location");
- $('#sw_chasecar').click(); // turn off chase car
- break;
- default:
- break;
- }
-}
-
-var positionUpdateHandle = function(position) {
- if(CHASE_enabled && !CHASE_listenerSent) {
- if(offline.get('opt_station')) {
- ChaseCar.putListenerInfo(callsign);
- CHASE_listenerSent = true;
- }
- }
-
- //navigator.geolocation.getCurrentPosition(function(position) {
- var lat = position.coords.latitude;
- var lon = position.coords.longitude;
- var alt = (position.coords.altitude) ? position.coords.altitude : 0;
- var accuracy = (position.coords.accuracy) ? position.coords.accuracy : 0;
- var speed = (position.coords.speed) ? position.coords.speed : 0;
-
- // constantly update 'last updated' field, and display friendly time since last update
- if(!GPS_ts) {
- GPS_ts = parseInt(position.timestamp/1000);
-
- setInterval(function() {
- var delta_ts = parseInt(Date.now()/1000) - GPS_ts;
-
- // generate friendly timestamp
- var hours = Math.floor(delta_ts / 3600);
- var minutes = Math.floor(delta_ts / 60) % 60;
- var ts_str = (delta_ts >= 60) ?
- ((hours)?hours+'h ':'') +
- ((minutes)?minutes+'m':'') +
- ' ago'
- : 'just now';
- $('#cc_timestamp').text(ts_str);
- }, 30000);
-
- $('#cc_timestamp').text('just now');
- }
-
- // save position and update only if different is available
- if(CHASE_timer < (new Date()).getTime() &&
- (
- GPS_lat != lat ||
- GPS_lon != lon ||
- GPS_alt != alt ||
- GPS_speed != speed
- )
- )
- {
- GPS_lat = lat;
- GPS_lon = lon;
- GPS_alt = alt;
- GPS_speed = speed;
- GPS_ts = parseInt(position.timestamp/1000);
- $('#cc_timestamp').text('just now');
-
- // update look angles once we get position
- if(follow_vehicle !== null && vehicles[follow_vehicle] !== undefined) {
- update_lookangles(follow_vehicle);
- }
-
- if(CHASE_enabled) {
- ChaseCar.updatePosition(callsign, position);
- CHASE_timer = (new Date()).getTime() + 15000;
-
- if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'upload', 'chase car position']);
- }
- }
- else { return; }
-
- // add/update marker on the map (tracker.js)
- updateCurrentPosition(lat, lon);
-
- // round the coordinates
- lat = parseInt(lat * 10000)/10000; // 4 decimal places (11m accuracy at equator)
- lon = parseInt(lon * 10000)/10000; // 4 decimal places
- speed = parseInt(speed * 10)/10; // 1 decimal place
- accuracy = parseInt(accuracy);
- alt = parseInt(alt);
-
- // dispaly them in the top right corner
- $('#app_name b').html(lat + ' ' + lon);
-
- // update chase car interface
- $('#cc_lat').text(lat);
- $('#cc_lon').text(lon);
- $('#cc_alt').text(alt + " m");
- $('#cc_accuracy').text(accuracy + " m");
- $('#cc_speed').text(speed + " m/s");
- /*
- },
- function() {
- // when there is no location
- $('#app_name b').html('mobile tracker');
- });
- */
-};
-
-var twoZeroPad = function(n) {
- n = String(n);
- return (n.length<2) ? '0'+n : n;
-};
-
-// updates timebox
-var updateTimebox = function(date) {
- var elm = $("#timebox");
- var a,b,c,d,e,f,g,z;
-
- a = date.getUTCFullYear();
- b = twoZeroPad(date.getUTCMonth()+1); // months 0-11
- c = twoZeroPad(date.getUTCDate());
- e = twoZeroPad(date.getUTCHours());
- f = twoZeroPad(date.getUTCMinutes());
- g = twoZeroPad(date.getUTCSeconds());
-
- elm.find(".current").text("UTC: "+a+'-'+b+'-'+c+' '+e+':'+f+':'+g);
-
- a = date.getFullYear();
- b = twoZeroPad(date.getMonth()+1); // months 0-11
- c = twoZeroPad(date.getDate());
- e = twoZeroPad(date.getHours());
- f = twoZeroPad(date.getMinutes());
- g = twoZeroPad(date.getSeconds());
- z = date.getTimezoneOffset() / -60;
-
- elm.find(".local").text("Local: "+a+'-'+b+'-'+c+' '+e+':'+f+':'+g+" "+((z<0)?"-":"+")+z);
-};
-
-var format_time_friendly = function(start, end) {
- var dt = Math.floor((end - start) / 1000);
- if(dt < 0) return null;
-
- if(dt < 60) return dt + 's';
- else if(dt < 3600) return Math.floor(dt/60)+'m';
- else if(dt < 86400) {
- dt = Math.floor(dt/60);
- return Math.floor(dt/60)+'h '+(dt % 60)+'m';
- } else {
- dt = Math.floor(dt/3600);
- return Math.floor(dt/24)+'d '+(dt % 24)+'h';
- }
-};
-
-// runs every second
-var updateTime = function(date) {
- // update timebox
- var elm = $("#timebox.present");
- if(elm.length > 0) updateTimebox(date);
-
- // update friendly delta time fields
- elm = $(".friendly-dtime");
- if(elm.length > 0) {
- var now = new Date().getTime();
-
- elm.each(function(k,v) {
- var e = $(v);
- if(e.attr('data-timestamp') === undefined) return;
- var ts = e.attr('data-timestamp');
- var str = format_time_friendly(ts, now);
- if(str) e.text(str + ' ago');
- });
- }
-};
-
-
$(window).ready(function() {
- // refresh timebox
- setInterval(function() {
- updateTime(new Date());
- }, 1000);
-
// resize elements if needed
checkSize();
// add inline scroll to vehicle list
- listScroll = new IScroll('#main', {
- hScrollbar: false,
- hScroll: false,
- snap: false,
- mouseWheel: true,
- scrollbars: true,
- scrollbarClass: 'scrollStyle',
- shrinkScrollbars: 'scale',
- tap: true,
- click: true,
- disableMouse: false,
- disableTouch: false,
- disablePointer: false,
- });
-
- $('#telemetry_graph').on('click', '.graph_label', function() {
- var e = $(this), h;
- if(e.hasClass('active')) {
- e.removeClass('active');
- h = $('#map').height() + $('#telemetry_graph').height();
-
- plot_open = false;
-
- //analytics
- if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'UI', 'Collapse', 'Telemetry Graph']);
- } else {
- e.addClass('active');
- h = $('#map').height() - $('#telemetry_graph').height();
-
- plot_open = true;
-
- //analytics
- if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'UI', 'Expand', 'Telemetry Graph']);
- }
- $('#map').stop(null,null).animate({'height': h}, function() {
- if(map) google.maps.event.trigger(map, 'resize');
-
- if(plot_open &&
- follow_vehicle !== null &&
- (follow_vehicle != graph_vehicle || vehicles[follow_vehicle].graph_data_updated)) updateGraph(follow_vehicle, true);
- });
- });
-
- // expand graph on startup, if nessary
- if(wvar.graph_expanded) $('#telemetry_graph .graph_label').click();
-
- // hysplit button
- $("#main").on('click','.row .data .vbutton.hysplit', function(event) {
- event.stopPropagation();
-
- var elm = $(this);
- var name = elm.attr('data-vcallsign');
-
- if(elm.hasClass("active")) {
- elm.removeClass('active');
- hysplit[name].setMap(null);
- }
- else {
- elm.addClass('active');
- hysplit[name].setMap(map);
- }
- });
-
- $("#main").on('click','.row .data .vbutton.path', function(event) {
- event.stopPropagation();
-
- var elm = $(this);
- var name = elm.attr('data-vcallsign');
-
- if(elm.hasClass("active")) {
- elm.removeClass('active');
- set_polyline_visibility(name, false);
- }
- else {
- elm.addClass('active');
- set_polyline_visibility(name, true);
- }
- });
-
- // reset nite-overlay and timebox when mouse goes out of the graph box
- $("#telemetry_graph").on('mouseout','.holder', function() {
- if(plot_crosshair_locked) return;
-
- updateGraph(null, true);
- });
-
- // hand cursor for dragging the vehicle list
- $("#main").on("mousedown", ".row", function () {
- $("#main").addClass("drag");
- });
- $("body").on("mouseup", function () {
- $("#main").removeClass("drag");
- });
-
- // confirm dialog when launchnig a native map app with coordinates
- //$('#main').on('click', '#launch_mapapp', function() {
- // var answer = confirm("Launch your maps app?");
-
- // //analytics
- // if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'Functionality', ((answer)?"Yes":"No"), 'Coord Click']);
-
- // return answer;
- //});
-
- // follow vehicle by clicking on data
- $('#main').on('click', '.row .data', function() {
- var e = $(this).parent();
-
- followVehicle(e.attr('data-vcallsign'));
- });
-
- // expand/collapse data when header is clicked
- $('#main').on('click', '.row .header', function() {
- var e = $(this).parent();
- if(e.hasClass('active')) {
- // collapse data for selected vehicle
- e.removeClass('active');
- e.find('.data').hide();
-
- listScroll.refresh();
-
- // disable following only we are collapsing the followed vehicle
- if(follow_vehicle !== null && follow_vehicle == e.attr('data-vcallsign')) {
- stopFollow();
- }
- } else {
- // expand data for selected vehicle
- e.addClass('active');
- e.find('.data').show();
-
- listScroll.refresh();
-
- // auto scroll when expanding an item
- if($('.portrait:visible').length) {
- var eName = "." + e.parent().attr('class') + " ." + e.attr('class').match(/vehicle\d+/)[0];
- listScroll.scrollToElement(eName);
- }
-
- // pan to selected vehicle
- followVehicle(e.attr('data-vcallsign'));
- }
- });
-
- // menu interface options
- $('.nav')
- .on('click', 'li', function() {
- var e = $(this);
- var name = e.attr('class').replace(" last","");
-
- // makes the menu buttons act like a switch
- if($("#"+name+"box").is(':visible')) name = 'home';
-
- var box = $("#"+name+"box");
-
- if(box.is(':hidden')) {
- $('.flatpage, #homebox').hide();
- box.show().scrollTop(0);
-
- if(name == 'about' && !$('#motd').hasClass('inited')) {
- $('#motd').addClass('inited');
-
- $.getJSON("//spacenear.us/tracker/datanew.php?type=info", function(data) {
- if('html' in data) $('#motd').html(data.html.replace(/\\/g,''));
- });
-
- var iframe = box.find('iframe');
- var src = iframe.attr('data-src');
- iframe.attr('src', src);
- }
-
- // analytics
- var pretty_name;
- switch(name) {
- case "home": pretty_name = "Map"; break;
- case "chasecar": pretty_name = "Chase Car"; break;
- default: pretty_name = name[0].toUpperCase() + name.slice(1);
- }
-
- if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'UI Menubar', 'Open Page', pretty_name]);
- }
- checkSize();
- });
-
- // toggle functionality for switch button
- $("#sw_chasecar").click(function() {
- var e = $(this);
- var field = $('#cc_callsign');
-
- // turning the switch off
- if(e.hasClass('on')) {
- field.removeAttr('disabled');
- e.removeClass('on').addClass('off');
-
- if(navigator.geolocation) navigator.geolocation.clearWatch(CHASE_enabled);
- CHASE_enabled = null;
- //CHASE_enabled = false;
-
- // blue man reappers :)
- if(currentPosition && currentPosition.marker) currentPosition.marker.setVisible(true);
-
- // analytics
- if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'Functionality', 'Turn Off', 'Chase Car']);
- // turning the switch on
- } else {
- if(callsign.length < 5) { alert('Please enter a valid callsign, at least 5 characters'); return; }
- if(!callsign.match(/^[a-zA-Z0-9\_\-]+$/)) { alert('Invalid characters in callsign (use only a-z,0-9,-,_)'); return; }
-
- field.attr('disabled','disabled');
- e.removeClass('off').addClass('on');
-
- // push listener doc to habitat
- // this gets a station on the map, under the car marker
- // im still not sure its nessesary
- if(!CHASE_listenerSent) {
- if(offline.get('opt_station')) {
- ChaseCar.putListenerInfo(callsign);
- CHASE_listenerSent = true;
- }
- }
- // if already have a position push it to habitat
- if(GPS_ts) {
- ChaseCar.updatePosition(callsign, { coords: { latitude: GPS_lat, longitude: GPS_lon, altitude: GPS_alt, speed: GPS_speed }});
- if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'upload', 'chase car position']);
- }
-
- if(navigator.geolocation) CHASE_enabled = navigator.geolocation.watchPosition(positionUpdateHandle, positionUpdateError);
- //CHASE_enabled = true;
-
- // hide the blue man
- if(currentPosition && currentPosition.marker) currentPosition.marker.setVisible(false);
-
- // analytics
- if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'Functionality', 'Turn On', 'Chase Car']);
- }
- });
-
- // remember callsign as a cookie
- $("#cc_callsign").on('change keyup', function() {
- callsign = $(this).val().trim();
- offline.set('callsign', callsign); // put in localStorage
- CHASE_listenerSent = false;
- });
-
- // load value from localStorage
- callsign = offline.get('callsign');
- $('#cc_callsign').val(callsign);
-
- // settings page
-
- // list of all switches
- var opts = [
- "#sw_layers_aprs",
- "#sw_offline",
- "#sw_station",
- "#sw_imperial",
- "#sw_haxis_hours",
- "#sw_daylight",
- "#sw_hide_receivers",
- "#sw_hide_timebox",
- "#sw_hilight_vehicle",
- "#sw_nowelcome",
- "#sw_interpolate",
- ];
-
- // applies functionality when switches are toggled
- $(opts.join(',')).click(function() {
- var e = $(this);
- var name = e.attr('id').replace('sw', 'opt');
- var on;
-
- if(e.hasClass('on')) {
- e.removeClass('on').addClass('off');
- on = 0;
-
- //analytics
- if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'Functionality', 'Turn Off', name]);
- } else {
- e.removeClass('off').addClass('on');
- on = 1;
-
- //analytics
- if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'Functionality', 'Turn On', name]);
- }
-
- // remember choice
- offline.set(name, on);
-
- // execute functionality
- switch(name) {
- case "opt_hilight_vehicle":
- if(on) focusVehicle(follow_vehicle);
- else focusVehicle(null, true);
- break;
- case "opt_imperial":
- case "opt_haxis_hours":
- refreshUI();
- break;
- case "opt_daylight":
- if(on) { nite.show(); }
- else { nite.hide(); }
- break;
- case "opt_hide_receivers":
- if(on) {
- updateReceivers([]);
- clearTimeout(periodical_listeners);
- }
- else {
- refreshReceivers();
- }
- break;
- case "opt_hide_timebox":
- var elm = $("#timebox");
- if(on) {
- elm.removeClass('past').removeClass('present').hide();
- $('#lookanglesbox').css({top:'7px'});
- } else {
- elm.addClass('present').show();
- $('#lookanglesbox').css({top:'40px'});
- }
- break;
- case "opt_layers_aprs":
- if(on) map.overlayMapTypes.setAt("1", overlayAPRS);
- else map.overlayMapTypes.setAt("1", null);
- break;
- case "opt_interpolate":
- if(on) { graph_gap_size = graph_gap_size_max; }
- else { graph_gap_size = graph_gap_size_default; }
- clean_refresh(wvar.mode, true, false);
- break;
- }
- });
-
- // set the switch, based on the remembered choice
- for(var k in opts) {
- var switch_id = opts[k];
- var opt_name = switch_id.replace("#sw_", "opt_");
-
- if(offline.get(opt_name)) $(switch_id).removeClass('off').addClass('on');
- }
-
- // force re-cache
- $('#sw_cache').click(function() {
- var e = $(this).removeClass('off').addClass('on');
- if(confirm("The app will automatically reload, if new version is available.")) {
- force_check_cache = true;
-
- try {
- applicationCache.update();
- } catch (v) {
- force_check_cache = false;
- alert("There is no applicationCache available");
- }
- }
- e.removeClass('on').addClass('off');
- });
-
- // We are able to get GPS position on idevices, if the user allows
- // The position is displayed in top right corner of the screen
- // This should be very handly for in the field tracking
- //setTimeout(function() {updateCurrentPosition(50.27533, 3.335166);}, 5000);
- if(navigator.geolocation) {
- // if we have geolocation services, show the locate me button
- // the button pants the map to the user current location
- if(is_mobile && !wvar.enabled) $(".chasecar").show();
- $("#locate-me,#app_name").attr('style','').click(function() {
- if(map && currentPosition) {
- // disable following of vehicles
- stopFollow();
- // open map
- $('.nav .home').click();
- // pan map to our current location
- map.panTo(new google.maps.LatLng(currentPosition.lat, currentPosition.lon));
-
- //analytics
- if(typeof _gaq == 'object') _gaq.push(['_trackEvent', 'Functionality', 'Locate me']);
- } else {
- alert("No position available");
- }
- });
-
- navigator.geolocation.getCurrentPosition(positionUpdateHandle);
- // check for location update every 30sec
- //setInterval(positionUpdateHandle, 30000);
- // immediatelly check for position
- //positionUpdateHandle();
- }
-
- // weather feature
-
- // list of overlays
- var overlayList = [
- ['Global', [
- ['google-radar','Google Earth Radar'],
- ['nrl-global-cloudtop','NRL Monterey Cloudtop'],
- ['nrl-global-ir','NRL Monterey IR'],
- ['nrl-global-vapor','NRL Monterey Vapor']
- ]],
- ['Europe/Africa', [
- ['meteosat-Odeg-MPE', 'METEOSAT Precip. Estimate']
- ]],
- ['Indian Ocean', [
- ['meteosat-iodc-MPE', 'METEOSAT IODC Precip. Est.']
- ]],
- ['North America', [
- ['nexrad-n0q-900913', 'NEXRAD Base Reflectivity'],
- ['goes-ir-4km-900913', 'GOES NA Infrared ~4km'],
- ['goes-wv-4km-900913', 'GOES NA Water Vapor ~4km'],
- ['goes-vis-1km-900913', 'GOES NA Visible ~1km'],
- ['goes-east-ir-4km-900913', 'GOES East CONUS Infrared'],
- ['goes-east-wv-4km-900913', 'GOES East CONUS Water Vapor'],
- ['goes-east-vis-1km-900913', 'GOES East CONUS Visible'],
- ['goes-west-ir-4km-900913', 'GOES West CONUS Infrared'],
- ['goes-west-wv-4km-900913', 'GOES West CONUS Water Vapor'],
- ['goes-west-vis-1km-900913', 'GOES West CONUS Visible'],
- ['hawaii-vis-900913', 'GOES West Hawaii Visible'],
- ['alaska-vis-900913', 'GOES West Alaska Visible'],
- ['alaska-ir-900913', 'GOES West Alaska IR'],
- ['alaska-wv-900913', 'GOES West Alaska Water Vapor'],
- ['q2-n1p-900913', 'Q2 1 Hour Precipitation'],
- ['q2-p24h-900913', 'Q2 24 Hour Precipitation'],
- ['q2-p48h-900913', 'Q2 48 Hour Precipitation'],
- ['q2-p72h-900913', 'Q2 72 Hour Precipitation'],
- ['q2-hsr-900913', 'MRMS Hybrid-Scan Reflectivity Composite.']
- ]]
- ];
-
- // generate the list of switches for each overlay
- var elm = $("#weatherbox .slimContainer");
- var j;
- for(j in overlayList) {
- var region = overlayList[j][0];
- var switches = overlayList[j][1];
-
- elm.append("
"+region+"
");
-
- var i;
- for(i in switches) {
- var id = switches[i][0];
- var name = switches[i][1];
-
- var html = '
' +
- ''+name+'' +
- '
' +
- '' +
- '' +
- '
' +
- '
';
-
- elm.append(html);
- }
- }
-
- // the magic that makes the switches do things
- elm.find(".switch").click(function() {
- var e = $(this);
- var name = e.attr('id').replace('sw', 'opt');
- var id = name.replace("opt_weather_","");
- var on;
-
- if(e.hasClass('on')) {
- e.removeClass('on').addClass('off');
- on = 0;
- } else {
- // only one overlay at a time
- $("#weatherbox .switch").removeClass('on').addClass('off');
- e.removeClass('off').addClass('on');
- on = 1;
- }
-
- weatherImageOverlay.setMap(null);
- weatherGoogleRadar.setMap(null);
- map.overlayMapTypes.setAt("0", null);
-
- if(on) {
- if(id == "google-radar") {
- weatherGoogleRadar.setMap(map);
- return;
- } else if(id in weatherImageOverlayList) {
- var o = weatherImageOverlayList[id];
- var sw = new google.maps.LatLng(o[1][0][0], o[1][0][1]);
- var ne = new google.maps.LatLng(o[1][1][0], o[1][1][1]);
- var bounds = new google.maps.LatLngBounds(sw, ne);
- weatherImageOverlay = new google.maps.GroundOverlay(o[0], bounds, {opacity: 0.7});
- weatherImageOverlay.setMap(map);
- return;
- }
-
- weatherOverlayId = id;
- map.overlayMapTypes.setAt("0", weatherOverlay);
- }
- });
-
- $("header .search form").on('submit', function(e) {
- e.preventDefault();
-
- var text = $("header .search input[type='text']").val();
-
- if(text === wvar.query) return;
-
- // when running an empty search, it's probably best to reset the query mode
-
- wvar.query = text;
- stopFollow();
- zoomed_in = false;
- wvar.zoom = true;
-
- if(text === "") { wvar.mode = null; }
- clean_refresh(wvar.mode, true, true);
- });
+ listScroll = new iScroll('main', { hScrollbar: false, hScroll: false, snap: false, scrollbarClass: 'scrollStyle' });
});
diff --git a/js/chasecar.lib.js b/js/chasecar.lib.js
deleted file mode 100644
index 9b24c27..0000000
--- a/js/chasecar.lib.js
+++ /dev/null
@@ -1,95 +0,0 @@
-/* Habitat ChaseCar lib
- * Uploads geolocation for chase cars to habitat
- *
- * Author: Rossen Gerogiev
- * Requires: jQuery
- */
-
-ChaseCar = {
- db_uri: "https://tracker.habhub.org/", // db address
- uuidsRequested: false, // used in .request() to track whenever it has requested uuids
- _uuids: [], // array with uuids
- ucount: 20, // number of uuids in the _uuids array (determines how many uuids are request at a time)
- uused: 0, // how many have been used for requests
- queue: [] // request queue, incase we dont have uuids
-};
-ChaseCar.uused = ChaseCar.ucount; // we start without any uuids
-
-// get some uuids for us to use
-ChaseCar.getUUIDS = function(callback) {
- $.getJSON(ChaseCar.db_uri + "_uuids?count=" + ChaseCar.ucount, function (data) {
- ChaseCar._uuids = data.uuids; // load the new uuids
- ChaseCar.uused = 0; // reset counter
- if(callback) callback();
- });
-};
-// handles request and uuid management
-// @doc JSONobject
-ChaseCar.request = function(doc) {
- if(doc) { ChaseCar.queue.push(doc); }
-
- var i = ChaseCar.queue.length;
- while(i--) {
- if(ChaseCar.ucount == ChaseCar.uused && !ChaseCar.uuidsRequested) {
- ChaseCar.uuidsRequested = true; // blocks further uuids request until the current one completes
- ChaseCar.getUUIDS(function() {
- ChaseCar.uuidsRequested = false;
- ChaseCar.request();
- });
- return;
- } else {
- ChaseCar.uused++;
- // get one uuid and one doc from the queue and push to habitat
- var uuid = ChaseCar._uuids.shift();
- doc = ChaseCar.queue.shift();
-
- // update doc with uuids and time of upload
- doc._id = uuid;
- doc.time_uploaded = (new Date()).toISOString();
-
- // push the doc to habitat
- $.ajax({
- type: "POST",
- url: ChaseCar.db_uri + "habitat/",
- contentType: "application/json; charset=utf-8",
- dataType: "json",
- data: JSON.stringify(doc),
- });
- }
- }
-};
-// run once at start,
-// @callsign string
-ChaseCar.putListenerInfo = function(callsign) {
- if(!callsign) return;
-
- ChaseCar.request({
- 'type': "listener_information",
- 'time_created': (new Date()).toISOString(),
- 'data': { 'callsign': callsign }
- });
-};
-// run every time the location has changed
-// @callsign string
-// @position object (geolocation position object)
-ChaseCar.updatePosition = function(callsign, position) {
- if(!position || !position.coords) return;
-
- ChaseCar.request({
- 'type': "listener_telemetry",
- 'time_created': (new Date()).toISOString(),
- 'data': {
- 'callsign': callsign,
- 'chase': true,
- 'latitude': position.coords.latitude,
- 'longitude': position.coords.longitude,
- 'altitude': ((!!position.coords.altitude) ? position.coords.altitude : 0),
- 'speed': ((!!position.coords.speed) ? position.coords.speed : 0),
- 'client': {
- 'name': 'Habitat Mobile Tracker',
- 'version': '{VER}',
- 'agent': navigator.userAgent
- }
- }
- });
-};
diff --git a/js/gmaps_extentions.js b/js/gmaps_extentions.js
deleted file mode 100644
index fdc73e6..0000000
--- a/js/gmaps_extentions.js
+++ /dev/null
@@ -1,220 +0,0 @@
-
-// custom label function
-
-google.maps.Label = function(opt_options) {
- // init default values
- this.set('visible', true);
- this.set('opacity', 1);
- this.set('clickable', false);
- this.set('strokeColor', "#00F");
- this.set('text', "");
- this.set('textOnly', false); // true only text, false text within a box
-
- this.setValues(opt_options);
-
- var span = this.span_ = document.createElement('span');
- span.style.cssText = 'position: relative; left: -50%;' +
- 'white-space: nowrap; color: #000;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;-khtml-user-select:none;';
-
- span.style.cssText += !this.get('textOnly') ?
- 'border: 1px solid '+this.get('strokeColor')+'; border-radius: 5px; ' +
- 'top:-12px;font-size:9px;padding: 2px; background-color: white'
- :
- 'top:-8px;font-size:12px;font-weight: bold; text-shadow: 2px 0 0 #fff, -2px 0 0 #fff, 0 2px 0 #fff, 0 -2px 0 #fff, 1px 1px #fff, -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff;'
- ;
-
- var div = this.div_ = document.createElement('div');
- div.appendChild(span);
- div.style.cssText = 'position: absolute; display: none;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;-khtml-user-select:none;';
-};
-
-google.maps.Label.prototype = new google.maps.OverlayView();
-
-
-// Implement onAdd
-google.maps.Label.prototype.onAdd = function() {
- var pane = this.getPanes().overlayImage;
- pane.appendChild(this.div_);
-
- // redraw if any option is changed
- var ctx = this;
- var callback = function() { ctx.draw(); };
- this.listeners_ = [
- google.maps.event.addListener(this, 'opacity_changed', callback),
- google.maps.event.addListener(this, 'position_changed', callback),
- google.maps.event.addListener(this, 'visible_changed', callback),
- google.maps.event.addListener(this, 'clickable_changed', callback),
- google.maps.event.addListener(this, 'text_changed', callback),
- google.maps.event.addListener(this, 'zindex_changed', callback),
- google.maps.event.addDomListener(this.div_, 'click', function() {
- if (ctx.get('clickable')) {
- google.maps.event.trigger(ctx, 'click');
- }
- })
- ];
-};
-
-
-// Implement onRemove
-google.maps.Label.prototype.onRemove = function() {
- this.div_.parentNode.removeChild(this.div_);
-
- // remove all listeners
- for (var i = 0, j = this.listeners_.length; i < j; i++) {
- google.maps.event.removeListener(this.listeners_[i]);
- }
-};
-
-
-// Implement draw
-google.maps.Label.prototype.draw = function() {
- var projection = this.getProjection();
- var position = projection.fromLatLngToDivPixel(this.get('position'));
-
- var div = this.div_;
- if(position !== null) {
- div.style.left = position.x + 'px';
- div.style.top = position.y + 'px';
- }
-
- div.style.display = this.get('visible') && this.get('opacity') >= 0.6 ? 'block' : 'none';
- this.span_.style.cursor = this.get('clickable') ? 'pointer' : '';
- div.style.zIndex = this.get('zIndex');
- this.span_.innerHTML = this.get('text').toString();
-};
-
-
-// custom dropdown menu control
-
-google.maps.DropDownControl = function(options) {
- var ctx = this;
- this.options = options;
-
- // generate the controls
- this.div_ = document.createElement('div');
- this.div_.className = "gmnoprint";
- this.div_.draggable = false;
- this.div_.style.cssText = "margin: 10px; margin-top: 0;z-index: 0; position: absolute; cursor: pointer; text-align: left; width: 85px; right: 0px; top: 0px;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;-khtml-user-select:none";
-
- this.div_head = document.createElement('div');
- this.div_head.style.cssText = "direction: ltr; overflow: hidden; text-align: left; position: relative; color: rgb(0, 0, 0); font-family: Roboto, Arial, sans-serif; -webkit-user-select: none; font-size: 11px; padding: 8px; border-radius: 2px; -webkit-background-clip: padding-box; box-shadow: rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px; font-weight: 500; background-color: rgb(255, 255, 255); background-clip: padding-box;";
- this.div_head.title = options.title;
-
- google.maps.event.addDomListener(this.div_head, 'mouseover', function(){
- ctx.div_head.style.backgroundColor = "rgb(235,235,235)";
- });
- google.maps.event.addDomListener(this.div_head, 'mouseout', function(){
- ctx.div_head.style.backgroundColor = "rgb(255,255,255)";
- });
-
- this.header = document.createElement('span');
- this.header.innerHTML = (options.headerPrefix || "") + options.list[options.listDefault || 0];
- var arrow = document.createElement('img');
- arrow.src = "//maps.gstatic.com/mapfiles/arrow-down.png";
- arrow.style.cssText = "-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;-khtml-user-select: none; border: 0px none; padding: 0px; margin: -2px 0px 0px; position: absolute; right: 6px; top: 50%; width: 7px; height: 4px;";
-
- this.div_head.appendChild(this.header);
- this.div_head.appendChild(arrow);
- this.div_.appendChild(this.div_head);
-
- // generate list of dropdown entries
- this.div_list = document.createElement('div');
- this.div_list.style.cssText = "z-index: -1; padding: 2px; border-bottom-left-radius: 2px; border-bottom-right-radius: 2px; box-shadow: rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px; position: absolute; top: 100%; left: 0px; right: 0px; text-align: left; display: none; background-color: white;";
-
- var div_list = this.div_list;
-
- options.list.forEach(function(name) {
- var row = document.createElement('div');
- row.style.cssText = "color: rgb(86, 86, 86); font-family: Roboto, Arial, sans-serif; -webkit-user-select: none; font-size: 11px; padding: 6px; background-color: rgb(255, 255, 255);";
- row.innerHTML = name;
-
- google.maps.event.addDomListener(row, 'click', function(){
- if(ctx.options.callback(row.innerHTML)) {
- ctx.header.innerHTML = (ctx.options.headerPrefix || "") + row.innerHTML;
- row.style.fontWeight = "800";
- row.style.color = "rgb(0,0,0)";
- }
- });
- google.maps.event.addDomListener(row, 'mouseover', function(){
- if(ctx.header.innerHTML == (ctx.options.headerPrefix || "") + row.innerHTML) {
- row.style.fontWeight = "800";
- row.style.color = "rgb(0,0,0)";
- }
- row.style.backgroundColor = "rgb(235,235,235)";
- });
- google.maps.event.addDomListener(row, 'mouseout', function(){
- row.style.fontWeight = "500";
- row.style.color = "rgb(86,86,86)";
- row.style.backgroundColor = "rgb(255,255,255)";
- });
-
- div_list.appendChild(row);
- });
-
- this.div_.appendChild(this.div_list);
-
- // add control
- options.map.controls[options.position].push(this.div_);
-
- // event for expanding
-
- google.maps.event.addDomListener(this.div_head, 'click', function(){
- clearTimeout(ctx.hideTimeout);
- div_list.style.display = (div_list.style.display != 'none') ? 'none' : 'block';
- });
- google.maps.event.addDomListener(this.div_, 'mouseout', function(){
- ctx.hideTimeout = setTimeout(function() { div_list.style.display = 'none'; }, 1000);
- });
- google.maps.event.addDomListener(this.div_, 'mouseover', function(){
- clearTimeout(ctx.hideTimeout);
- });
-};
-
-google.maps.DropDownControl.prototype.setVisible = function(isVisible) {
- isVisible = !!isVisible;
- this.div_.style.display = (isVisible) ? 'block' : 'none';
-};
-
-google.maps.DropDownControl.prototype.select = function(text) {
- this.header.innerHTML = (this.options.headerPrefix || "") + text;
-};
-
-// simple status control
-
-google.maps.StatusTextControl = function(options) {
- this.options = options || {
- text: "",
- map: null,
- position: 0,
- fontSize: "10px",
- };
-
- this.div_ = document.createElement('div');
- this.div_.style.cssText = "display: none";
- this.div_.innerHTML = "