Commit 58256acb by Rashid Khan

Switch styles to LESS build, add inspector.html, update flot

parent c87ca770
......@@ -46,14 +46,28 @@ module.exports = function (grunt) {
moment: false
}
}
},
less: {
production: {
options: {
paths: ["vendor/bootstrap/less"],
yuicompress:true
},
files: {
"common/css/bootstrap.dark.min.css": "vendor/bootstrap/less/bootstrap.dark.less",
"common/css/bootstrap.light.min.css": "vendor/bootstrap/less/bootstrap.light.less"
}
}
}
});
// load plugins
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('assemble-less');
// Default task.
grunt.registerTask('default', ['jshint']);
grunt.registerTask('default', ['jshint','less']);
};
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -132,10 +132,20 @@
max-width: 500px;
}
.modal {
width: 770px;
margin-left: -385px;
top: 10px !important;
}
.tiny {
font-size: 50%;
}
.smaller {
font-size: 70%;
}
.small {
font-size: 85%;
}
......
/* Javascript plotting library for jQuery, version 0.8 alpha.
/* Javascript plotting library for jQuery, version 0.8.1.
Copyright (c) 2007-2012 IOLA and Ole Laursen.
Copyright (c) 2007-2013 IOLA and Ole Laursen.
Licensed under the MIT license.
*/
......@@ -33,6 +33,462 @@ Licensed under the MIT license.
// the actual Flot code
(function($) {
// Cache the prototype hasOwnProperty for faster access
var hasOwnProperty = Object.prototype.hasOwnProperty;
///////////////////////////////////////////////////////////////////////////
// The Canvas object is a wrapper around an HTML5 <canvas> tag.
//
// @constructor
// @param {string} cls List of classes to apply to the canvas.
// @param {element} container Element onto which to append the canvas.
//
// Requiring a container is a little iffy, but unfortunately canvas
// operations don't work unless the canvas is attached to the DOM.
function Canvas(cls, container) {
var element = container.children("." + cls)[0];
if (element == null) {
element = document.createElement("canvas");
element.className = cls;
$(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 })
.appendTo(container);
// If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas
if (!element.getContext) {
if (window.G_vmlCanvasManager) {
element = window.G_vmlCanvasManager.initElement(element);
} else {
throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.");
}
}
}
this.element = element;
var context = this.context = element.getContext("2d");
// Determine the screen's ratio of physical to device-independent
// pixels. This is the ratio between the canvas width that the browser
// advertises and the number of pixels actually present in that space.
// The iPhone 4, for example, has a device-independent width of 320px,
// but its screen is actually 640px wide. It therefore has a pixel
// ratio of 2, while most normal devices have a ratio of 1.
var devicePixelRatio = window.devicePixelRatio || 1,
backingStoreRatio =
context.webkitBackingStorePixelRatio ||
context.mozBackingStorePixelRatio ||
context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio ||
context.backingStorePixelRatio || 1;
this.pixelRatio = devicePixelRatio / backingStoreRatio;
// Size the canvas to match the internal dimensions of its container
this.resize(container.width(), container.height());
// Collection of HTML div layers for text overlaid onto the canvas
this.textContainer = null;
this.text = {};
// Cache of text fragments and metrics, so we can avoid expensively
// re-calculating them when the plot is re-rendered in a loop.
this._textCache = {};
}
// Resizes the canvas to the given dimensions.
//
// @param {number} width New width of the canvas, in pixels.
// @param {number} width New height of the canvas, in pixels.
Canvas.prototype.resize = function(width, height) {
if (width <= 0 || height <= 0) {
throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height);
}
var element = this.element,
context = this.context,
pixelRatio = this.pixelRatio;
// Resize the canvas, increasing its density based on the display's
// pixel ratio; basically giving it more pixels without increasing the
// size of its element, to take advantage of the fact that retina
// displays have that many more pixels in the same advertised space.
// Resizing should reset the state (excanvas seems to be buggy though)
if (this.width != width) {
element.width = width * pixelRatio;
element.style.width = width + "px";
this.width = width;
}
if (this.height != height) {
element.height = height * pixelRatio;
element.style.height = height + "px";
this.height = height;
}
// Save the context, so we can reset in case we get replotted. The
// restore ensure that we're really back at the initial state, and
// should be safe even if we haven't saved the initial state yet.
context.restore();
context.save();
// Scale the coordinate space to match the display density; so even though we
// may have twice as many pixels, we still want lines and other drawing to
// appear at the same size; the extra pixels will just make them crisper.
context.scale(pixelRatio, pixelRatio);
};
// Clears the entire canvas area, not including any overlaid HTML text
Canvas.prototype.clear = function() {
this.context.clearRect(0, 0, this.width, this.height);
};
// Finishes rendering the canvas, including managing the text overlay.
Canvas.prototype.render = function() {
var cache = this._textCache;
// For each text layer, add elements marked as active that haven't
// already been rendered, and remove those that are no longer active.
for (var layerKey in cache) {
if (hasOwnProperty.call(cache, layerKey)) {
var layer = this.getTextLayer(layerKey),
layerCache = cache[layerKey];
layer.hide();
for (var styleKey in layerCache) {
if (hasOwnProperty.call(layerCache, styleKey)) {
var styleCache = layerCache[styleKey];
for (var key in styleCache) {
if (hasOwnProperty.call(styleCache, key)) {
var positions = styleCache[key].positions;
for (var i = 0, position; position = positions[i]; i++) {
if (position.active) {
if (!position.rendered) {
layer.append(position.element);
position.rendered = true;
}
} else {
positions.splice(i--, 1);
if (position.rendered) {
position.element.detach();
}
}
}
if (positions.length == 0) {
delete styleCache[key];
}
}
}
}
}
layer.show();
}
}
};
// Creates (if necessary) and returns the text overlay container.
//
// @param {string} classes String of space-separated CSS classes used to
// uniquely identify the text layer.
// @return {object} The jQuery-wrapped text-layer div.
Canvas.prototype.getTextLayer = function(classes) {
var layer = this.text[classes];
// Create the text layer if it doesn't exist
if (layer == null) {
// Create the text layer container, if it doesn't exist
if (this.textContainer == null) {
this.textContainer = $("<div class='flot-text'></div>")
.css({
position: "absolute",
top: 0,
left: 0,
bottom: 0,
right: 0,
'font-size': "smaller",
color: "#545454"
})
.insertAfter(this.element);
}
layer = this.text[classes] = $("<div></div>")
.addClass(classes)
.css({
position: "absolute",
top: 0,
left: 0,
bottom: 0,
right: 0
})
.appendTo(this.textContainer);
}
return layer;
};
// Creates (if necessary) and returns a text info object.
//
// The object looks like this:
//
// {
// width: Width of the text's wrapper div.
// height: Height of the text's wrapper div.
// element: The jQuery-wrapped HTML div containing the text.
// positions: Array of positions at which this text is drawn.
// }
//
// The positions array contains objects that look like this:
//
// {
// active: Flag indicating whether the text should be visible.
// rendered: Flag indicating whether the text is currently visible.
// element: The jQuery-wrapped HTML div containing the text.
// x: X coordinate at which to draw the text.
// y: Y coordinate at which to draw the text.
// }
//
// Each position after the first receives a clone of the original element.
//
// The idea is that that the width, height, and general 'identity' of the
// text is constant no matter where it is placed; the placements are a
// secondary property.
//
// Canvas maintains a cache of recently-used text info objects; getTextInfo
// either returns the cached element or creates a new entry.
//
// @param {string} layer A string of space-separated CSS classes uniquely
// identifying the layer containing this text.
// @param {string} text Text string to retrieve info for.
// @param {(string|object)=} font Either a string of space-separated CSS
// classes or a font-spec object, defining the text's font and style.
// @param {number=} angle Angle at which to rotate the text, in degrees.
// Angle is currently unused, it will be implemented in the future.
// @param {number=} width Maximum width of the text before it wraps.
// @return {object} a text info object.
Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) {
var textStyle, layerCache, styleCache, info;
// Cast the value to a string, in case we were given a number or such
text = "" + text;
// If the font is a font-spec object, generate a CSS font definition
if (typeof font === "object") {
textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family;
} else {
textStyle = font;
}
// Retrieve (or create) the cache for the text's layer and styles
layerCache = this._textCache[layer];
if (layerCache == null) {
layerCache = this._textCache[layer] = {};
}
styleCache = layerCache[textStyle];
if (styleCache == null) {
styleCache = layerCache[textStyle] = {};
}
info = styleCache[text];
// If we can't find a matching element in our cache, create a new one
if (info == null) {
var element = $("<div></div>").html(text)
.css({
position: "absolute",
'max-width': width,
top: -9999
})
.appendTo(this.getTextLayer(layer));
if (typeof font === "object") {
element.css({
font: textStyle,
color: font.color
});
} else if (typeof font === "string") {
element.addClass(font);
}
info = styleCache[text] = {
width: element.outerWidth(true),
height: element.outerHeight(true),
element: element,
positions: []
};
element.detach();
}
return info;
};
// Adds a text string to the canvas text overlay.
//
// The text isn't drawn immediately; it is marked as rendering, which will
// result in its addition to the canvas on the next render pass.
//
// @param {string} layer A string of space-separated CSS classes uniquely
// identifying the layer containing this text.
// @param {number} x X coordinate at which to draw the text.
// @param {number} y Y coordinate at which to draw the text.
// @param {string} text Text string to draw.
// @param {(string|object)=} font Either a string of space-separated CSS
// classes or a font-spec object, defining the text's font and style.
// @param {number=} angle Angle at which to rotate the text, in degrees.
// Angle is currently unused, it will be implemented in the future.
// @param {number=} width Maximum width of the text before it wraps.
// @param {string=} halign Horizontal alignment of the text; either "left",
// "center" or "right".
// @param {string=} valign Vertical alignment of the text; either "top",
// "middle" or "bottom".
Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) {
var info = this.getTextInfo(layer, text, font, angle, width),
positions = info.positions;
// Tweak the div's position to match the text's alignment
if (halign == "center") {
x -= info.width / 2;
} else if (halign == "right") {
x -= info.width;
}
if (valign == "middle") {
y -= info.height / 2;
} else if (valign == "bottom") {
y -= info.height;
}
// Determine whether this text already exists at this position.
// If so, mark it for inclusion in the next render pass.
for (var i = 0, position; position = positions[i]; i++) {
if (position.x == x && position.y == y) {
position.active = true;
return;
}
}
// If the text doesn't exist at this position, create a new entry
// For the very first position we'll re-use the original element,
// while for subsequent ones we'll clone it.
position = {
active: true,
rendered: false,
element: positions.length ? info.element.clone() : info.element,
x: x,
y: y
}
positions.push(position);
// Move the element to its final position within the container
position.element.css({
top: Math.round(y),
left: Math.round(x),
'text-align': halign // In case the text wraps
});
};
// Removes one or more text strings from the canvas text overlay.
//
// If no parameters are given, all text within the layer is removed.
//
// Note that the text is not immediately removed; it is simply marked as
// inactive, which will result in its removal on the next render pass.
// This avoids the performance penalty for 'clear and redraw' behavior,
// where we potentially get rid of all text on a layer, but will likely
// add back most or all of it later, as when redrawing axes, for example.
//
// @param {string} layer A string of space-separated CSS classes uniquely
// identifying the layer containing this text.
// @param {number=} x X coordinate of the text.
// @param {number=} y Y coordinate of the text.
// @param {string=} text Text string to remove.
// @param {(string|object)=} font Either a string of space-separated CSS
// classes or a font-spec object, defining the text's font and style.
// @param {number=} angle Angle at which the text is rotated, in degrees.
// Angle is currently unused, it will be implemented in the future.
Canvas.prototype.removeText = function(layer, x, y, text, font, angle) {
if (text == null) {
var layerCache = this._textCache[layer];
if (layerCache != null) {
for (var styleKey in layerCache) {
if (hasOwnProperty.call(layerCache, styleKey)) {
var styleCache = layerCache[styleKey];
for (var key in styleCache) {
if (hasOwnProperty.call(styleCache, key)) {
var positions = styleCache[key].positions;
for (var i = 0, position; position = positions[i]; i++) {
position.active = false;
}
}
}
}
}
}
} else {
var positions = this.getTextInfo(layer, text, font, angle).positions;
for (var i = 0, position; position = positions[i]; i++) {
if (position.x == x && position.y == y) {
position.active = false;
}
}
}
};
///////////////////////////////////////////////////////////////////////////
// The top-level container for the entire plot.
function Plot(placeholder, data_, options_, plugins) {
// data is on the form:
// [ series1, series2 ... ]
......@@ -59,8 +515,7 @@ Licensed under the MIT license.
show: null, // null = auto-detect, true = always, false = never
position: "bottom", // or "top"
mode: null, // null or "time"
timezone: null, // "browser" for local to the client or timezone for timezone-js
font: null, // null (derived from CSS in placeholder) or object like { size: 11, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" }
font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" }
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
......@@ -75,14 +530,9 @@ Licensed under the MIT license.
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
minTickSize: null // number or [number, "unit"]
},
yaxis: {
autoscaleMargin: 0.02,
......@@ -117,7 +567,6 @@ Licensed under the MIT license.
fillColor: null,
align: "left", // "left", "right", or "center"
horizontal: false,
zero: true
},
shadowSize: 3,
highlightColor: null
......@@ -148,13 +597,12 @@ Licensed under the MIT license.
},
hooks: {}
},
canvas = null, // the canvas for the plot itself
surface = 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: [],
......@@ -175,7 +623,7 @@ Licensed under the MIT license.
plot.setupGrid = setupGrid;
plot.draw = draw;
plot.getPlaceholder = function() { return placeholder; };
plot.getCanvas = function() { return canvas; };
plot.getCanvas = function() { return surface.element; };
plot.getPlotOffset = function() { return plotOffset; };
plot.width = function () { return plotWidth; };
plot.height = function () { return plotHeight; };
......@@ -210,9 +658,10 @@ Licensed under the MIT license.
};
plot.shutdown = shutdown;
plot.resize = function () {
getCanvasDimensions();
resizeCanvas(canvas);
resizeCanvas(overlay);
var width = placeholder.width(),
height = placeholder.height();
surface.resize(width, height);
overlay.resize(width, height);
};
// public attributes
......@@ -235,40 +684,103 @@ Licensed under the MIT license.
}
function initPlugins() {
// References to key classes, allowing plugins to modify them
var classes = {
Canvas: Canvas
};
for (var i = 0; i < plugins.length; ++i) {
var p = plugins[i];
p.init(plot);
p.init(plot, classes);
if (p.options)
$.extend(true, options, p.options);
}
}
function parseOptions(opts) {
var i;
$.extend(true, options, opts);
// $.extend merges arrays, rather than replacing them. When less
// colors are provided than the size of the default palette, we
// end up with those colors plus the remaining defaults, which is
// not expected behavior; avoid it by replacing them here.
if (opts && opts.colors) {
options.colors = opts.colors;
}
if (options.xaxis.color == null)
options.xaxis.color = options.grid.color;
options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
if (options.yaxis.color == null)
options.yaxis.color = options.grid.color;
options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
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.xaxis.tickColor == null) // grid.tickColor for back-compatibility
options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color;
if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility
options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color;
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]);
// Fill in defaults for axis options, including any unspecified
// font-spec fields, if a font-spec was provided.
// If no x/y axis options were provided, create one of each anyway,
// since the rest of the code assumes that they exist.
var i, axisOptions, axisCount,
fontDefaults = {
style: placeholder.css("font-style"),
size: Math.round(0.8 * (+placeholder.css("font-size").replace("px", "") || 13)),
variant: placeholder.css("font-variant"),
weight: placeholder.css("font-weight"),
family: placeholder.css("font-family")
};
fontDefaults.lineHeight = fontDefaults.size * 1.15;
axisCount = options.xaxes.length || 1;
for (i = 0; i < axisCount; ++i) {
axisOptions = options.xaxes[i];
if (axisOptions && !axisOptions.tickColor) {
axisOptions.tickColor = axisOptions.color;
}
axisOptions = $.extend(true, {}, options.xaxis, axisOptions);
options.xaxes[i] = axisOptions;
if (axisOptions.font) {
axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
if (!axisOptions.font.color) {
axisOptions.font.color = axisOptions.color;
}
}
}
axisCount = options.yaxes.length || 1;
for (i = 0; i < axisCount; ++i) {
axisOptions = options.yaxes[i];
if (axisOptions && !axisOptions.tickColor) {
axisOptions.tickColor = axisOptions.color;
}
axisOptions = $.extend(true, {}, options.yaxis, axisOptions);
options.yaxes[i] = axisOptions;
if (axisOptions.font) {
axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
if (!axisOptions.font.color) {
axisOptions.font.color = axisOptions.color;
}
}
}
// backwards compatibility, to be removed in future
if (options.xaxis.noTicks && options.xaxis.ticks == null)
......@@ -618,11 +1130,15 @@ Licensed under the MIT license.
if (val != null) {
f = format[m];
// extract min/max info
if (f.x)
if (f.autoscale) {
if (f.x) {
updateAxis(s.xaxis, val, val);
if (f.y)
}
if (f.y) {
updateAxis(s.yaxis, val, val);
}
}
}
points[k + m] = null;
}
}
......@@ -658,7 +1174,7 @@ Licensed under the MIT license.
// second pass: find datamax/datamin for auto-scaling
for (i = 0; i < series.length; ++i) {
s = series[i];
points = s.datapoints.points,
points = s.datapoints.points;
ps = s.datapoints.pointsize;
format = s.datapoints.format;
......@@ -730,161 +1246,33 @@ Licensed under the MIT license.
});
}
//////////////////////////////////////////////////////////////////////////////////
// Returns the display's ratio between physical and device-independent pixels.
//
// This is the ratio between the width that the browser advertises and the number
// of pixels actually available in that space. The iPhone 4, for example, has a
// device-independent width of 320px, but its screen is actually 640px wide. It
// therefore has a pixel ratio of 2, while most normal devices have a ratio of 1.
function getPixelRatio(cctx) {
var devicePixelRatio = window.devicePixelRatio || 1;
var backingStoreRatio =
cctx.webkitBackingStorePixelRatio ||
cctx.mozBackingStorePixelRatio ||
cctx.msBackingStorePixelRatio ||
cctx.oBackingStorePixelRatio ||
cctx.backingStorePixelRatio || 1;
return devicePixelRatio / backingStoreRatio;
}
function makeCanvas(cls) {
var c = document.createElement('canvas');
c.className = cls;
$(c).css({ direction: "ltr", position: "absolute", left: 0, top: 0 })
.appendTo(placeholder);
// If HTML5 Canvas isn't available, fall back to Excanvas
if (!c.getContext) {
if (window.G_vmlCanvasManager) {
c = window.G_vmlCanvasManager.initElement(c);
} else {
throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.");
}
}
var cctx = c.getContext("2d");
// Increase the canvas density based on the display's pixel ratio; basically
// giving the canvas more pixels without increasing the size of its element,
// to take advantage of the fact that retina displays have that many more
// pixels than they actually use for page & element widths.
var pixelRatio = getPixelRatio(cctx);
c.width = canvasWidth * pixelRatio;
c.height = canvasHeight * pixelRatio;
c.style.width = canvasWidth + "px";
c.style.height = canvasHeight + "px";
// Save the context so we can reset in case we get replotted
cctx.save();
// Scale the coordinate space to match the display density; so even though we
// may have twice as many pixels, we still want lines and other drawing to
// appear at the same size; the extra pixels will just make them crisper.
cctx.scale(pixelRatio, pixelRatio);
return c;
}
function getCanvasDimensions() {
canvasWidth = placeholder.width();
canvasHeight = placeholder.height();
if (canvasWidth <= 0 || canvasHeight <= 0)
throw new Error("Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight);
}
function resizeCanvas(c) {
var cctx = c.getContext("2d");
// Handle pixel ratios > 1 for retina displays, as explained in makeCanvas
var pixelRatio = getPixelRatio(cctx);
// Resizing should reset the state (excanvas seems to be buggy though)
if (c.style.width != canvasWidth) {
c.width = canvasWidth * pixelRatio;
c.style.width = canvasWidth + "px";
}
if (c.style.height != canvasHeight) {
c.height = canvasHeight * pixelRatio;
c.style.height = canvasHeight + "px";
}
// so try to get back to the initial state (even if it's
// gone now, this should be safe according to the spec)
cctx.restore();
// and save again
cctx.save();
// Apply scaling for retina displays, as explained in makeCanvas
cctx.scale(pixelRatio, pixelRatio);
}
function setupCanvases() {
var reused,
existingCanvas = placeholder.children("canvas.flot-base"),
existingOverlay = placeholder.children("canvas.flot-overlay");
if (existingCanvas.length == 0 || existingOverlay == 0) {
// init everything
// Make sure the placeholder is clear of everything except canvases
// from a previous plot in this container that we'll try to re-use.
placeholder.html(""); // make sure placeholder is clear
placeholder.css({ padding: 0 }); // padding messes up the positioning
placeholder.css("padding", 0) // padding messes up the positioning
.children(":not(.flot-base,.flot-overlay)").remove();
if (placeholder.css("position") == 'static')
placeholder.css("position", "relative"); // for positioning labels and overlay
getCanvasDimensions();
surface = new Canvas("flot-base", placeholder);
overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features
canvas = makeCanvas("flot-base");
overlay = makeCanvas("flot-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");
ctx = surface.context;
octx = overlay.context;
// define which element we're listening for events on
eventHolder = $(overlay);
eventHolder = $(overlay.element).unbind();
if (reused) {
// run shutdown in the old plot object
placeholder.data("plot").shutdown();
// If we're re-using a plot object, shut down the old one
// reset reused canvases
plot.resize();
var existing = placeholder.data("plot");
// 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();
if (existing) {
existing.shutdown();
overlay.clear();
}
// save in case we get replotted
......@@ -956,56 +1344,31 @@ Licensed under the MIT license.
}
function measureTickLabels(axis) {
var opts = axis.options, ticks = axis.ticks || [],
axisw = opts.labelWidth || 0, axish = opts.labelHeight || 0,
f = axis.font;
ctx.save();
ctx.font = f.style + " " + f.variant + " " + f.weight + " " + f.size + "px '" + f.family + "'";
var opts = axis.options,
ticks = axis.ticks || [],
labelWidth = opts.labelWidth || 0,
labelHeight = opts.labelHeight || 0,
maxWidth = labelWidth || axis.direction == "x" ? Math.floor(surface.width / (ticks.length || 1)) : null;
legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
font = opts.font || "flot-tick-label tickLabel";
for (var i = 0; i < ticks.length; ++i) {
var t = ticks[i];
t.lines = [];
t.width = t.height = 0;
var t = ticks[i];
if (!t.label)
continue;
// accept various kinds of newlines, including HTML ones
// (you can actually split directly on regexps in Javascript,
// but IE < 9 is unfortunately broken)
var lines = (t.label + "").replace(/<br ?\/?>|\r\n|\r/g, "\n").split("\n");
for (var j = 0; j < lines.length; ++j) {
var line = { text: lines[j] },
m = ctx.measureText(line.text);
var info = surface.getTextInfo(layer, t.label, font, null, maxWidth);
line.width = m.width;
// m.height might not be defined, not in the
// standard yet
line.height = m.height != null ? m.height : f.size;
// add a bit of margin since font rendering is
// not pixel perfect and cut off letters look
// bad, this also doubles as spacing between
// lines
line.height += Math.round(f.size * 0.15);
t.width = Math.max(line.width, t.width);
t.height += line.height;
t.lines.push(line);
labelWidth = Math.max(labelWidth, info.width);
labelHeight = Math.max(labelHeight, info.height);
}
if (opts.labelWidth == null)
axisw = Math.max(axisw, t.width);
if (opts.labelHeight == null)
axish = Math.max(axish, t.height);
}
ctx.restore();
axis.labelWidth = Math.ceil(axisw);
axis.labelHeight = Math.ceil(axish);
axis.labelWidth = opts.labelWidth || labelWidth;
axis.labelHeight = opts.labelHeight || labelHeight;
}
function allocateAxisBoxFirstPhase(axis) {
......@@ -1053,7 +1416,7 @@ Licensed under the MIT license.
if (pos == "bottom") {
plotOffset.bottom += lh + axisMargin;
axis.box = { top: canvasHeight - plotOffset.bottom, height: lh };
axis.box = { top: surface.height - plotOffset.bottom, height: lh };
}
else {
axis.box = { top: plotOffset.top + axisMargin, height: lh };
......@@ -1069,7 +1432,7 @@ Licensed under the MIT license.
}
else {
plotOffset.right += lw + axisMargin;
axis.box = { left: canvasWidth - plotOffset.right, width: lw };
axis.box = { left: surface.width - plotOffset.right, width: lw };
}
}
......@@ -1085,11 +1448,11 @@ Licensed under the MIT license.
// dimension, we can set the remaining dimension coordinates
if (axis.direction == "x") {
axis.box.left = plotOffset.left - axis.labelWidth / 2;
axis.box.width = canvasWidth - plotOffset.left - plotOffset.right + axis.labelWidth;
axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth;
}
else {
axis.box.top = plotOffset.top - axis.labelHeight / 2;
axis.box.height = canvasHeight - plotOffset.bottom - plotOffset.top + axis.labelHeight;
axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight;
}
}
......@@ -1161,14 +1524,6 @@ Licensed under the MIT license.
});
if (showGrid) {
// determine from the placeholder the font size ~ height of font ~ 1 em
var fontDefaults = {
style: placeholder.css("font-style"),
size: Math.round(0.8 * (+placeholder.css("font-size").replace("px", "") || 13)),
variant: placeholder.css("font-variant"),
weight: placeholder.css("font-weight"),
family: placeholder.css("font-family")
};
var allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace; });
......@@ -1177,9 +1532,7 @@ Licensed under the MIT license.
setupTickGeneration(axis);
setTicks(axis);
snapRangeToTicks(axis, axis.ticks);
// find labelWidth/Height for axis
axis.font = $.extend({}, fontDefaults, axis.options.font);
measureTickLabels(axis);
});
......@@ -1198,14 +1551,18 @@ Licensed under the MIT license.
});
}
plotWidth = canvasWidth - plotOffset.left - plotOffset.right;
plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top;
plotWidth = surface.width - plotOffset.left - plotOffset.right;
plotHeight = surface.height - plotOffset.bottom - plotOffset.top;
// now we got the proper plot dimensions, we can compute the scaling
$.each(axes, function (_, axis) {
setTransformationHelpers(axis);
});
if (showGrid) {
drawAxisLabels();
}
insertLegend();
}
......@@ -1258,62 +1615,64 @@ Licensed under the MIT license.
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);
noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height);
axis.delta = (axis.max - axis.min) / noTicks;
var delta = (axis.max - axis.min) / noTicks,
dec = -Math.floor(Math.log(delta) / Math.LN10),
maxDec = opts.tickDecimals;
// Time mode was moved to a plug-in in 0.8, but since so many people use this
// we'll add an especially friendly make sure they remembered to include it.
if (opts.mode == "time" && !axis.tickGenerator) {
throw new Error("Time mode requires the flot.time plugin.");
}
// Flot supports base-10 axes; any other mode else is handled by a plug-in,
// like flot.time.js.
if (!axis.tickGenerator) {
axis.tickGenerator = function (axis) {
var maxDec = opts.tickDecimals,
dec = -Math.floor(Math.log(axis.delta) / Math.LN10);
if (maxDec != null && dec > maxDec)
if (maxDec != null && dec > maxDec) {
dec = maxDec;
}
var magn = Math.pow(10, -dec),
norm = axis.delta / magn, // norm is between 1.0 and 10.0
size,
ticks = [],
start,
i = 0,
v = Number.NaN,
prev;
norm = delta / magn, // norm is between 1.0 and 10.0
size;
if (norm < 1.5)
if (norm < 1.5) {
size = 1;
else if (norm < 3) {
} 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)
} else if (norm < 7.5) {
size = 5;
else size = 10;
} else {
size = 10;
}
size *= magn;
if (opts.minTickSize != null && size < opts.minTickSize)
if (opts.minTickSize != null && size < opts.minTickSize) {
size = opts.minTickSize;
}
axis.delta = delta;
axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
axis.tickSize = opts.tickSize || size;
start = floorInBase(axis.min, axis.tickSize)
// Time mode was moved to a plug-in in 0.8, but since so many people use this
// we'll add an especially friendly make sure they remembered to include it.
if (opts.mode == "time" && !axis.tickGenerator) {
throw new Error("Time mode requires the flot.time plugin.");
}
// Flot supports base-10 axes; any other mode else is handled by a plug-in,
// like flot.time.js.
if (!axis.tickGenerator) {
axis.tickGenerator = function (axis) {
var ticks = [],
start = floorInBase(axis.min, axis.tickSize),
i = 0,
v = Number.NaN,
prev;
do {
prev = v;
......@@ -1326,7 +1685,7 @@ Licensed under the MIT license.
axis.tickFormatter = function (value, axis) {
var factor = Math.pow(10, axis.tickDecimals);
var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1;
var formatted = "" + Math.round(value * factor) / factor;
// If tickDecimals was specified, ensure that we have exactly that
......@@ -1429,7 +1788,8 @@ Licensed under the MIT license.
}
function draw() {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
surface.clear();
executeHooks(hooks.drawBackground, [ctx]);
......@@ -1441,7 +1801,6 @@ Licensed under the MIT license.
if (grid.show && !grid.aboveData) {
drawGrid();
drawAxisLabels();
}
for (var i = 0; i < series.length; ++i) {
......@@ -1453,8 +1812,14 @@ Licensed under the MIT license.
if (grid.show && grid.aboveData) {
drawGrid();
drawAxisLabels();
}
surface.render();
// A draw implies that either the axes or data have changed, so we
// should probably update the overlay highlights as well.
triggerRedrawOverlay();
}
function extractRange(ranges, coord) {
......@@ -1584,7 +1949,6 @@ Licensed under the MIT license.
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
......@@ -1605,16 +1969,20 @@ Licensed under the MIT license.
// draw tick bar
if (!axis.innermost) {
ctx.strokeStyle = axis.options.color;
ctx.beginPath();
xoff = yoff = 0;
if (axis.direction == "x")
xoff = plotWidth;
xoff = plotWidth + 1;
else
yoff = plotHeight;
yoff = plotHeight + 1;
if (ctx.lineWidth == 1) {
x = Math.floor(x) + 0.5;
if (axis.direction == "x") {
y = Math.floor(y) + 0.5;
} else {
x = Math.floor(x) + 0.5;
}
}
ctx.moveTo(x, y);
......@@ -1623,13 +1991,16 @@ Licensed under the MIT license.
}
// draw ticks
ctx.strokeStyle = axis.options.tickColor;
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
if (isNaN(v) || v < axis.min || v > axis.max
// skip those lying on the axes if we got a border
|| (t == "full"
&& ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0)
......@@ -1726,74 +2097,48 @@ Licensed under the MIT license.
}
function drawAxisLabels() {
ctx.save();
$.each(allAxes(), function (_, axis) {
if (!axis.show || axis.ticks.length == 0)
return;
var box = axis.box, f = axis.font;
// placeholder.append('<div style="position:absolute;opacity:0.10;background-color:red;left:' + box.left + 'px;top:' + box.top + 'px;width:' + box.width + 'px;height:' + box.height + 'px"></div>') // debug
ctx.fillStyle = axis.options.color;
// Important: Don't use quotes around axis.font.family! Just around single
// font names like 'Times New Roman' that have a space or special character in it.
ctx.font = f.style + " " + f.variant + " " + f.weight + " " + f.size + "px " + f.family;
ctx.textAlign = "start";
// middle align the labels - top would be more
// natural, but browsers can differ a pixel or two in
// where they consider the top to be, so instead we
// middle align to minimize variation between browsers
// and compensate when calculating the coordinates
ctx.textBaseline = "middle";
var box = axis.box,
legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
font = axis.options.font || "flot-tick-label tickLabel",
tick, x, y, halign, valign;
surface.removeText(layer);
for (var i = 0; i < axis.ticks.length; ++i) {
var tick = axis.ticks[i];
tick = axis.ticks[i];
if (!tick.label || tick.v < axis.min || tick.v > axis.max)
continue;
var x, y, offset = 0, line;
for (var k = 0; k < tick.lines.length; ++k) {
line = tick.lines[k];
if (axis.direction == "x") {
x = plotOffset.left + axis.p2c(tick.v) - line.width/2;
if (axis.position == "bottom")
halign = "center";
x = plotOffset.left + axis.p2c(tick.v);
if (axis.position == "bottom") {
y = box.top + box.padding;
else
y = box.top + box.height - box.padding - tick.height;
} else {
y = box.top + box.height - box.padding;
valign = "bottom";
}
else {
y = plotOffset.top + axis.p2c(tick.v) - tick.height/2;
if (axis.position == "left")
x = box.left + box.width - box.padding - line.width;
else
} else {
valign = "middle";
y = plotOffset.top + axis.p2c(tick.v);
if (axis.position == "left") {
x = box.left + box.width - box.padding;
halign = "right";
} else {
x = box.left + box.padding;
}
// account for middle aligning and line number
y += line.height/2 + offset;
offset += line.height;
if (!!(window.opera && window.opera.version().split('.')[0] < 12)) {
// FIXME: LEGACY BROWSER FIX
// AFFECTS: Opera < 12.00
// round the coordinates since Opera
// otherwise switches to more ugly
// rendering (probably non-hinted) and
// offset the y coordinates since it seems
// to be off pretty consistently compared
// to the other browsers
x = Math.floor(x);
y = Math.ceil(y - 2);
}
ctx.fillText(line.text, x, y);
}
surface.addText(layer, x, y, tick.label, font, null, null, halign, valign);
}
});
ctx.restore();
}
function drawSeries(series) {
......@@ -2092,6 +2437,15 @@ Licensed under the MIT license.
sw = series.shadowSize,
radius = series.points.radius,
symbol = series.points.symbol;
// If the user sets the line width to 0, we change it to a very
// small value. A line width of 0 seems to force the default of 1.
// Doing the conditional here allows the shadow setting to still be
// optional even with a lineWidth of 0.
if( lw == 0 )
lw = 0.0001;
if (lw > 0 && sw > 0) {
// draw shadow in two steps
var w = sw / 2;
......@@ -2306,6 +2660,8 @@ Licensed under the MIT license.
if (options.legend.sorted) {
if ($.isFunction(options.legend.sorted)) {
entries.sort(options.legend.sorted);
} else if (options.legend.sorted == "reverse") {
entries.reverse();
} else {
var ascending = options.legend.sorted != "descending";
entries.sort(function(a, b) {
......@@ -2543,7 +2899,7 @@ Licensed under the MIT license.
// draw highlights
octx.save();
octx.clearRect(0, 0, canvasWidth, canvasHeight);
overlay.clear();
octx.translate(plotOffset.left, plotOffset.top);
var i, hi;
......@@ -2583,13 +2939,16 @@ Licensed under the MIT license.
if (s == null && point == null) {
highlights = [];
triggerRedrawOverlay();
return;
}
if (typeof s == "number")
s = series[s];
if (typeof point == "number")
point = s.data[point];
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) {
......@@ -2672,6 +3031,8 @@ Licensed under the MIT license.
}
}
// Add the plot function to the top level of the jQuery object
$.plot = function(placeholder, data, options) {
//var t0 = new Date();
var plot = new Plot($(placeholder), data, options, $.plot.plugins);
......@@ -2679,10 +3040,18 @@ Licensed under the MIT license.
return plot;
};
$.plot.version = "0.8-alpha";
$.plot.version = "0.8.1";
$.plot.plugins = [];
// Also add the plot function as a chainable property
$.fn.plot = function(data, options) {
return this.each(function() {
$.plot(this, data, options);
});
};
// round to nearby lower multiple of base
function floorInBase(n, base) {
return base * Math.floor(n / base);
......
/* Flot plugin for rendering pie charts.
Copyright (c) 2007-2012 IOLA and Ole Laursen.
Copyright (c) 2007-2013 IOLA and Ole Laursen.
Licensed under the MIT license.
The plugin assumes that each series has a single data value, and that each
......@@ -57,22 +57,22 @@ More detail and specific examples can be found in the included HTML file.
(function($) {
// Maximum redraw attempts when fitting labels within the plot
var REDRAW_ATTEMPTS = 10;
// Factor by which to shrink the pie when fitting labels within the plot
var REDRAW_SHRINK = 0.95;
function init(plot) {
var canvas = null,
canvasWidth = 0,
canvasHeight = 0,
target = null,
maxRadius = null,
centerLeft = null,
centerTop = null,
total = 0,
redraw = true,
redrawAttempts = 10,
shrink = 0.95,
legendWidth = 0,
processed = false,
raw = false,
ctx = null;
// interactive variables
......@@ -81,16 +81,9 @@ More detail and specific examples can be found in the included HTML file.
// add hook to determine if pie plugin in enabled, and then perform necessary operations
plot.hooks.processOptions.push(checkPieEnabled);
plot.hooks.bindEvents.push(bindEvents);
// check to see if the pie plugin is enabled
function checkPieEnabled(plot, options) {
plot.hooks.processOptions.push(function(plot, options) {
if (options.series.pie.show) {
//disable grid
options.grid.show = false;
// set labels.show
......@@ -120,21 +113,10 @@ More detail and specific examples can be found in the included HTML file.
} else if (options.series.pie.tilt < 0) {
options.series.pie.tilt = 0;
}
// add processData hook to do transformations on the data
plot.hooks.processDatapoints.push(processDatapoints);
plot.hooks.drawOverlay.push(drawOverlay);
// draw hook
plot.hooks.draw.push(draw);
}
}
});
// bind hoverable events
function bindEvents(plot, eventHolder) {
plot.hooks.bindEvents.push(function(plot, eventHolder) {
var options = plot.getOptions();
if (options.series.pie.show) {
if (options.grid.hoverable) {
......@@ -144,47 +126,30 @@ More detail and specific examples can be found in the included HTML file.
eventHolder.unbind("click").click(onClick);
}
}
}
// debugging function that prints out an object
function alertObject(obj) {
var msg = "";
function traverse(obj, depth) {
if (!depth) {
depth = 0;
}
});
for (var i = 0; i < obj.length; ++i) {
for (var j = 0; j < depth; j++) {
msg += "\t";
}
if( typeof obj[i] == "object") {
msg += "" + i + ":\n";
traverse(obj[i], depth + 1);
} else {
msg += "" + i + ": " + obj[i] + "\n";
}
}
plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) {
var options = plot.getOptions();
if (options.series.pie.show) {
processDatapoints(plot, series, data, datapoints);
}
});
traverse(obj);
alert(msg);
plot.hooks.drawOverlay.push(function(plot, octx) {
var options = plot.getOptions();
if (options.series.pie.show) {
drawOverlay(plot, octx);
}
});
function calcTotal(data) {
for (var i = 0; i < data.length; ++i) {
var item = parseFloat(data[i].data[0][1]);
if (item) {
total += item;
}
}
plot.hooks.draw.push(function(plot, newCtx) {
var options = plot.getOptions();
if (options.series.pie.show) {
draw(plot, newCtx);
}
});
function processDatapoints(plot, series, data, datapoints) {
function processDatapoints(plot, series, datapoints) {
if (!processed) {
processed = true;
canvas = plot.getCanvas();
......@@ -194,85 +159,81 @@ More detail and specific examples can be found in the included HTML file.
}
}
function setupPie() {
function combine(data) {
legendWidth = target.children().filter(".legend").children().width() || 0;
var total = 0,
combined = 0,
numCombined = 0,
color = options.series.pie.combine.color,
newdata = [];
// calculate maximum radius and center point
// Fix up the raw data from Flot, ensuring the data is numeric
maxRadius = Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2;
centerTop = canvasHeight / 2 + options.series.pie.offset.top;
centerLeft = canvasWidth / 2;
for (var i = 0; i < data.length; ++i) {
if (options.series.pie.offset.left == "auto") {
if (options.legend.position.match("w")) {
centerLeft += legendWidth / 2;
var value = data[i].data;
// If the data is an array, we'll assume that it's a standard
// Flot x-y pair, and are concerned only with the second value.
// Note how we use the original array, rather than creating a
// new one; this is more efficient and preserves any extra data
// that the user may have stored in higher indexes.
if ($.isArray(value) && value.length == 1) {
value = value[0];
}
if ($.isArray(value)) {
// Equivalent to $.isNumeric() but compatible with jQuery < 1.7
if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) {
value[1] = +value[1];
} else {
centerLeft -= legendWidth / 2;
value[1] = 0;
}
} else if (!isNaN(parseFloat(value)) && isFinite(value)) {
value = [1, +value];
} else {
centerLeft += options.series.pie.offset.left;
value = [1, 0];
}
if (centerLeft < maxRadius) {
centerLeft = maxRadius;
} else if (centerLeft > canvasWidth - maxRadius) {
centerLeft = canvasWidth - maxRadius;
}
data[i].data = [value];
}
function fixData(data) {
// Sum up all the slices, so we can calculate percentages for each
for (var i = 0; i < data.length; ++i) {
if (typeof(data[i].data) == "number") {
data[i].data = [[1, data[i].data]];
} else if (typeof(data[i].data) == "undefined" || typeof(data[i].data[0]) == "undefined") {
if (typeof(data[i].data) != "undefined" && typeof(data[i].data.label) != "undefined") {
data[i].label = data[i].data.label; // fix weirdness coming from flot
}
data[i].data = [[1, 0]];
}
}
return data;
total += data[i].data[0][1];
}
function combine(data) {
data = fixData(data);
calcTotal(data);
var combined = 0;
var numCombined = 0;
var color = options.series.pie.combine.color;
var newdata = [];
// Count the number of slices with percentages below the combine
// threshold; if it turns out to be just one, we won't combine.
for (var i = 0; i < data.length; ++i) {
// make sure its a number
data[i].data[0][1] = parseFloat(data[i].data[0][1]);
if (!data[i].data[0][1]) {
data[i].data[0][1] = 0;
}
if (data[i].data[0][1] / total <= options.series.pie.combine.threshold) {
combined += data[i].data[0][1];
var value = data[i].data[0][1];
if (value / total <= options.series.pie.combine.threshold) {
combined += value;
numCombined++;
if (!color) {
color = data[i].color;
}
} else {
}
}
for (var i = 0; i < data.length; ++i) {
var value = data[i].data[0][1];
if (numCombined < 2 || value / total > options.series.pie.combine.threshold) {
newdata.push({
data: [[1, data[i].data[0][1]]],
data: [[1, value]],
color: data[i].color,
label: data[i].label,
angle: data[i].data[0][1] * Math.PI * 2 / total,
percent: data[i].data[0][1] / (total / 100)
angle: value * Math.PI * 2 / total,
percent: value / (total / 100)
});
}
}
if (numCombined > 0) {
if (numCombined > 1) {
newdata.push({
data: [[1, combined]],
color: color,
......@@ -291,39 +252,79 @@ More detail and specific examples can be found in the included HTML file.
return; // if no series were passed
}
canvasWidth = plot.getPlaceholder().width();
canvasHeight = plot.getPlaceholder().height();
var canvasWidth = plot.getPlaceholder().width(),
canvasHeight = plot.getPlaceholder().height(),
legendWidth = target.children().filter(".legend").children().width() || 0;
ctx = newCtx;
setupPie();
var slices = plot.getData();
var attempts = 0;
// WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE!
// When combining smaller slices into an 'other' slice, we need to
// add a new series. Since Flot gives plugins no way to modify the
// list of series, the pie plugin uses a hack where the first call
// to processDatapoints results in a call to setData with the new
// list of series, then subsequent processDatapoints do nothing.
// The plugin-global 'processed' flag is used to control this hack;
// it starts out false, and is set to true after the first call to
// processDatapoints.
while (redraw && attempts<redrawAttempts) {
redraw = false;
// Unfortunately this turns future setData calls into no-ops; they
// call processDatapoints, the flag is true, and nothing happens.
// To fix this we'll set the flag back to false here in draw, when
// all series have been processed, so the next sequence of calls to
// processDatapoints once again starts out with a slice-combine.
// This is really a hack; in 0.9 we need to give plugins a proper
// way to modify series before any processing begins.
processed = false;
// calculate maximum radius and center point
maxRadius = Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2;
centerTop = canvasHeight / 2 + options.series.pie.offset.top;
centerLeft = canvasWidth / 2;
if (options.series.pie.offset.left == "auto") {
if (options.legend.position.match("w")) {
centerLeft += legendWidth / 2;
} else {
centerLeft -= legendWidth / 2;
}
} else {
centerLeft += options.series.pie.offset.left;
}
if (centerLeft < maxRadius) {
centerLeft = maxRadius;
} else if (centerLeft > canvasWidth - maxRadius) {
centerLeft = canvasWidth - maxRadius;
}
var slices = plot.getData(),
attempts = 0;
// Keep shrinking the pie's radius until drawPie returns true,
// indicating that all the labels fit, or we try too many times.
do {
if (attempts > 0) {
maxRadius *= shrink;
maxRadius *= REDRAW_SHRINK;
}
attempts += 1;
clear();
if (options.series.pie.tilt <= 0.8) {
drawShadow();
}
drawPie();
}
} while (!drawPie() && attempts < REDRAW_ATTEMPTS)
if (attempts >= redrawAttempts) {
if (attempts >= REDRAW_ATTEMPTS) {
clear();
target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>");
}
// Reset the redraw flag on success, so the loop above can run
// again in the event of a resize or other update.
// TODO: We should remove this redraw system entirely!
redraw = true;
if (plot.setSeries && plot.insertLegend) {
plot.setSeries(slices);
plot.insertLegend();
......@@ -408,14 +409,13 @@ More detail and specific examples can be found in the included HTML file.
drawDonutHole(ctx);
// draw labels
ctx.restore();
if (options.series.pie.label.show) {
drawLabels();
}
// Draw the labels, returning true if they fit within the plot
// restore to original state
ctx.restore();
if (options.series.pie.label.show) {
return drawLabels();
} else return true;
function drawSlice(angle, color, fill) {
......@@ -456,14 +456,19 @@ More detail and specific examples can be found in the included HTML file.
for (var i = 0; i < slices.length; ++i) {
if (slices[i].percent >= options.series.pie.label.threshold * 100) {
drawLabel(slices[i], currentAngle, i);
if (!drawLabel(slices[i], currentAngle, i)) {
return false;
}
}
currentAngle += slices[i].angle;
}
return true;
function drawLabel(slice, startAngle, index) {
if (slice.data[0][1] == 0) {
return;
return true;
}
// format label text
......@@ -497,7 +502,7 @@ More detail and specific examples can be found in the included HTML file.
// check to make sure that the label is not outside the canvas
if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) {
redraw = true;
return false;
}
if (options.series.pie.label.background.opacity != 0) {
......@@ -515,6 +520,8 @@ More detail and specific examples can be found in the included HTML file.
.css("opacity", options.series.pie.label.background.opacity)
.insertBefore(label);
}
return true;
} // end individual label function
} // end drawLabels function
} // end drawPie function
......
/* Flot plugin for stacking data sets rather than overlyaing them.
Copyright (c) 2007-2012 IOLA and Ole Laursen.
Copyright (c) 2007-2013 IOLA and Ole Laursen.
Licensed under the MIT license.
The plugin assumes the data is sorted on x (or y if stacking horizontally).
......@@ -15,7 +15,7 @@ key (which can be any number or string or just "true"). To specify the default
stack, you can set the stack option like this:
series: {
stack: null or true or key (number/string)
stack: null/false, true, or a key (number/string)
}
You can also specify it for a single series, like this:
......@@ -55,7 +55,7 @@ charts or filled areas).
}
function stackData(plot, s, datapoints) {
if (s.stack == null)
if (s.stack == null || s.stack === false)
return;
var other = findMatchingSeries(s, plot.getData());
......@@ -186,130 +186,3 @@ charts or filled areas).
version: '1.2'
});
})(jQuery);
(function ($) {
var options = {
series: {
stackpercent: null
} // or number/string
};
function init(plot) {
// will be built up dynamically as a hash from x-value, or y-value if horizontal
var stackBases = {};
var processed = false;
var stackSums = {};
//set percentage for stacked chart
function processRawData(plot, series, data, datapoints) {
if (!processed) {
processed = true;
stackSums = getStackSums(plot.getData());
}
if (series.stackpercent == true) {
var num = data.length;
series.percents = [];
var key_idx = 0;
var value_idx = 1;
if (series.bars && series.bars.horizontal && series.bars.horizontal === true) {
key_idx = 1;
value_idx = 0;
}
for (var j = 0; j < num; j++) {
var sum = stackSums[data[j][key_idx] + ""];
if (sum > 0) {
series.percents.push(data[j][value_idx] * 100 / sum);
} else {
series.percents.push(0);
}
}
}
}
//calculate summary
function getStackSums(_data) {
var data_len = _data.length;
var sums = {};
if (data_len > 0) {
//caculate summary
for (var i = 0; i < data_len; i++) {
if (_data[i].stackpercent) {
var key_idx = 0;
var value_idx = 1;
if (_data[i].bars && _data[i].bars.horizontal && _data[i].bars.horizontal === true) {
key_idx = 1;
value_idx = 0;
}
var num = _data[i].data.length;
for (var j = 0; j < num; j++) {
var value = 0;
if (_data[i].data[j][1] != null) {
value = _data[i].data[j][value_idx];
}
if (sums[_data[i].data[j][key_idx] + ""]) {
sums[_data[i].data[j][key_idx] + ""] += value;
} else {
sums[_data[i].data[j][key_idx] + ""] = value;
}
}
}
}
}
return sums;
}
function stackData(plot, s, datapoints) {
if (!s.stackpercent) return;
if (!processed) {
stackSums = getStackSums(plot.getData());
}
var newPoints = [];
var key_idx = 0;
var value_idx = 1;
if (s.bars && s.bars.horizontal && s.bars.horizontal === true) {
key_idx = 1;
value_idx = 0;
}
for (var i = 0; i < datapoints.points.length; i += 3) {
// note that the values need to be turned into absolute y-values.
// in other words, if you were to stack (x, y1), (x, y2), and (x, y3),
// (each from different series, which is where stackBases comes in),
// you'd want the new points to be (x, y1, 0), (x, y1+y2, y1), (x, y1+y2+y3, y1+y2)
// generally, (x, thisValue + (base up to this point), + (base up to this point))
if (!stackBases[datapoints.points[i + key_idx]]) {
stackBases[datapoints.points[i + key_idx]] = 0;
}
newPoints[i + key_idx] = datapoints.points[i + key_idx];
newPoints[i + value_idx] = datapoints.points[i + value_idx] + stackBases[datapoints.points[i + key_idx]];
newPoints[i + 2] = stackBases[datapoints.points[i + key_idx]];
stackBases[datapoints.points[i + key_idx]] += datapoints.points[i + value_idx];
// change points to percentage values
// you may need to set yaxis:{ max = 100 }
if ( stackSums[newPoints[i+key_idx]+""] > 0 ){
newPoints[i + value_idx] = newPoints[i + value_idx] * 100 / stackSums[newPoints[i + key_idx] + ""];
newPoints[i + 2] = newPoints[i + 2] * 100 / stackSums[newPoints[i + key_idx] + ""];
} else {
newPoints[i + value_idx] = 0;
newPoints[i + 2] = 0;
}
}
datapoints.points = newPoints;
}
plot.hooks.processRawData.push(processRawData);
plot.hooks.processDatapoints.push(stackData);
}
$.plot.plugins.push({
init: init,
options: options,
name: 'stackpercent',
version: '0.1'
});
})(jQuery);
/* Pretty handling of time axes.
Copyright (c) 2007-2012 IOLA and Ole Laursen.
Copyright (c) 2007-2013 IOLA and Ole Laursen.
Licensed under the MIT license.
Set axis.mode to "time" to enable. See the section "Time series data" in
......@@ -10,7 +10,14 @@ API.txt for details.
(function($) {
var options = {};
var options = {
xaxis: {
timezone: null, // "browser" for local to the client or timezone for timezone-js
timeformat: null, // format string to use
twelveHourClock: false, // 12 or 24 time in time mode
monthNames: null // list of names of months
}
};
// round to nearby lower multiple of base
......@@ -66,6 +73,7 @@ API.txt for details.
case 'b': c = "" + monthNames[d.getMonth()]; break;
case 'd': c = leftPad(d.getDate()); break;
case 'e': c = leftPad(d.getDate(), " "); break;
case 'h': // For back-compat with 0.7; remove in 1.0
case 'H': c = leftPad(hours); break;
case 'I': c = leftPad(hours12); break;
case 'l': c = leftPad(hours12, " "); break;
......@@ -187,7 +195,7 @@ API.txt for details.
[1, "year"]]);
function init(plot) {
plot.hooks.processDatapoints.push(function (plot, series, datapoints) {
plot.hooks.processOptions.push(function (plot, options) {
$.each(plot.getAxes(), function(axisName, axis) {
var opts = axis.options;
......@@ -287,17 +295,23 @@ API.txt for details.
if (step >= timeUnitSize.minute) {
d.setSeconds(0);
} else if (step >= timeUnitSize.hour) {
}
if (step >= timeUnitSize.hour) {
d.setMinutes(0);
} else if (step >= timeUnitSize.day) {
}
if (step >= timeUnitSize.day) {
d.setHours(0);
} else if (step >= timeUnitSize.day * 4) {
}
if (step >= timeUnitSize.day * 4) {
d.setDate(1);
} else if (step >= timeUnitSize.month * 2) {
}
if (step >= timeUnitSize.month * 2) {
d.setMonth(floorInBase(d.getMonth(), 3));
} else if (step >= timeUnitSize.quarter * 2) {
}
if (step >= timeUnitSize.quarter * 2) {
d.setMonth(floorInBase(d.getMonth(), 6));
} else if (step >= timeUnitSize.year) {
}
if (step >= timeUnitSize.year) {
d.setMonth(0);
}
......
......@@ -16,7 +16,6 @@
<link rel="stylesheet" href="common/css/animate.min.css">
<link rel="stylesheet" href="common/css/bootstrap-responsive.min.css">
<link rel="stylesheet" href="common/css/font-awesome.min.css">
<link rel="stylesheet" href="common/css/main.css">
<link rel="stylesheet" href="common/css/timepicker.css">
<!-- project dependency libs -->
......@@ -26,13 +25,15 @@
<script src="config.js"></script>
<script src="js/app.js"></script>
<style>
</style>
</head>
<body ng-controller="DashCtrl" ng-cloak>
<div ng-repeat='alert in global_alert' class="alert alert-{{alert.severity}} span12" style="position: fixed;top:2px;opacity:0.9;z-index:8000">
<button type="button" class="close" ng-click="clear_alert(alert)">&times;</button>
<strong>{{alert.title}}</strong> <span ng-bind-html-unsafe='alert.text'></span> <div class='pull-right small'> {{$index + 1}} alert(s) </div>
<div ng-repeat='alert in global_alert' class="alert-{{alert.severity}} dashboard-notice" ng-show="$last">
<button type="button" class="close" ng-click="clear_alert(alert)" style="padding-right:50px">&times;</button>
<strong>{{alert.title}}</strong> <span ng-bind-html-unsafe='alert.text'></span> <div style="padding-right:10px" class='pull-right small'> {{$index + 1}} alert(s) </div>
</div>
<div class="navbar navbar-static-top">
<div class="navbar-inner">
......
......@@ -7,11 +7,15 @@ angular.module('kibana.directives', [])
return {
restrict: 'E',
link: function(scope, elem, attrs) {
var template = '<img src="common/img/load.gif" class="panel-loading" ng-show="panelMeta.loading == true">'+
var template = '<i class="icon-spinner small icon-spin icon-large panel-loading" '+
'ng-show="panelMeta.loading == true && !panel.title"></i>'+
' <span class="editlink panelextra pointer" style="right:15px;top:0px" ' +
'bs-modal="\'partials/paneleditor.html\'" ng-show="panel.editable != false">'+
'<span class="small">{{panel.type}}</span> <i class="icon-cog pointer"></i> '+
'</span><h4>{{panel.title}}</h4>';
'</span><h4>'+
'{{panel.title}} '+
'<i class="icon-spinner smaller icon-spin icon-large" ng-show="panelMeta.loading == true && panel.title"></i>'+
'</h4>';
elem.prepend($compile(angular.element(template))(scope));
}
};
......
......@@ -8,7 +8,8 @@
"devDependencies": {
"grunt": "~0.4.0",
"grunt-contrib": "~0.7.0",
"grunt-contrib-jshint": "~0.6.0"
"grunt-contrib-jshint": "~0.6.0",
"assemble-less": "~0.4.8"
},
"license": "Apache License"
}
......@@ -165,6 +165,7 @@ angular.module('kibana.histogram', [])
data = [];
if(filterSrv.idsByType('time').length > 0) {
data = [[_range.from.getTime(), null],[_range.to.getTime(), null]];
//data = [];
}
hits = 0;
} else {
......@@ -313,7 +314,7 @@ angular.module('kibana.histogram', [])
lineWidth: scope.panel.linewidth,
steps: false
},
bars: { show: scope.panel.bars, fill: 1, barWidth: barwidth/1.8 },
bars: { show: scope.panel.bars, fill: 1, barWidth: barwidth/1.8, zero: false },
points: { show: scope.panel.points, fill: 1, fillColor: false, radius: 5},
shadowSize: 1
},
......@@ -321,22 +322,21 @@ angular.module('kibana.histogram', [])
show: scope.panel['y-axis'],
min: 0,
max: scope.panel.percentage && scope.panel.stack ? 100 : null,
color: "#c8c8c8"
},
xaxis: {
timezone: scope.panel.timezone,
show: scope.panel['x-axis'],
mode: "time",
min: scope.range.from.getTime(),
max: scope.range.to.getTime(),
timeformat: time_format(scope.panel.interval),
label: "Datetime",
color: "#c8c8c8",
},
grid: {
backgroundColor: null,
borderWidth: 0,
borderColor: '#eee',
color: "#eee",
hoverable: true,
color: '#c8c8c8'
}
};
......
......@@ -8,7 +8,7 @@
</div>
<div class="span8">
<h6>Columns <small>Click to remove</small></h6>
<span style="margin-left:3px" ng-click="toggle_field(field)" ng-repeat="field in $parent.panel.fields" class="label remove pointer">{{field}} </span>
<span style="margin-left:3px" ng-click="toggle_field(field)" ng-repeat="field in $parent.panel.fields" class="label pointer remove">{{field}} </span>
</div>
</div>
<div class="row-fluid">
......
......@@ -7,7 +7,7 @@
<div>
<h5>Last Elasticsearch Query</h5>
<pre>curl -XGET '{{config.elasticsearch}}/{{dashboard.indices|stringify}}/_search?pretty' -d '{{inspector}}'
</pre>"
</pre>
</div>
</div>
......
......@@ -190,3 +190,16 @@
.faded {
opacity: 0.2;
}
div.flot-text {
color: @textColor !important;
}
.dashboard-notice {
z-index:8000;
margin-left:0px;
padding:3px 0px 3px 0px;
width:100%;
color: @textColor;
padding-left:20px;
}
\ No newline at end of file
......@@ -32,7 +32,7 @@
// Scaffolding
// -------------------------
@bodyBackground: @white;
@bodyBackground: #ffffff;
@textColor: @grayDark;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment