diff --git a/chart_export.php b/chart_export.php
index 87ab3a4615..e3a02f0e0b 100644
--- a/chart_export.php
+++ b/chart_export.php
@@ -7,17 +7,17 @@
*/
-define('PMA_MINIMUM_COMMON',true);
+define('PMA_MINIMUM_COMMON', true);
require_once './libraries/common.inc.php';
if(isset($_REQUEST['filename']) && isset($_REQUEST['image'])) {
$allowed = Array( 'image/png'=>'png', 'image/svg+xml'=>'svg');
- if(!isset($allowed[$_REQUEST['type']])) exit('Invalid export type');
+ if(! isset($allowed[$_REQUEST['type']])) exit('Invalid export type');
- if(!preg_match("/(".implode("|",$allowed).")$/i",$_REQUEST['filename']))
- $_REQUEST['filename'].='.'.$allowed[$_REQUEST['type']];
+ if(! preg_match("/(".implode("|",$allowed).")$/i", $_REQUEST['filename']))
+ $_REQUEST['filename'] .= '.' . $allowed[$_REQUEST['type']];
header("Cache-Control: public");
header("Content-Description: File Transfer");
@@ -25,8 +25,8 @@ if(isset($_REQUEST['filename']) && isset($_REQUEST['image'])) {
header("Content-Type: ".$_REQUEST['type']);
header("Content-Transfer-Encoding: binary");
- if($allowed[$_REQUEST['type']]!='svg')
- echo base64_decode(substr($_REQUEST['image'],strpos($_REQUEST['image'],',')+1));
+ if($allowed[$_REQUEST['type']] != 'svg')
+ echo base64_decode(substr($_REQUEST['image'], strpos($_REQUEST['image'],',') + 1));
else
echo $_REQUEST['image'];
diff --git a/js/functions.js b/js/functions.js
index aa639e27de..2a147d862d 100644
--- a/js/functions.js
+++ b/js/functions.js
@@ -723,13 +723,13 @@ var last_shift_clicked_row = -1;
* Row highlighting in horizontal mode (use "live"
* so that it works also for pages reached via AJAX)
*/
-$(document).ready(function() {
+/*$(document).ready(function() {
$('tr.odd, tr.even').live('hover',function(event) {
var $tr = $(this);
$tr.toggleClass('hover',event.type=='mouseover');
$tr.children().toggleClass('hover',event.type=='mouseover');
});
-})
+})*/
/**
* This array is used to remember mark status of rows in browse mode
@@ -1461,20 +1461,26 @@ function PMA_createChart(passedSettings) {
chart: {
type: 'spline',
marginRight: 10,
+ backgroundColor: 'transparent',
events: {
+ /* Live charting support */
load: function() {
var thisChart = this;
var lastValue = null, curValue = null;
var numLoadedPoints = 0, otherSum = 0;
var diff;
- // No realtime updates for graphs that are being exported, and disabled when no callback is set
+
+ // No realtime updates for graphs that are being exported, and disabled when realtime is not set
+ // Also don't do live charting if we don't have the server time
if(thisChart.options.chart.forExport == true ||
- ! passedSettings.realtime ||
- ! passedSettings.realtime.callback) return;
+ ! thisChart.options.realtime ||
+ ! thisChart.options.realtime.callback ||
+ ! server_time_diff) return;
thisChart.options.realtime.timeoutCallBack = function() {
- $.post(passedSettings.realtime.url,
- { ajax_request: true, chart_data: 1, type: passedSettings.realtime.type },
+ thisChart.options.realtime.postRequest = $.post(
+ thisChart.options.realtime.url,
+ thisChart.options.realtime.postData,
function(data) {
curValue = jQuery.parseJSON(data);
@@ -1487,13 +1493,13 @@ function PMA_createChart(passedSettings) {
false
);
- passedSettings.realtime.callback(thisChart,curValue,lastValue,numLoadedPoints);
+ thisChart.options.realtime.callback(thisChart,curValue,lastValue,numLoadedPoints);
lastValue = curValue;
numLoadedPoints++;
// Timeout has been cleared => don't start a new timeout
- if(chart_activeTimeouts[container]==null) return;
+ if(chart_activeTimeouts[container] == null) return;
chart_activeTimeouts[container] = setTimeout(
thisChart.options.realtime.timeoutCallBack,
@@ -1502,7 +1508,7 @@ function PMA_createChart(passedSettings) {
});
}
- chart_activeTimeouts[container] = setTimeout(thisChart.options.realtime.timeoutCallBack, 0);
+ chart_activeTimeouts[container] = setTimeout(thisChart.options.realtime.timeoutCallBack, 5);
}
}
},
@@ -1544,15 +1550,20 @@ function PMA_createChart(passedSettings) {
}
/* Set/Get realtime chart default values */
- if(passedSettings.realtime) {
+ if(passedSettings.realtime) {
if(!passedSettings.realtime.refreshRate)
passedSettings.realtime.refreshRate = 5000;
if(!passedSettings.realtime.numMaxPoints)
passedSettings.realtime.numMaxPoints = 30;
- settings.xAxis.min = new Date().getTime() - passedSettings.realtime.numMaxPoints * passedSettings.realtime.refreshRate;
- settings.xAxis.max = new Date().getTime() + passedSettings.realtime.refreshRate / 4;
+ // Allow custom POST vars to be added
+ passedSettings.realtime.postData = $.extend(false,{ ajax_request: true, chart_data: 1, type: passedSettings.realtime.type },passedSettings.realtime.postData);
+
+ if(server_time_diff) {
+ settings.xAxis.min = new Date().getTime() - server_time_diff - passedSettings.realtime.numMaxPoints * passedSettings.realtime.refreshRate;
+ settings.xAxis.max = new Date().getTime() - server_time_diff + passedSettings.realtime.refreshRate;
+ }
}
// Overwrite/Merge default settings with passedsettings
@@ -2564,10 +2575,10 @@ $(document).ready(function() {
$('.vpointer').live('hover',
//handlerInOut
function(e) {
- var $this_td = $(this);
- var row_num = PMA_getRowNumber($this_td.attr('class'));
- // for all td of the same vertical row, toggle hover
- $('.vpointer').filter('.row_' + row_num).toggleClass('hover');
+ var $this_td = $(this);
+ var row_num = PMA_getRowNumber($this_td.attr('class'));
+ // for all td of the same vertical row, toggle hover
+ $('.vpointer').filter('.row_' + row_num).toggleClass('hover');
}
);
}) // end of $(document).ready() for vertical pointer
@@ -2577,11 +2588,35 @@ $(document).ready(function() {
* Vertical marker
*/
$('.vmarker').live('click', function(e) {
+ // do not trigger when clicked on anchor
+ if ($(e.target).is('a, img, a *')) {
+ return;
+ }
+
var $this_td = $(this);
var row_num = PMA_getRowNumber($this_td.attr('class'));
- // for all td of the same vertical row, toggle the marked class
- $('.vmarker').filter('.row_' + row_num).toggleClass('marked');
- });
+
+ // XXX: FF fires two click events for (label and checkbox), so we need to handle this differently
+ var $tr = $(this);
+ var $checkbox = $('.vmarker').filter('.row_' + row_num + ':first').find(':checkbox');
+ if ($checkbox.length) {
+ // checkbox in a row, add or remove class depending on checkbox state
+ var checked = $checkbox.attr('checked');
+ if (!$(e.target).is(':checkbox, label')) {
+ checked = !checked;
+ $checkbox.attr('checked', checked);
+ }
+ // for all td of the same vertical row, toggle the marked class
+ if (checked) {
+ $('.vmarker').filter('.row_' + row_num).addClass('marked');
+ } else {
+ $('.vmarker').filter('.row_' + row_num).removeClass('marked');
+ }
+ } else {
+ // normaln data table, just toggle class
+ $('.vmarker').filter('.row_' + row_num).toggleClass('marked');
+ }
+ });
/**
* Reveal visual builder anchor
diff --git a/js/highcharts/exporting.js b/js/highcharts/exporting.js
index 171ea503ca..cb04357b32 100644
--- a/js/highcharts/exporting.js
+++ b/js/highcharts/exporting.js
@@ -46,7 +46,7 @@ var HC = Highcharts,
downloadPDF: 'Download PDF document',
downloadSVG: 'Download SVG vector image',
exportButtonTitle: 'Export to raster or vector image',
- printButtonTitle: 'Print the chart'
+ printButton: 'Print the chart'
}
});
@@ -106,7 +106,7 @@ defaultOptions.exporting = {
type: 'image/png',
url: 'chart_export.php',
width: 800,
- buttons: {
+ buttons: {
exportButton: {
//enabled: true,
symbol: 'exportIcon',
@@ -114,54 +114,26 @@ defaultOptions.exporting = {
symbolFill: '#A8BF77',
hoverSymbolFill: '#768F3E',
_titleKey: 'exportButtonTitle',
+ menuName: 'export',
menuItems: [{
textKey: 'downloadPNG',
onclick: function() {
this.exportChart();
}
- },/* {
- textKey: 'downloadJPEG',
- onclick: function() {
- this.exportChart({
- type: 'image/jpeg'
- });
- }
- }, {
- textKey: 'downloadPDF',
- onclick: function() {
- this.exportChart({
- type: 'application/pdf'
- });
- }
- }, */{
+ },{
textKey: 'downloadSVG',
onclick: function() {
this.exportChart({
type: 'image/svg+xml'
});
}
- }/*, {
- text: 'View SVG',
+ },{
+ textKey: 'printButton',
onclick: function() {
- var svg = this.getSVG()
- .replace(//g, '>');
-
- doc.body.innerHTML = ''+ svg +' ';
- }
- }*/]
+ this.print();
+ }
+ }]
- },
- printButton: {
- //enabled: true,
- symbol: 'printIcon',
- x: -36,
- symbolFill: '#B5C9DF',
- hoverSymbolFill: '#779ABF',
- _titleKey: 'printButtonTitle',
- onclick: function() {
- this.print();
- }
}
}
};
@@ -522,8 +494,8 @@ extend(Chart.prototype, {
btnOptions = merge(chart.options.navigation.buttonOptions, options),
onclick = btnOptions.onclick,
menuItems = btnOptions.menuItems,
- /*position = chart.getAlignment(btnOptions),
- buttonLeft = position.x,
+ //position = chart.getAlignment(btnOptions),
+ /*buttonLeft = position.x,
buttonTop = position.y,*/
buttonWidth = btnOptions.width,
buttonHeight = btnOptions.height,
@@ -543,7 +515,7 @@ extend(Chart.prototype, {
if (btnOptions.enabled === false) {
return;
}
-
+
// element to capture the click
function revert() {
symbol.attr(symbolAttr);
@@ -603,7 +575,7 @@ extend(Chart.prototype, {
onclick = function(e) {
revert();
var bBox = button.getBBox();
- chart.contextMenu('export-menu', menuItems, bBox.x, bBox.y, buttonWidth, buttonHeight);
+ chart.contextMenu(btnOptions.menuName, menuItems, bBox.x, bBox.y, buttonWidth, buttonHeight);
};
}
/*addEvent(button.element, 'click', function() {
@@ -612,7 +584,7 @@ extend(Chart.prototype, {
button.on('click', function() {
onclick.apply(chart, arguments);
});
-
+
// the icon
symbol = renderer.symbol(
btnOptions.symbol,
@@ -653,33 +625,6 @@ HC.Renderer.prototype.symbols.exportIcon = function(x, y, radius) {
'Z'
];
};
-// Create the print icon
-HC.Renderer.prototype.symbols.printIcon = function(x, y, radius) {
- return [
- M, // the printer
- x - radius, y + radius * 0.5,
- L,
- x + radius, y + radius * 0.5,
- x + radius, y - radius / 3,
- x - radius, y - radius / 3,
- 'Z',
- M, // the upper sheet
- x - radius * 0.5, y - radius / 3,
- L,
- x - radius * 0.5, y - radius,
- x + radius * 0.5, y - radius,
- x + radius * 0.5, y - radius / 3,
- 'Z',
- M, // the lower sheet
- x - radius * 0.5, y + radius * 0.5,
- L,
- x - radius * 0.75, y + radius,
- x + radius * 0.75, y + radius,
- x + radius * 0.5, y + radius * 0.5,
- 'Z'
- ];
-};
-
// Add the buttons on chart load
Chart.prototype.callbacks.push(function(chart) {
@@ -692,6 +637,10 @@ Chart.prototype.callbacks.push(function(chart) {
for (n in buttons) {
chart.addButton(buttons[n]);
}
+ for (n in chart.options.buttons) {
+ chart.addButton(chart.options.buttons[n]);
+ }
+
}
});
diff --git a/js/highcharts/highcharts.js b/js/highcharts/highcharts.js
index 4ad0c6c4ae..b3b8e9d571 100644
--- a/js/highcharts/highcharts.js
+++ b/js/highcharts/highcharts.js
@@ -2,9 +2,9 @@
// @compilation_level SIMPLE_OPTIMIZATIONS
/**
- * @license Highcharts JS v2.1.4 (2011-03-02)
+ * @license Highcharts JS v2.1.5 (2011-06-22)
*
- * (c) 2009-2010 Torstein Hønsi
+ * (c) 2009-2011 Torstein Hønsi
*
* License: www.highcharts.com/license
*/
@@ -33,16 +33,17 @@ var doc = document,
// some variables
userAgent = navigator.userAgent,
isIE = /msie/i.test(userAgent) && !win.opera,
- docMode8 = doc.documentMode == 8,
+ docMode8 = doc.documentMode === 8,
isWebKit = /AppleWebKit/.test(userAgent),
isFirefox = /Firefox/.test(userAgent),
//hasSVG = win.SVGAngle || doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"),
hasSVG = !!doc.createElementNS && !!doc.createElementNS("http://www.w3.org/2000/svg", "svg").createSVGRect,
SVG_NS = 'http://www.w3.org/2000/svg',
- hasTouch = 'ontouchstart' in doc.documentElement,
+ Renderer,
+ hasTouch = doc.documentElement.ontouchstart !== undefined,
colorCounter,
symbolCounter,
- symbolSizes = {},
+ symbolSizes = { },
idCounter = 0,
timeFactor = 1, // 1 = JavaScript time, 1000 = Unix time
garbageBin,
@@ -122,10 +123,11 @@ var doc = document,
* @param {Object} b The object to add to the first one
*/
function extend(a, b) {
+ var n;
if (!a) {
a = {};
}
- for (var n in b) {
+ for (n in b) {
a[n] = b[n];
}
return a;
@@ -144,7 +146,7 @@ function pInt(s, mag) {
* @param {Object} s
*/
function isString(s) {
- return typeof s == 'string';
+ return typeof s === 'string';
}
/**
@@ -152,7 +154,7 @@ function isString(s) {
* @param {Object} obj
*/
function isObject(obj) {
- return typeof obj == 'object';
+ return typeof obj === 'object';
}
/**
@@ -160,7 +162,14 @@ function isObject(obj) {
* @param {Object} n
*/
function isNumber(n) {
- return typeof n == 'number';
+ return typeof n === 'number';
+}
+
+function log2lin(num) {
+ return math.log(num) / math.LN10;
+}
+function lin2log(num) {
+ return math.pow(10, num);
}
/**
@@ -171,7 +180,7 @@ function isNumber(n) {
function erase(arr, item) {
var i = arr.length;
while (i--) {
- if (arr[i] == item) {
+ if (arr[i] === item) {
arr.splice(i, 1);
break;
}
@@ -225,7 +234,7 @@ function attr(elem, prop, value) {
* MooTools' $.splat.
*/
function splat(obj) {
- if (!obj || obj.constructor != Array) {
+ if (!obj || obj.constructor !== Array) {
obj = [obj];
}
return obj;
@@ -257,15 +266,15 @@ function serializeCSS(style) {
key;
// serialize the declaration
for (key in style) {
- s += hyphenate(key) +':'+ style[key] + ';';
+ s += key +':'+ style[key] + ';';
}
return s;
}
/**
- * Set CSS on a give element
+ * Set CSS on a given element
* @param {Object} el
- * @param {Object} styles
+ * @param {Object} styles Style object with camel case property names
*/
function css (el, styles) {
if (isIE) {
@@ -276,6 +285,23 @@ function css (el, styles) {
extend(el.style, styles);
}
+/* *
+ * Get CSS value on a given element
+ * @param {Object} el DOM object
+ * @param {String} styleProp Camel cased CSS propery
+ * /
+function getStyle (el, styleProp) {
+ var ret,
+ CURRENT_STYLE = 'currentStyle',
+ GET_COMPUTED_STYLE = 'getComputedStyle';
+ if (el[CURRENT_STYLE]) {
+ ret = el[CURRENT_STYLE][styleProp];
+ } else if (win[GET_COMPUTED_STYLE]) {
+ ret = win[GET_COMPUTED_STYLE](el, null).getPropertyValue(hyphenate(styleProp));
+ }
+ return ret;
+}*/
+
/**
* Utility function to create element with attributes and styles
* @param {Object} tag
@@ -301,6 +327,141 @@ function createElement (tag, attribs, styles, parent, nopad) {
return el;
}
+/**
+ * Extend a prototyped class by new members
+ * @param {Object} parent
+ * @param {Object} members
+ */
+function extendClass(parent, members) {
+ var object = function(){};
+ object.prototype = new parent();
+ extend(object.prototype, members);
+ return object;
+}
+
+/**
+ * Format a number and return a string based on input settings
+ * @param {Number} number The input number to format
+ * @param {Number} decimals The amount of decimals
+ * @param {String} decPoint The decimal point, defaults to the one given in the lang options
+ * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options
+ */
+function numberFormat (number, decimals, decPoint, thousandsSep) {
+ var lang = defaultOptions.lang,
+ // http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/
+ n = number, c = isNaN(decimals = mathAbs(decimals)) ? 2 : decimals,
+ d = decPoint === undefined ? lang.decimalPoint : decPoint,
+ t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep, s = n < 0 ? "-" : "",
+ i = String(pInt(n = mathAbs(+n || 0).toFixed(c))),
+ j = i.length > 3 ? i.length % 3 : 0;
+
+ return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) +
+ (c ? d + mathAbs(n - i).toFixed(c).slice(2) : "");
+}
+
+/**
+ * Based on http://www.php.net/manual/en/function.strftime.php
+ * @param {String} format
+ * @param {Number} timestamp
+ * @param {Boolean} capitalize
+ */
+dateFormat = function (format, timestamp, capitalize) {
+ function pad (number) {
+ return number.toString().replace(/^([0-9])$/, '0$1');
+ }
+
+ if (!defined(timestamp) || isNaN(timestamp)) {
+ return 'Invalid date';
+ }
+ format = pick(format, '%Y-%m-%d %H:%M:%S');
+
+ var date = new Date(timestamp * timeFactor),
+ key, // used in for constuct below
+ // get the basic time values
+ hours = date[getHours](),
+ day = date[getDay](),
+ dayOfMonth = date[getDate](),
+ month = date[getMonth](),
+ fullYear = date[getFullYear](),
+ lang = defaultOptions.lang,
+ langWeekdays = lang.weekdays,
+ langMonths = lang.months,
+ /* // uncomment this and the 'W' format key below to enable week numbers
+ weekNumber = function() {
+ var clone = new Date(date.valueOf()),
+ day = clone[getDay]() == 0 ? 7 : clone[getDay](),
+ dayNumber;
+ clone.setDate(clone[getDate]() + 4 - day);
+ dayNumber = mathFloor((clone.getTime() - new Date(clone[getFullYear](), 0, 1, -6)) / 86400000);
+ return 1 + mathFloor(dayNumber / 7);
+ },
+ */
+
+ // list all format keys
+ replacements = {
+
+ // Day
+ 'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon'
+ 'A': langWeekdays[day], // Long weekday, like 'Monday'
+ 'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31
+ 'e': dayOfMonth, // Day of the month, 1 through 31
+
+ // Week (none implemented)
+ //'W': weekNumber(),
+
+ // Month
+ 'b': langMonths[month].substr(0, 3), // Short month, like 'Jan'
+ 'B': langMonths[month], // Long month, like 'January'
+ 'm': pad(month + 1), // Two digit month number, 01 through 12
+
+ // Year
+ 'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009
+ 'Y': fullYear, // Four digits year, like 2009
+
+ // Time
+ 'H': pad(hours), // Two digits hours in 24h format, 00 through 23
+ 'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11
+ 'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12
+ 'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59
+ 'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM
+ 'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM
+ 'S': pad(date.getSeconds()) // Two digits seconds, 00 through 59
+
+ };
+
+
+ // do the replaces
+ for (key in replacements) {
+ format = format.replace('%'+ key, replacements[key]);
+ }
+
+ // Optionally capitalize the string and return
+ return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format;
+};
+
+/**
+ * Loop up the node tree and add offsetWidth and offsetHeight to get the
+ * total page offset for a given element. Used by Opera and iOS on hover and
+ * all browsers on point click.
+ *
+ * @param {Object} el
+ *
+ */
+function getPosition (el) {
+ var p = { left: el.offsetLeft, top: el.offsetTop };
+ el = el.offsetParent;
+ while (el) {
+ p.left += el.offsetLeft;
+ p.top += el.offsetTop;
+ if (el !== doc.body && el !== doc.documentElement) {
+ p.left -= el.scrollLeft;
+ p.top -= el.scrollTop;
+ }
+ el = el.offsetParent;
+ }
+ return p;
+}
+
/**
* Set the global animation to either a given value, or fall back to the
* given chart's animation option
@@ -327,7 +488,9 @@ if (!globalAdapter && win.jQuery) {
* @param {Function} fn
*/
each = function(arr, fn) {
- for (var i = 0, len = arr.length; i < len; i++) {
+ var i = 0,
+ len = arr.length;
+ for (; i < len; i++) {
if (fn.call(arr[i], arr[i], i, arr) === false) {
return i;
}
@@ -346,8 +509,9 @@ if (!globalAdapter && win.jQuery) {
*/
map = function(arr, fn){
//return jQuery.map(arr, fn);
- var results = [];
- for (var i = 0, len = arr.length; i < len; i++) {
+ var results = [],
+ i = 0, len = arr.length;
+ for (; i < len; i++) {
results[i] = fn.call(arr[i], arr[i], i, arr);
}
return results;
@@ -541,7 +705,7 @@ pathAnim = {
sixify = function(arr) { // in splines make move points have six parameters like bezier curves
i = arr.length;
while (i--) {
- if (arr[i] == M) {
+ if (arr[i] === M) {
arr.splice(i + 1, 0, arr[i+1], arr[i+2], arr[i+1], arr[i+2]);
}
}
@@ -595,10 +759,10 @@ pathAnim = {
i = start.length,
startVal;
- if (pos == 1) { // land on the final path without adjustment points appended in the ends
+ if (pos === 1) { // land on the final path without adjustment points appended in the ends
ret = complete;
- } else if (i == end.length && pos < 1) {
+ } else if (i === end.length && pos < 1) {
while (i--) {
startVal = parseFloat(start[i]);
ret[i] =
@@ -787,10 +951,12 @@ defaultOptions = {
animation: {
duration: 1000
},
+ // connectNulls: false, // docs
//cursor: 'default',
//dashStyle: null,
//enableMouseTracking: true,
events: {},
+ //legendIndex: 0, // docs (+ pie points)
lineWidth: 2,
shadow: true,
// stacking: null,
@@ -1031,7 +1197,7 @@ var defaultXAxisOptions = {
//x: 0,
//y: 0
},
- type: 'linear' // linear or datetime
+ type: 'linear' // linear, logarithmic or datetime // docs
},
defaultYAxisOptions = merge(defaultXAxisOptions, {
@@ -1052,6 +1218,19 @@ defaultYAxisOptions = merge(defaultXAxisOptions, {
title: {
rotation: 270,
text: 'Y-values'
+ },
+ stackLabels: {
+ enabled: false,
+ //align: dynamic,
+ //y: dynamic,
+ //x: dynamic,
+ //verticalAlign: dynamic,
+ //textAlign: dynamic,
+ //rotation: 0,
+ formatter: function() {
+ return this.total;
+ },
+ style: defaultLabelOptions.style
}
}),
@@ -1137,6 +1316,10 @@ defaultPlotOptions.column = merge(defaultSeriesOptions, {
borderColor: '#000000',
shadow: false
}
+ },
+ dataLabels: {
+ y: null,
+ verticalAlign: null
}
});
defaultPlotOptions.bar = merge(defaultPlotOptions.column, {
@@ -1183,19 +1366,6 @@ defaultPlotOptions.pie = merge(defaultSeriesOptions, {
setTimeMethods();
-/**
- * Extend a prototyped class by new members
- * @param {Object} parent
- * @param {Object} members
- */
-function extendClass(parent, members) {
- var object = function(){};
- object.prototype = new parent();
- extend(object.prototype, members);
- return object;
-}
-
-
/**
* Handle color operations. The object methods are chainable.
* @param {String} input The input color in either rbga or hex format
@@ -1211,13 +1381,17 @@ var Color = function(input) {
function init(input) {
// rgba
- if((result = /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(input))) {
+ result = /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(input);
+ if (result) {
rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
}
// hex
- else if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input))) {
- rgba = [pInt(result[1],16), pInt(result[2],16), pInt(result[3],16), 1];
+ else {
+ result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input);
+ if (result) {
+ rgba = [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1];
+ }
}
}
@@ -1230,9 +1404,9 @@ var Color = function(input) {
// it's NaN if gradient colors on a column chart
if (rgba && !isNaN(rgba[0])) {
- if (format == 'rgb') {
+ if (format === 'rgb') {
ret = 'rgb('+ rgba[0] +','+ rgba[1] +','+ rgba[2] +')';
- } else if (format == 'a') {
+ } else if (format === 'a') {
ret = rgba[3];
} else {
ret = 'rgba('+ rgba.join(',') +')';
@@ -1283,120 +1457,6 @@ var Color = function(input) {
};
};
-
-
-/**
- * Format a number and return a string based on input settings
- * @param {Number} number The input number to format
- * @param {Number} decimals The amount of decimals
- * @param {String} decPoint The decimal point, defaults to the one given in the lang options
- * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options
- */
-function numberFormat (number, decimals, decPoint, thousandsSep) {
- var lang = defaultOptions.lang,
- // http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/
- n = number, c = isNaN(decimals = mathAbs(decimals)) ? 2 : decimals,
- d = decPoint === undefined ? lang.decimalPoint : decPoint,
- t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep, s = n < 0 ? "-" : "",
- i = pInt(n = mathAbs(+n || 0).toFixed(c)) + "", j = (j = i.length) > 3 ? j % 3 : 0;
-
- return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) +
- (c ? d + mathAbs(n - i).toFixed(c).slice(2) : "");
-}
-
-/**
- * Based on http://www.php.net/manual/en/function.strftime.php
- * @param {String} format
- * @param {Number} timestamp
- * @param {Boolean} capitalize
- */
-dateFormat = function (format, timestamp, capitalize) {
- function pad (number) {
- return number.toString().replace(/^([0-9])$/, '0$1');
- }
-
- if (!defined(timestamp) || isNaN(timestamp)) {
- return 'Invalid date';
- }
- format = pick(format, '%Y-%m-%d %H:%M:%S');
-
- var date = new Date(timestamp * timeFactor),
-
- // get the basic time values
- hours = date[getHours](),
- day = date[getDay](),
- dayOfMonth = date[getDate](),
- month = date[getMonth](),
- fullYear = date[getFullYear](),
- lang = defaultOptions.lang,
- langWeekdays = lang.weekdays,
- langMonths = lang.months,
-
- // list all format keys
- replacements = {
-
- // Day
- 'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon'
- 'A': langWeekdays[day], // Long weekday, like 'Monday'
- 'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31
- 'e': dayOfMonth, // Day of the month, 1 through 31
-
- // Week (none implemented)
-
- // Month
- 'b': langMonths[month].substr(0, 3), // Short month, like 'Jan'
- 'B': langMonths[month], // Long month, like 'January'
- 'm': pad(month + 1), // Two digit month number, 01 through 12
-
- // Year
- 'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009
- 'Y': fullYear, // Four digits year, like 2009
-
- // Time
- 'H': pad(hours), // Two digits hours in 24h format, 00 through 23
- 'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11
- 'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12
- 'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59
- 'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM
- 'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM
- 'S': pad(date.getSeconds()) // Two digits seconds, 00 through 59
-
- };
-
-
- // do the replaces
- for (var key in replacements) {
- format = format.replace('%'+ key, replacements[key]);
- }
-
- // Optionally capitalize the string and return
- return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format;
-};
-
-
-
-/**
- * Loop up the node tree and add offsetWidth and offsetHeight to get the
- * total page offset for a given element. Used by Opera and iOS on hover and
- * all browsers on point click.
- *
- * @param {Object} el
- *
- */
-function getPosition (el) {
- var p = { left: el.offsetLeft, top: el.offsetTop };
- while ((el = el.offsetParent)) {
- p.left += el.offsetLeft;
- p.top += el.offsetTop;
- if (el != doc.body && el != doc.documentElement) {
- p.left -= el.scrollLeft;
- p.top -= el.scrollTop;
- }
- }
- return p;
-}
-
-
/**
* A wrapper object for SVG elements
*/
@@ -1461,14 +1521,14 @@ SVGElement.prototype = {
// used as a getter: first argument is a string, second is undefined
if (isString(hash)) {
key = hash;
- if (nodeName == 'circle') {
+ if (nodeName === 'circle') {
key = { x: 'cx', y: 'cy' }[key] || key;
- } else if (key == 'strokeWidth') {
+ } else if (key === 'strokeWidth') {
key = 'stroke-width';
}
ret = attr(element, key) || this[key] || 0;
- if (key != 'd' && key != 'visibility') { // 'd' is string in animation step
+ if (key !== 'd' && key !== 'visibility') { // 'd' is string in animation step
ret = parseFloat(ret);
}
@@ -1480,7 +1540,7 @@ SVGElement.prototype = {
value = hash[key];
// paths
- if (key == 'd') {
+ if (key === 'd') {
if (value && value.join) { // join path
value = value.join(' ');
}
@@ -1490,11 +1550,11 @@ SVGElement.prototype = {
this.d = value; // shortcut for animations
// update child tspans x values
- } else if (key == 'x' && nodeName == 'text') {
+ } else if (key === 'x' && nodeName === 'text') {
for (i = 0; i < element.childNodes.length; i++ ) {
child = element.childNodes[i];
// if the x values are equal, the tspan represents a linebreak
- if (attr(child, 'x') == attr(element, 'x')) {
+ if (attr(child, 'x') === attr(element, 'x')) {
//child.setAttribute('x', value);
attr(child, 'x', value);
}
@@ -1506,28 +1566,31 @@ SVGElement.prototype = {
}
// apply gradients
- } else if (key == 'fill') {
+ } else if (key === 'fill') {
value = renderer.color(value, element, key);
// circle x and y
- } else if (nodeName == 'circle' && (key == 'x' || key == 'y')) {
+ } else if (nodeName === 'circle' && (key === 'x' || key === 'y')) {
key = { x: 'cx', y: 'cy' }[key] || key;
// translation and text rotation
- } else if (key == 'translateX' || key == 'translateY' || key == 'rotation' || key == 'verticalAlign') {
+ } else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || key === 'verticalAlign') {
this[key] = value;
this.updateTransform();
skipAttr = true;
// apply opacity as subnode (required by legacy WebKit and Batik)
- } else if (key == 'stroke') {
+ } else if (key === 'stroke') {
value = renderer.color(value, element, key);
// emulate VML's dashstyle implementation
- } else if (key == 'dashstyle') {
+ } else if (key === 'dashstyle') {
key = 'stroke-dasharray';
- if (value) {
- value = value.toLowerCase()
+ value = value && value.toLowerCase();
+ if (value === 'solid') {
+ value = NONE;
+ } else if (value) {
+ value = value
.replace('shortdashdotdot', '3,1,1,1,1,1,')
.replace('shortdashdot', '3,1,1,1')
.replace('shortdot', '1,1,')
@@ -1542,20 +1605,21 @@ SVGElement.prototype = {
while (i--) {
value[i] = pInt(value[i]) * hash['stroke-width'];
}
+
value = value.join(',');
}
// special
- } else if (key == 'isTracker') {
+ } else if (key === 'isTracker') {
this[key] = value;
// IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2
// is unable to cast them. Test again with final IE9.
- } else if (key == 'width') {
+ } else if (key === 'width') {
value = pInt(value);
// Text alignment
- } else if (key == 'align') {
+ } else if (key === 'align') {
key = 'text-anchor';
value = { left: 'start', center: 'middle', right: 'end' }[value];
}
@@ -1563,12 +1627,12 @@ SVGElement.prototype = {
// jQuery animate changes case
- if (key == 'strokeWidth') {
+ if (key === 'strokeWidth') {
key = 'stroke-width';
}
// Chrome/Win < 6 bug (http://code.google.com/p/chromium/issues/detail?id=15461)
- if (isWebKit && key == 'stroke-width' && value === 0) {
+ if (isWebKit && key === 'stroke-width' && value === 0) {
value = 0.000001;
}
@@ -1591,15 +1655,12 @@ SVGElement.prototype = {
}
}
- /* trows errors in Chrome
- if ((key == 'width' || key == 'height') && nodeName == 'rect' && value < 0) {
- console.log(element);
+ // validate heights
+ if ((key === 'width' || key === 'height') && nodeName === 'rect' && value < 0) {
+ value = 0;
}
- */
-
-
- if (key == 'text') {
+ if (key === 'text') {
// only one node allowed
this.textStr = value;
if (this.added) {
@@ -1625,12 +1686,16 @@ SVGElement.prototype = {
symbolAttr: function(hash) {
var wrapper = this;
- each (['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR'], function(key) {
+ each(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR'], function(key) {
wrapper[key] = pick(hash[key], wrapper[key]);
});
wrapper.attr({
- d: wrapper.renderer.symbols[wrapper.symbolName](wrapper.x, wrapper.y, wrapper.r, {
+ d: wrapper.renderer.symbols[wrapper.symbolName](
+ mathRound(wrapper.x * 2) / 2, // Round to halves. Issue #274.
+ mathRound(wrapper.y * 2) / 2,
+ wrapper.r,
+ {
start: wrapper.start,
end: wrapper.end,
width: wrapper.width,
@@ -1676,7 +1741,7 @@ SVGElement.prototype = {
values.strokeWidth = strokeWidth;
for (key in values) {
- if (wrapper[key] != values[key]) { // only set attribute if changed
+ if (wrapper[key] !== values[key]) { // only set attribute if changed
wrapper[key] = attr[key] = values[key];
}
}
@@ -1691,8 +1756,10 @@ SVGElement.prototype = {
css: function(styles) {
var elemWrapper = this,
elem = elemWrapper.element,
- textWidth = styles && styles.width && elem.nodeName == 'text';
-
+ textWidth = styles && styles.width && elem.nodeName === 'text',
+ camelStyles = styles,
+ n;
+
// convert legacy
if (styles && styles.color) {
styles.fill = styles.color;
@@ -1708,6 +1775,14 @@ SVGElement.prototype = {
// store object
elemWrapper.styles = styles;
+ // hyphenate
+ if (defined(styles)) {
+ styles = {};
+ for (n in camelStyles) {
+ styles[hyphenate(n)] = camelStyles[n];
+ }
+ }
+
// serialize and set style attribute
if (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute
if (textWidth) {
@@ -1737,12 +1812,12 @@ SVGElement.prototype = {
on: function(eventType, handler) {
var fn = handler;
// touch
- if (hasTouch && eventType == 'click') {
+ if (hasTouch && eventType === 'click') {
eventType = 'touchstart';
fn = function(e) {
e.preventDefault();
handler();
- }
+ };
}
// simplest possible event model for internal use
this.element['on'+ eventType] = fn;
@@ -1790,6 +1865,11 @@ SVGElement.prototype = {
translateY += wrapper.attr('height');
}
+ if(wrapper.imagesize) {
+ translateX -= wrapper.imagesize[0]/2;
+ translateY -= wrapper.imagesize[1]/2;
+ }
+
// apply translate
if (translateX || translateY) {
transform.push('translate('+ translateX +','+ translateY +')');
@@ -1826,19 +1906,20 @@ SVGElement.prototype = {
*
*/
align: function(alignOptions, alignByTranslate, box) {
+ var elemWrapper = this;
if (!alignOptions) { // called on resize
- alignOptions = this.alignOptions;
- alignByTranslate = this.alignByTranslate;
+ alignOptions = elemWrapper.alignOptions;
+ alignByTranslate = elemWrapper.alignByTranslate;
} else { // first call on instanciate
- this.alignOptions = alignOptions;
- this.alignByTranslate = alignByTranslate;
+ elemWrapper.alignOptions = alignOptions;
+ elemWrapper.alignByTranslate = alignByTranslate;
if (!box) { // boxes other than renderer handle this internally
- this.renderer.alignedObjects.push(this);
+ elemWrapper.renderer.alignedObjects.push(elemWrapper);
}
}
- box = pick(box, this.renderer);
+ box = pick(box, elemWrapper.renderer);
var align = alignOptions.align,
vAlign = alignOptions.verticalAlign,
@@ -1864,17 +1945,18 @@ SVGElement.prototype = {
attribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y);
// animate only if already placed
- this[this.placed ? 'animate' : 'attr'](attribs);
- this.placed = true;
+ elemWrapper[elemWrapper.placed ? 'animate' : 'attr'](attribs);
+ elemWrapper.placed = true;
+ elemWrapper.alignAttr = attribs;
- return this;
+ return elemWrapper;
},
/**
* Get the bounding box (width, height, x and y) for the element
*/
getBBox: function() {
- var bBox,
+ var bBox,
width,
height,
rotation = this.rotation,
@@ -1962,7 +2044,7 @@ SVGElement.prototype = {
for (i = 0; i < childNodes.length; i++) {
otherElement = childNodes[i];
otherZIndex = attr(otherElement, 'zIndex');
- if (otherElement != element && (
+ if (otherElement !== element && (
// insert before the first element with a higher zIndex
pInt(otherZIndex) > zIndex ||
// if no zIndex given, insert before the first element with a zIndex
@@ -2039,7 +2121,7 @@ SVGElement.prototype = {
* Add a shadow to the element. Must be done after the element is added to the DOM
* @param {Boolean} apply
*/
- shadow: function(apply) {
+ shadow: function(apply, group) {
var shadows = [],
i,
shadow,
@@ -2061,8 +2143,11 @@ SVGElement.prototype = {
'fill': NONE
});
-
- element.parentNode.insertBefore(shadow, element);
+ if (group) {
+ group.element.appendChild(shadow);
+ } else {
+ element.parentNode.insertBefore(shadow, element);
+ }
shadows.push(shadow);
}
@@ -2074,8 +2159,6 @@ SVGElement.prototype = {
}
};
-
-
/**
* The default SVG renderer
*/
@@ -2083,6 +2166,9 @@ var SVGRenderer = function() {
this.init.apply(this, arguments);
};
SVGRenderer.prototype = {
+
+ Element: SVGElement,
+
/**
* Initialize the SVGRenderer
* @param {Object} container
@@ -2095,7 +2181,6 @@ SVGRenderer.prototype = {
loc = location,
boxWrapper;
- renderer.Element = SVGElement;
boxWrapper = renderer.createElement('svg')
.attr({
xmlns: SVG_NS,
@@ -2139,17 +2224,19 @@ SVGRenderer.prototype = {
.replace(/<(i|em)>/g, '')
.replace(//g, ' ')
- .split(/ ]?>/g),
+ .split(//g),
childNodes = textNode.childNodes,
styleRegex = /style="([^"]+)"/,
hrefRegex = /href="([^"]+)"/,
parentX = attr(textNode, 'x'),
textStyles = wrapper.styles,
- reverse = isFirefox && textStyles && textStyles.HcDirection == 'rtl' && !this.forExport, // issue #38
+ reverse = isFirefox && textStyles && textStyles['-hc-direction'] === 'rtl' &&
+ !this.forExport && pInt(userAgent.split('Firefox/')[1]) < 4, // issue #38
arr,
width = textStyles && pInt(textStyles.width),
- textLineHeight = textStyles && textStyles.lineHeight,
+ textLineHeight = textStyles && textStyles['line-height'],
lastLine,
+ GET_COMPUTED_STYLE = 'getComputedStyle',
i = childNodes.length;
// remove old text
@@ -2168,7 +2255,7 @@ SVGRenderer.prototype = {
spans = line.split('|||');
each(spans, function (span) {
- if (span !== '' || spans.length == 1) {
+ if (span !== '' || spans.length === 1) {
var attributes = {},
tspan = doc.createElementNS(SVG_NS, 'tspan');
if (styleRegex.test(span)) {
@@ -2183,14 +2270,16 @@ SVGRenderer.prototype = {
css(tspan, { cursor: 'pointer' });
}
- span = span.replace(/<(.|\n)*?>/g, '') || ' ';
+ span = (span.replace(/<(.|\n)*?>/g, '') || ' ')
+ .replace(/</g, '<')
+ .replace(/>/g, '>');
// issue #38 workaround.
if (reverse) {
arr = [];
i = span.length;
while (i--) {
- arr.push(span.charAt(i))
+ arr.push(span.charAt(i));
}
span = arr.join('');
}
@@ -2208,10 +2297,18 @@ SVGRenderer.prototype = {
// first span on subsequent line, add the line height
if (!spanNo) {
if (lineNo) {
+
+ // allow getting the right offset height in exporting in IE
+ if (!hasSVG && wrapper.renderer.forExport) {
+ css(tspan, { display: 'block' });
+ }
+
// Webkit and opera sometimes return 'normal' as the line height. In that
// case, webkit uses offsetHeight, while Opera falls back to 18
- if(window.getComputedStyle) lineHeight = pInt(window.getComputedStyle(lastLine, null).getPropertyValue('line-height'));
- if (isNaN(lineHeight)) {
+ lineHeight = win[GET_COMPUTED_STYLE] &&
+ win[GET_COMPUTED_STYLE](lastLine, null).getPropertyValue('line-height');
+
+ if (!lineHeight || isNaN(lineHeight)) {
lineHeight = textLineHeight || lastLine.offsetHeight || 18;
}
attr(tspan, 'dy', lineHeight);
@@ -2237,14 +2334,14 @@ SVGRenderer.prototype = {
while (words.length || rest.length) {
actualWidth = textNode.getBBox().width;
tooLong = actualWidth > width;
- if (!tooLong || words.length == 1) { // new line needed
+ if (!tooLong || words.length === 1) { // new line needed
words = rest;
rest = [];
if (words.length) {
tspan = doc.createElementNS(SVG_NS, 'tspan');
attr(tspan, {
- x: parentX,
- dy: textLineHeight || 16
+ dy: textLineHeight || 16,
+ x: parentX
});
textNode.appendChild(tspan);
@@ -2254,12 +2351,12 @@ SVGRenderer.prototype = {
}
} else { // append to existing line tspan
tspan.removeChild(tspan.firstChild);
- rest.unshift(words.pop());
+ rest.unshift(words.pop());
+ }
+ if (words.length) {
+ tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
}
-
- tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
}
-
}
}
});
@@ -2276,10 +2373,10 @@ SVGRenderer.prototype = {
crispLine: function(points, width) {
// points format: [M, 0, 0, L, 100, 0]
// normalize to a crisp line
- if (points[1] == points[4]) {
+ if (points[1] === points[4]) {
points[1] = points[4] = mathRound(points[1]) + (width % 2 / 2);
}
- if (points[2] == points[5]) {
+ if (points[2] === points[5]) {
points[2] = points[5] = mathRound(points[2]) + (width % 2 / 2);
}
return points;
@@ -2359,6 +2456,7 @@ SVGRenderer.prototype = {
width = x.width;
height = x.height;
r = x.r;
+ strokeWidth = x.strokeWidth;
x = x.x;
}
var wrapper = this.createElement('rect').attr({
@@ -2433,8 +2531,14 @@ SVGRenderer.prototype = {
elemWrapper = this.createElement('image').attr(attribs);
// set the href in the xlink namespace
- elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink',
- 'href', src);
+ if (elemWrapper.element.setAttributeNS) {
+ elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink',
+ 'href', src);
+ } else {
+ // could be exporting in IE
+ // using href throws "not supported" in ie7 and under, requries regex shim to fix later
+ elemWrapper.element.setAttribute('hc-svg-href', src);
+ }
return elemWrapper;
},
@@ -2457,14 +2561,15 @@ SVGRenderer.prototype = {
// check if there's a path defined for this symbol
path = symbolFn && symbolFn(
- x,
- y,
+ mathRound(x),
+ mathRound(y),
radius,
options
),
imageRegex = /^url\((.*?)\)$/,
- imageSrc;
+ imageSrc,
+ imageSize;
if (path) {
@@ -2484,7 +2589,19 @@ SVGRenderer.prototype = {
// image symbols
} else if (imageRegex.test(symbol)) {
+ var centerImage = function(img, size) {
+ img.attr({
+ width: size[0],
+ height: size[1]
+ }).translate(
+ obj.translateX-mathRound(size[0] / 2),
+ obj.translateY-mathRound(size[1] / 2)
+ );
+ img.imagesize = [size[0], size[1]];
+ };
+
imageSrc = symbol.match(imageRegex)[1];
+ imageSize = symbolSizes[imageSrc];
// create the image synchronously, add attribs async
obj = this.image(imageSrc)
@@ -2492,22 +2609,22 @@ SVGRenderer.prototype = {
x: x,
y: y
});
-
- // create a dummy JavaScript image to get the width and height
- createElement('img', {
- onload: function() {
- var img = this,
- size = symbolSizes[img.src] || [img.width, img.height];
- obj.attr({
- width: size[0],
- height: size[1]
- }).translate(
- -mathRound(size[0] / 2),
- -mathRound(size[1] / 2)
- );
- },
- src: imageSrc
- });
+
+ if (imageSize) {
+ centerImage(obj, imageSize);
+ } else {
+ // initialize image to be 0 size so export will still function if there's no cached sizes
+ obj.attr({ width: 0, height: 0 });
+
+ // create a dummy JavaScript image to get the width and height
+ createElement('img', {
+ onload: function() {
+ var img = this;
+ centerImage(obj, symbolSizes[imageSrc] = [img.width, img.height]);
+ },
+ src: imageSrc
+ });
+ }
// default circles
} else {
@@ -2711,6 +2828,8 @@ SVGRenderer.prototype = {
}
}; // end SVGRenderer
+// general renderer
+Renderer = SVGRenderer;
@@ -2741,18 +2860,18 @@ var VMLElement = extendClass( SVGElement, {
style = ['position: ', ABSOLUTE, ';'];
// divs and shapes need size
- if (nodeName == 'shape' || nodeName == DIV) {
+ if (nodeName === 'shape' || nodeName === DIV) {
style.push('left:0;top:0;width:10px;height:10px;');
}
if (docMode8) {
- style.push('visibility: ', nodeName == DIV ? HIDDEN : VISIBLE);
+ style.push('visibility: ', nodeName === DIV ? HIDDEN : VISIBLE);
}
markup.push(' style="', style.join(''), '"/>');
// create element with default attributes and style
if (nodeName) {
- markup = nodeName == DIV || nodeName == 'span' || nodeName == 'img' ?
+ markup = nodeName === DIV || nodeName === 'span' || nodeName === 'img' ?
markup.join('')
: renderer.prepVML(markup);
this.element = createElement(markup);
@@ -2784,7 +2903,7 @@ var VMLElement = extendClass( SVGElement, {
}
// issue #140 workaround - related to #61 and #74
- if (docMode8 && parentNode.gVis == HIDDEN) {
+ if (docMode8 && parentNode.gVis === HIDDEN) {
css(element, { visibility: HIDDEN });
}
@@ -2828,7 +2947,7 @@ var VMLElement = extendClass( SVGElement, {
// used as a getter, val is undefined
if (isString(hash)) {
key = hash;
- if (key == 'strokeWidth' || key == 'stroke-width') {
+ if (key === 'strokeWidth' || key === 'stroke-width') {
ret = this.strokeweight;
} else {
ret = this[key];
@@ -2847,7 +2966,6 @@ var VMLElement = extendClass( SVGElement, {
// check all the others only once for each call to an element's
// .attr() method
if (!hasSetSymbolSize) {
-
this.symbolAttr(hash);
hasSetSymbolSize = true;
@@ -2855,7 +2973,7 @@ var VMLElement = extendClass( SVGElement, {
skipAttr = true;
- } else if (key == 'd') {
+ } else if (key === 'd') {
value = value || [];
this.d = value.join(' '); // used in getter for animation
@@ -2871,7 +2989,7 @@ var VMLElement = extendClass( SVGElement, {
convertedPath[i] = mathRound(value[i] * 10) - 5;
}
// close the path
- else if (value[i] == 'Z') {
+ else if (value[i] === 'Z') {
convertedPath[i] = 'x';
}
else {
@@ -2879,7 +2997,7 @@ var VMLElement = extendClass( SVGElement, {
}
}
- value = convertedPath.join(' ') || 'x';
+ value = convertedPath.join(' ') || 'x';
element.path = value;
// update shadows
@@ -2892,17 +3010,17 @@ var VMLElement = extendClass( SVGElement, {
skipAttr = true;
// directly mapped to css
- } else if (key == 'zIndex' || key == 'visibility') {
+ } else if (key === 'zIndex' || key === 'visibility') {
// issue 61 workaround
- if (docMode8 && key == 'visibility' && nodeName == 'DIV') {
+ if (docMode8 && key === 'visibility' && nodeName === 'DIV') {
element.gVis = value;
childNodes = element.childNodes;
i = childNodes.length;
while (i--) {
css(childNodes[i], { visibility: value });
}
- if (value == VISIBLE) { // issue 74
+ if (value === VISIBLE) { // issue 74
value = null;
}
}
@@ -2936,7 +3054,7 @@ var VMLElement = extendClass( SVGElement, {
this[key] = value; // used in getter
- if (element.tagName == 'SPAN') {
+ if (element.tagName === 'SPAN') {
this.updateTransform();
} else {
@@ -2944,19 +3062,19 @@ var VMLElement = extendClass( SVGElement, {
}
// class name
- } else if (key == 'class') {
+ } else if (key === 'class') {
// IE8 Standards mode has problems retrieving the className
element.className = value;
// stroke
- } else if (key == 'stroke') {
+ } else if (key === 'stroke') {
value = renderer.color(value, element, key);
key = 'strokecolor';
// stroke width
- } else if (key == 'stroke-width' || key == 'strokeWidth') {
+ } else if (key === 'stroke-width' || key === 'strokeWidth') {
element.stroked = value ? true : false;
key = 'strokeweight';
this[key] = value; // used in getter, issue #113
@@ -2965,7 +3083,7 @@ var VMLElement = extendClass( SVGElement, {
}
// dashStyle
- } else if (key == 'dashstyle') {
+ } else if (key === 'dashstyle') {
var strokeElem = element.getElementsByTagName('stroke')[0] ||
createElement(renderer.prepVML([' ']), null, null, element);
strokeElem[key] = value || 'solid';
@@ -2974,12 +3092,12 @@ var VMLElement = extendClass( SVGElement, {
skipAttr = true;
// fill
- } else if (key == 'fill') {
+ } else if (key === 'fill') {
- if (nodeName == 'SPAN') { // text color
+ if (nodeName === 'SPAN') { // text color
elemStyle.color = value;
} else {
- element.filled = value != NONE ? true : false;
+ element.filled = value !== NONE ? true : false;
value = renderer.color(value, element, key);
@@ -2987,8 +3105,8 @@ var VMLElement = extendClass( SVGElement, {
}
// translation for animation
- } else if (key == 'translateX' || key == 'translateY' || key == 'rotation' || key == 'align') {
- if (key == 'align') {
+ } else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || key === 'align') {
+ if (key === 'align') {
key = 'textAlign';
}
this[key] = value;
@@ -2998,14 +3116,15 @@ var VMLElement = extendClass( SVGElement, {
}
// text for rotated and non-rotated elements
- else if (key == 'text') {
+ else if (key === 'text') {
+ this.bBox = null;
element.innerHTML = value;
skipAttr = true;
}
// let the shadow follow the main element
- if (shadows && key == 'visibility') {
+ if (shadows && key === 'visibility') {
i = shadows.length;
while (i--) {
shadows[i].style[key] = value;
@@ -3049,7 +3168,7 @@ var VMLElement = extendClass( SVGElement, {
css: function(styles) {
var wrapper = this,
element = wrapper.element,
- textWidth = styles && element.tagName == 'SPAN' && styles.width;
+ textWidth = styles && element.tagName === 'SPAN' && styles.width;
/*if (textWidth) {
extend(styles, {
@@ -3066,8 +3185,6 @@ var VMLElement = extendClass( SVGElement, {
wrapper.styles = extend(wrapper.styles, styles);
css(wrapper.element, styles);
-
-
return wrapper;
},
@@ -3106,19 +3223,24 @@ var VMLElement = extendClass( SVGElement, {
*/
getBBox: function() {
- var element = this.element;
+ var wrapper = this,
+ element = wrapper.element,
+ bBox = wrapper.bBox;
- // faking getBBox in exported SVG in legacy IE
- if (element.nodeName == 'text') {
- element.style.position = ABSOLUTE;
+ if (!bBox) {
+ // faking getBBox in exported SVG in legacy IE
+ if (element.nodeName === 'text') {
+ element.style.position = ABSOLUTE;
+ }
+
+ bBox = wrapper.bBox = {
+ x: element.offsetLeft,
+ y: element.offsetTop,
+ width: element.offsetWidth,
+ height: element.offsetHeight
+ };
}
-
- return {
- x: element.offsetLeft,
- y: element.offsetTop,
- width: element.offsetWidth,
- height: element.offsetHeight
- };
+ return bBox;
},
@@ -3157,7 +3279,7 @@ var VMLElement = extendClass( SVGElement, {
y = wrapper.y || 0,
align = wrapper.textAlign || 'left',
alignCorrection = { left: 0, center: 0.5, right: 1 }[align],
- nonLeft = align && align != 'left';
+ nonLeft = align && align !== 'left';
// apply translate
if (translateX || translateY) {
@@ -3174,7 +3296,7 @@ var VMLElement = extendClass( SVGElement, {
});
}
- if (elem.tagName == 'SPAN') {
+ if (elem.tagName === 'SPAN') {
var width, height,
rotation = wrapper.rotation,
@@ -3188,7 +3310,7 @@ var VMLElement = extendClass( SVGElement, {
yCorr = wrapper.yCorr || 0,
currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(',');
- if (currentTextTransform != wrapper.cTT) { // do the calculations and DOM access only if properties changed
+ if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed
if (defined(rotation)) {
radians = rotation * deg2rad; // deg to rad
@@ -3218,7 +3340,7 @@ var VMLElement = extendClass( SVGElement, {
}
// correct x and y
- lineHeight = mathRound(pInt(elem.style.fontSize || 12) * 1.2);
+ lineHeight = mathRound((pInt(elem.style.fontSize) || 12) * 1.2);
xCorr = costheta < 0 && -width;
yCorr = sintheta < 0 && -height;
@@ -3258,7 +3380,7 @@ var VMLElement = extendClass( SVGElement, {
* Apply a drop shadow by copying elements and giving them different strokes
* @param {Boolean} apply
*/
- shadow: function(apply) {
+ shadow: function(apply, group) {
var shadows = [],
i,
element = this.element,
@@ -3268,8 +3390,8 @@ var VMLElement = extendClass( SVGElement, {
markup,
path = element.path;
- // the path is some mysterious string-like object that can be cast to a string
- if (''+ element.path === '') {
+ // some times empty paths are not strings
+ if (path && typeof path.value !== 'string') {
path = 'x';
}
@@ -3291,7 +3413,11 @@ var VMLElement = extendClass( SVGElement, {
// insert it
- element.parentNode.insertBefore(shadow, element);
+ if (group) {
+ group.element.appendChild(shadow);
+ } else {
+ element.parentNode.insertBefore(shadow, element);
+ }
// record it
shadows.push(shadow);
@@ -3313,6 +3439,7 @@ VMLRenderer = function() {
};
VMLRenderer.prototype = merge( SVGRenderer.prototype, { // inherit SVGRenderer
+ Element: VMLElement,
isIE8: userAgent.indexOf('MSIE 8.0') > -1,
@@ -3326,7 +3453,6 @@ VMLRenderer.prototype = merge( SVGRenderer.prototype, { // inherit SVGRenderer
var renderer = this,
boxWrapper;
- renderer.Element = VMLElement;
renderer.alignedObjects = [];
boxWrapper = renderer.createElement(DIV);
@@ -3471,7 +3597,7 @@ VMLRenderer.prototype = merge( SVGRenderer.prototype, { // inherit SVGRenderer
// if the color is an rgba color, split it and add a fill node
// to hold the opacity component
- } else if (regexRgba.test(color) && elem.tagName != 'IMG') {
+ } else if (regexRgba.test(color) && elem.tagName !== 'IMG') {
colorObject = Color(color);
@@ -3499,7 +3625,7 @@ VMLRenderer.prototype = merge( SVGRenderer.prototype, { // inherit SVGRenderer
if (isIE8) { // add xmlns and style inline
markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />');
- if (markup.indexOf('style="') == -1) {
+ if (markup.indexOf('style="') === -1) {
markup = markup.replace('/>', ' style="'+ vmlStyle +'" />');
} else {
markup = markup.replace('style="', 'style="'+ vmlStyle);
@@ -3556,7 +3682,7 @@ VMLRenderer.prototype = merge( SVGRenderer.prototype, { // inherit SVGRenderer
* @param {Number} r
*/
circle: function(x, y, r) {
- return this.path(this.symbols.circle(x, y, r));
+ return this.symbol('circle').attr({ x: x, y: y, r: r});
},
/**
@@ -3614,6 +3740,7 @@ VMLRenderer.prototype = merge( SVGRenderer.prototype, { // inherit SVGRenderer
width = x.width;
height = x.height;
r = x.r;
+ strokeWidth = x.strokeWidth;
x = x.x;
}
var wrapper = this.symbol('rect');
@@ -3653,7 +3780,7 @@ VMLRenderer.prototype = merge( SVGRenderer.prototype, { // inherit SVGRenderer
sinEnd = mathSin(end),
innerRadius = options.innerR,
circleCorrection = 0.07 / radius,
- innerCorrection = innerRadius && 0.1 / innerRadius || 0;
+ innerCorrection = (innerRadius && 0.1 / innerRadius) || 0;
if (end - start === 0) { // no angle, don't show it.
return ['x'];
@@ -3774,17 +3901,15 @@ VMLRenderer.prototype = merge( SVGRenderer.prototype, { // inherit SVGRenderer
}
}
});
+
+// general renderer
+Renderer = VMLRenderer;
}
/* ****************************************************************************
* *
* END OF INTERNET EXPLORER <= 8 SPECIFIC CODE *
* *
*****************************************************************************/
-
-/**
- * General renderer
- */
-var Renderer = hasSVG ? SVGRenderer : VMLRenderer;
/**
@@ -3900,7 +4025,9 @@ function Chart (options, callback) {
);
var axis = this,
- isDatetimeAxis = options.type == 'datetime',
+ type = options.type,
+ isDatetimeAxis = type === 'datetime',
+ isLog = type === 'logarithmic',
offset = options.offset || 0,
xOrY = isXAxis ? 'x' : 'y',
axisLength,
@@ -3915,8 +4042,8 @@ function Chart (options, callback) {
dataMin,
dataMax,
associatedSeries,
- userSetMin,
- userSetMax,
+ userMin,
+ userMax,
max = null,
min = null,
oldMin,
@@ -3967,7 +4094,7 @@ function Chart (options, callback) {
staggerLines = horiz && options.labels.staggerLines,
reversed = options.reversed,
- tickmarkOffset = (categories && options.tickmarkPlacement == 'between') ? 0.5 : 0;
+ tickmarkOffset = (categories && options.tickmarkPlacement === 'between') ? 0.5 : 0;
/**
* The Tick class
@@ -3990,27 +4117,28 @@ function Chart (options, callback) {
var pos = this.pos,
labelOptions = options.labels,
str,
- withLabel = !((pos == min && !pick(options.showFirstLabel, 1)) ||
- (pos == max && !pick(options.showLastLabel, 0))),
- width = categories && horiz && categories.length &&
+ withLabel = !((pos === min && !pick(options.showFirstLabel, 1)) ||
+ (pos === max && !pick(options.showLastLabel, 0))),
+ width = (categories && horiz && categories.length &&
!labelOptions.step && !labelOptions.staggerLines &&
!labelOptions.rotation &&
- plotWidth / categories.length ||
- !horiz && plotWidth / 2,
+ plotWidth / categories.length) ||
+ (!horiz && plotWidth / 2),
css,
label = this.label;
// get the string
str = labelFormatter.call({
- isFirst: pos == tickPositions[0],
- isLast: pos == tickPositions[tickPositions.length - 1],
+ isFirst: pos === tickPositions[0],
+ isLast: pos === tickPositions[tickPositions.length - 1],
dateTimeLabelFormat: dateTimeLabelFormat,
value: (categories && categories[pos] ? categories[pos] : pos)
});
+
// prepare CSS
- css = width && { width: (width - 2 * (labelOptions.padding || 10)) +PX };
+ css = width && { width: mathMax(1, mathRound(width - 2 * (labelOptions.padding || 10))) +PX };
css = extend(css, labelOptions.style);
// first call
@@ -4072,7 +4200,7 @@ function Chart (options, callback) {
tickColor = major ? options.tickColor : options.minorTickColor,
tickPosition = major ? options.tickPosition : options.minorTickPosition,
step = labelOptions.step,
- cHeight = old && oldChartHeight || chartHeight,
+ cHeight = (old && oldChartHeight) || chartHeight,
attribs,
x,
y;
@@ -4080,7 +4208,7 @@ function Chart (options, callback) {
// get x and y position for ticks and labels
x = horiz ?
translate(pos + tickmarkOffset, null, null, old) + transB :
- plotLeft + offset + (opposite ? (old && oldChartWidth || chartWidth) - marginRight - plotLeft : 0);
+ plotLeft + offset + (opposite ? ((old && oldChartWidth) || chartWidth) - marginRight - plotLeft : 0);
y = horiz ?
cHeight - marginBottom + offset - (opposite ? plotHeight : 0) :
@@ -4115,7 +4243,7 @@ function Chart (options, callback) {
if (tickWidth) {
// negate the length
- if (tickPosition == 'inside') {
+ if (tickPosition === 'inside') {
tickLength = -tickLength;
}
if (opposite) {
@@ -4146,7 +4274,7 @@ function Chart (options, callback) {
}
// the label is created on init - now move it into place
- if (label) {
+ if (label && !isNaN(x)) {
x = x + labelOptions.x - (tickmarkOffset && horiz ?
tickmarkOffset * transA * (reversed ? -1 : 1) : 0);
y = y + labelOptions.y - (tickmarkOffset && !horiz ?
@@ -4154,13 +4282,13 @@ function Chart (options, callback) {
// vertically centered
if (!defined(labelOptions.y)) {
- y += parseInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2;
+ y += pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2;
}
// correct for staggered labels
if (staggerLines) {
- y += (index % staggerLines) * 16;
+ y += (index / (step || 1) % staggerLines) * 16;
}
// apply step
if (step) {
@@ -4172,7 +4300,6 @@ function Chart (options, callback) {
x: x,
y: y
});
-
}
tick.isNew = false;
@@ -4251,7 +4378,7 @@ function Chart (options, callback) {
else if (defined(from) && defined(to)) {
// keep within plot area
from = mathMax(from, min);
- to = mathMin(to, max);
+ to = mathMin(to, max);
toPath = getPlotLinePath(to);
path = getPlotLinePath(from);
@@ -4286,7 +4413,7 @@ function Chart (options, callback) {
svgElem.hide();
svgElem.onGetPath = function() {
svgElem.show();
- }
+ };
}
} else if (path && path.length) {
plotLine.svgElem = svgElem = renderer.path(path)
@@ -4333,8 +4460,8 @@ function Chart (options, callback) {
}
// get the bounding box and align the label
- xs = [path[1], path[4], path[6] || path[1]];
- ys = [path[2], path[5], path[7] || path[2]];
+ xs = [path[1], path[4], pick(path[6], path[1])];
+ ys = [path[2], path[5], pick(path[7], path[2])];
x = mathMin.apply(math, xs);
y = mathMin.apply(math, ys);
@@ -4372,6 +4499,91 @@ function Chart (options, callback) {
}
};
+ /**
+ * The class for stack items
+ */
+ function StackItem(options, isNegative, x) {
+ var stackItem = this;
+
+ // Tells if the stack is negative
+ stackItem.isNegative = isNegative;
+
+ // Save the options to be able to style the label
+ stackItem.options = options;
+
+ // Save the x value to be able to position the label later
+ stackItem.x = x;
+
+ // The align options and text align varies on whether the stack is negative and
+ // if the chart is inverted or not.
+ // First test the user supplied value, then use the dynamic.
+ stackItem.alignOptions = {
+ align: options.align || (inverted ? (isNegative ? 'left' : 'right') : 'center'),
+ verticalAlign: options.verticalAlign || (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')),
+ y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)),
+ x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0)
+ };
+
+ stackItem.textAlign = options.textAlign || (inverted ? (isNegative ? 'right' : 'left') : 'center');
+ }
+
+ StackItem.prototype = {
+ /**
+ * Sets the total of this stack. Should be called when a serie is hidden or shown
+ * since that will affect the total of other stacks.
+ */
+ setTotal: function(total) {
+ this.total = total;
+ this.cum = total;
+ },
+
+ /**
+ * Renders the stack total label and adds it to the stack label group.
+ */
+ render: function(group) {
+ var stackItem = this, // aliased this
+ str = stackItem.options.formatter.call(stackItem); // format the text in the label
+
+ // Change the text to reflect the new total and set visibility to hidden in case the serie is hidden
+ if (stackItem.label) {
+ stackItem.label.attr({text: str, visibility: HIDDEN});
+ // Create new label
+ } else {
+ stackItem.label =
+ chart.renderer.text(str, 0, 0) // dummy positions, actual position updated with setOffset method in columnseries
+ .css(stackItem.options.style) // apply style
+ .attr({align: stackItem.textAlign, // fix the text-anchor
+ rotation: stackItem.options.rotation, // rotation
+ visibility: HIDDEN }) // hidden until setOffset is called
+ .add(group); // add to the labels-group
+ }
+ },
+
+ /**
+ * Sets the offset that the stack has from the x value and repositions the label.
+ */
+ setOffset: function(xOffset, xWidth) {
+ var stackItem = this, // aliased this
+ neg = stackItem.isNegative, // special treatment is needed for negative stacks
+ y = axis.translate(stackItem.total), // stack value translated mapped to chart coordinates
+ yZero = axis.translate(0), // stack origin
+ h = mathAbs(y - yZero), // stack height
+ x = chart.xAxis[0].translate(stackItem.x) + xOffset, // stack x position
+ plotHeight = chart.plotHeight,
+ stackBox = { // this is the box for the complete stack
+ x: inverted ? (neg ? y : y - h) : x,
+ y: inverted ? plotHeight - x - xWidth : (neg ? (plotHeight - y - h) : plotHeight - y),
+ width: inverted ? h : xWidth,
+ height: inverted ? xWidth : h
+ };
+
+ if (stackItem.label) {
+ stackItem.label
+ .align(stackItem.alignOptions, null, stackBox) // align the label to the box
+ .attr({visibility: VISIBLE}); // set visibility
+ }
+ }
+ };
/**
* Get the minimum and maximum for the series of each axis
@@ -4397,9 +4609,9 @@ function Chart (options, callback) {
// the series is a cartesian type, and...
serie.isCartesian &&
// we're in the right x or y dimension, and...
- (strAxis == 'xAxis' && isXAxis || strAxis == 'yAxis' && !isXAxis) && (
+ ((strAxis === 'xAxis' && isXAxis) || (strAxis === 'yAxis' && !isXAxis)) && (
// the axis number is given in the options and matches this axis index, or
- (serie.options[strAxis] == options.index) ||
+ (serie.options[strAxis] === options.index) ||
// the axis index is not given
(serie.options[strAxis] === UNDEFINED && options.index === 0)
)
@@ -4426,7 +4638,7 @@ function Chart (options, callback) {
if (!isXAxis) {
stacking = serie.options.stacking;
- usePercentage = stacking == 'percent';
+ usePercentage = stacking === 'percent';
// create a stack for this particular series type
if (stacking) {
@@ -4492,10 +4704,13 @@ function Chart (options, callback) {
if (!stacks[key]) {
stacks[key] = {};
}
- stacks[key][pointX] = {
- total: totalPos,
- cum: totalPos
- };
+
+ // If the StackItem is there, just update the values,
+ // if not, create one first
+ if (!stacks[key][pointX]) {
+ stacks[key][pointX] = new StackItem(options.stackLabels, isNegative, pointX);
+ }
+ stacks[key][pointX].setTotal(totalPos);
}
}
});
@@ -4504,11 +4719,12 @@ function Chart (options, callback) {
// For column, areas and bars, set the minimum automatically to zero
// and prevent that minPadding is added in setScale
if (/(area|column|bar)/.test(serie.type) && !isXAxis) {
- if (dataMin >= 0) {
- dataMin = 0;
+ var threshold = 0; // use series.options.threshold?
+ if (dataMin >= threshold) {
+ dataMin = threshold;
ignoreMinPadding = true;
- } else if (dataMax < 0) {
- dataMax = 0;
+ } else if (dataMax < threshold) {
+ dataMax = threshold;
ignoreMaxPadding = true;
}
}
@@ -4522,7 +4738,7 @@ function Chart (options, callback) {
* Translate from axis value to pixel position on the chart, or back
*
*/
- translate = function(val, backwards, cvsCoord, old) {
+ translate = function(val, backwards, cvsCoord, old, handleLog) {
var sign = 1,
cvsOffset = 0,
localA = old ? oldTransA : transA,
@@ -4546,9 +4762,15 @@ function Chart (options, callback) {
if (reversed) {
val = axisLength - val;
}
- returnValue = val / localA + localMin; // from chart pixel to value
+ returnValue = val / localA + localMin; // from chart pixel to value
+ if (isLog && handleLog) {
+ returnValue = lin2log(returnValue);
+ }
} else { // normal translation
+ if (isLog && handleLog) {
+ val = log2lin(val);
+ }
returnValue = sign * (val - localMin) * localA + cvsOffset; // from value to chart pixel
}
@@ -4568,8 +4790,8 @@ function Chart (options, callback) {
x2,
y2,
translatedValue = translate(value, null, null, old),
- cHeight = old && oldChartHeight || chartHeight,
- cWidth = old && oldChartWidth || chartWidth,
+ cHeight = (old && oldChartHeight) || chartHeight,
+ cWidth = (old && oldChartWidth) || chartWidth,
skip;
x1 = x2 = mathRound(translatedValue + transB);
@@ -4596,12 +4818,13 @@ function Chart (options, callback) {
renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 0);
};
+
/**
* Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
* @param {Number} interval
*/
function normalizeTickInterval(interval, multiples) {
- var normalized;
+ var normalized, i;
// round to a tenfold of 1, 2, 2.5 or 5
magnitude = multiples ? 1 : math.pow(10, mathFloor(math.log(interval) / math.LN10));
@@ -4613,8 +4836,8 @@ function Chart (options, callback) {
//multiples = [1, 2, 2.5, 4, 5, 7.5, 10];
// the allowDecimals option
- if (options.allowDecimals === false) {
- if (magnitude == 1) {
+ if (options.allowDecimals === false || isLog) {
+ if (magnitude === 1) {
multiples = [1, 2, 5, 10];
} else if (magnitude <= 0.1) {
multiples = [1 / magnitude];
@@ -4623,7 +4846,7 @@ function Chart (options, callback) {
}
// normalize the interval to the nearest multiple
- for (var i = 0; i < multiples.length; i++) {
+ for (i = 0; i < multiples.length; i++) {
interval = multiples[i];
if (normalized <= (multiples[i] + (multiples[i+1] || multiples[i])) / 2) {
break;
@@ -4706,7 +4929,7 @@ function Chart (options, callback) {
}
// prevent 2.5 years intervals, though 25, 250 etc. are allowed
- if (interval == oneYear && tickInterval < 5 * interval) {
+ if (interval === oneYear && tickInterval < 5 * interval) {
multiples = [1, 2, 5];
}
@@ -4749,7 +4972,7 @@ function Chart (options, callback) {
}
// week is a special case that runs outside the hierarchy
- if (interval == oneWeek) {
+ if (interval === oneWeek) {
// get start of current week, independent of multitude
minDate[setDate](minDate[getDate]() - minDate[getDay]() +
options.startOfWeek);
@@ -4768,18 +4991,18 @@ function Chart (options, callback) {
tickPositions.push(time);
// if the interval is years, use Date.UTC to increase years
- if (interval == oneYear) {
+ if (interval === oneYear) {
time = makeTime(minYear + i * multitude, 0) / timeFactor;
// if the interval is months, use Date.UTC to increase months
- } else if (interval == oneMonth) {
+ } else if (interval === oneMonth) {
time = makeTime(minYear, minMonth + i * multitude) / timeFactor;
// if we're using global time, the interval is not fixed as it jumps
// one hour at the DST crossover
- } else if (!useUTC && (interval == oneDay || interval == oneWeek)) {
+ } else if (!useUTC && (interval === oneDay || interval === oneWeek)) {
time = makeTime(minYear, minMonth, minDateDate +
- i * multitude * (interval == oneDay ? 1 : 7));
+ i * multitude * (interval === oneDay ? 1 : 7));
// else, the interval is fixed and we use simple addition
} else {
@@ -4802,8 +5025,10 @@ function Chart (options, callback) {
*/
function correctFloat(num) {
var invMag, ret = num;
- if (defined(magnitude)) {
- invMag = (magnitude < 1 ? mathRound(1 / magnitude) : 1) * 10;
+ magnitude = pick(magnitude, math.pow(10, mathFloor(math.log(tickInterval) / math.LN10)));
+
+ if (magnitude < 1) {
+ invMag = mathRound(1 / magnitude) * 10;
ret = mathRound(num * invMag) / invMag;
}
return ret;
@@ -4815,8 +5040,8 @@ function Chart (options, callback) {
function setLinearTickPositions() {
var i,
- roundedMin = mathFloor(min / tickInterval) * tickInterval,
- roundedMax = mathCeil(max / tickInterval) * tickInterval;
+ roundedMin = correctFloat(mathFloor(min / tickInterval) * tickInterval),
+ roundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval);
tickPositions = [];
@@ -4841,7 +5066,7 @@ function Chart (options, callback) {
tickIntervalOption = options.tickInterval,
tickPixelIntervalOption = options.tickPixelInterval,
maxZoom = options.maxZoom || (
- isXAxis ?
+ isXAxis && !defined(options.min) && !defined(options.max) ?
mathMin(chart.smallestInterval * 5, dataMax - dataMin) :
null
),
@@ -4860,8 +5085,13 @@ function Chart (options, callback) {
// initial min and max from the extreme data values
else {
- min = pick(userSetMin, options.min, dataMin);
- max = pick(userSetMax, options.max, dataMax);
+ min = pick(userMin, options.min, dataMin);
+ max = pick(userMax, options.max, dataMax);
+ }
+
+ if (isLog) {
+ min = log2lin(min);
+ max = log2lin(max);
}
// maxZoom exceeded, just center the selection
@@ -4875,19 +5105,19 @@ function Chart (options, callback) {
// pad the values to get clear of the chart's edges
if (!categories && !usePercentage && !isLinked && defined(min) && defined(max)) {
length = (max - min) || 1;
- if (!defined(options.min) && !defined(userSetMin) && minPadding && (dataMin < 0 || !ignoreMinPadding)) {
+ if (!defined(options.min) && !defined(userMin) && minPadding && (dataMin < 0 || !ignoreMinPadding)) {
min -= length * minPadding;
}
- if (!defined(options.max) && !defined(userSetMax) && maxPadding && (dataMax > 0 || !ignoreMaxPadding)) {
+ if (!defined(options.max) && !defined(userMax) && maxPadding && (dataMax > 0 || !ignoreMaxPadding)) {
max += length * maxPadding;
}
}
// get tickInterval
- if (min == max) {
+ if (min === max) {
tickInterval = 1;
} else if (isLinked && !tickIntervalOption &&
- tickPixelIntervalOption == linkedParent.options.tickPixelInterval) {
+ tickPixelIntervalOption === linkedParent.options.tickPixelInterval) {
tickInterval = linkedParent.tickInterval;
} else {
tickInterval = pick(
@@ -4918,10 +5148,10 @@ function Chart (options, callback) {
// pad categorised axis to nearest half unit
if (categories || (isXAxis && chart.hasColumn)) {
catPad = (categories ? 1 : tickInterval) * 0.5;
- if (categories || !defined(pick(options.min, userSetMin))) {
+ if (categories || !defined(pick(options.min, userMin))) {
min -= catPad;
}
- if (categories || !defined(pick(options.max, userSetMax))) {
+ if (categories || !defined(pick(options.max, userMax))) {
max += catPad;
}
}
@@ -4981,7 +5211,7 @@ function Chart (options, callback) {
max = tickPositions[tickPositions.length - 1];
}
- if (defined(oldTickAmount) && tickAmount != oldTickAmount) {
+ if (defined(oldTickAmount) && tickAmount !== oldTickAmount) {
axis.isDirty = true;
}
}
@@ -5020,7 +5250,7 @@ function Chart (options, callback) {
// mark as dirty if it is not already set to dirty and extremes have changed
if (!axis.isDirty) {
- axis.isDirty = (min != oldMin || max != oldMax);
+ axis.isDirty = (min !== oldMin || max !== oldMax);
}
}
@@ -5043,8 +5273,8 @@ function Chart (options, callback) {
max: newMax
}, function() { // the default event handler
- userSetMin = newMin;
- userSetMax = newMax;
+ userMin = newMin;
+ userMax = newMax;
// redraw
@@ -5063,7 +5293,9 @@ function Chart (options, callback) {
min: min,
max: max,
dataMin: dataMin,
- dataMax: dataMax
+ dataMax: dataMax,
+ userMin: userMin,
+ userMax: userMax
};
}
@@ -5102,7 +5334,8 @@ function Chart (options, callback) {
titleMargin = 0,
axisTitleOptions = options.title,
labelOptions = options.labels,
- directionFactor = [-1, 1, 1, -1][side];
+ directionFactor = [-1, 1, 1, -1][side],
+ n;
if (!axisGroup) {
axisGroup = renderer.g('axis')
@@ -5124,7 +5357,7 @@ function Chart (options, callback) {
}
// left side must be align: right and right side must have align: left for labels
- if (side === 0 || side == 2 || { 1: 'left', 3: 'right' }[side] == labelOptions.align) {
+ if (side === 0 || side === 2 || { 1: 'left', 3: 'right' }[side] === labelOptions.align) {
// get the highest offset
labelOffset = mathMax(
@@ -5140,7 +5373,7 @@ function Chart (options, callback) {
}
} else { // doesn't have data
- for (var n in ticks) {
+ for (n in ticks) {
ticks[n].destroy();
delete ticks[n];
}
@@ -5174,7 +5407,7 @@ function Chart (options, callback) {
axisTitleMargin =
labelOffset +
- (side != 2 && labelOffset && directionFactor * options.labels[horiz ? 'y' : 'x']) +
+ (side !== 2 && labelOffset && directionFactor * options.labels[horiz ? 'y' : 'x']) +
titleMargin;
axisOffset[side] = mathMax(
@@ -5189,6 +5422,7 @@ function Chart (options, callback) {
*/
function render() {
var axisTitleOptions = options.title,
+ stackLabelOptions = options.stackLabels,
alternateGridColor = options.alternateGridColor,
lineWidth = options.lineWidth,
lineLeft,
@@ -5289,7 +5523,8 @@ function Chart (options, callback) {
// remove inactive ticks
each([ticks, minorTicks, alternateBands], function(coll) {
- for (var pos in coll) {
+ var pos;
+ for (pos in coll) {
if (!coll[pos].isActive) {
coll[pos].destroy();
delete coll[pos];
@@ -5356,7 +5591,7 @@ function Chart (options, callback) {
(opposite ? -1 : 1) * // so does opposite axes
axisTitleMargin +
//(isIE ? fontSize / 3 : 0)+ // preliminary fix for vml's centerline
- (side == 2 ? fontSize : 0);
+ (side === 2 ? fontSize : 0);
axis.axisTitle[hasRendered ? 'animate' : 'attr']({
x: horiz ?
@@ -5370,6 +5605,33 @@ function Chart (options, callback) {
}
+ // Stacked totals:
+ if (stackLabelOptions && stackLabelOptions.enabled) {
+ var stackKey, oneStack, stackCategory,
+ stackTotalGroup = axis.stackTotalGroup;
+
+ // Create a separate group for the stack total labels
+ if (!stackTotalGroup) {
+ axis.stackTotalGroup = stackTotalGroup =
+ renderer.g('stack-labels')
+ .attr({
+ visibility: VISIBLE,
+ zIndex: 6
+ })
+ .translate(plotLeft, plotTop)
+ .add();
+ }
+
+ // Render each stack total
+ for (stackKey in stacks) {
+ oneStack = stacks[stackKey];
+ for (stackCategory in oneStack) {
+ oneStack[stackCategory].render(stackTotalGroup);
+ }
+ }
+ }
+ // End stacked totals
+
axis.isDirty = false;
}
@@ -5380,7 +5642,7 @@ function Chart (options, callback) {
function removePlotBandOrLine(id) {
var i = plotLinesAndBands.length;
while (i--) {
- if (plotLinesAndBands[i].id == id) {
+ if (plotLinesAndBands[i].id === id) {
plotLinesAndBands[i].destroy();
}
}
@@ -5578,22 +5840,22 @@ function Chart (options, callback) {
items = pThis.points || splat(pThis),
xAxis = items[0].series.xAxis,
x = pThis.x,
- isDateTime = xAxis && xAxis.options.type == 'datetime',
+ isDateTime = xAxis && xAxis.options.type === 'datetime',
useHeader = isString(x) || isDateTime,
series,
s;
// build the header
s = useHeader ?
- ['',
- (isDateTime ? dateFormat('%A, %b %e, %Y', x) : x),
- ' '] : [];
+ ['' +
+ (isDateTime ? dateFormat('%A, %b %e, %Y', x) : x) +
+ ' '] : [];
// build the values
each(items, function(item) {
s.push(item.point.tooltipFormatter(useHeader));
});
- return s.join('');
+ return s.join(' ');
}
/**
@@ -5637,7 +5899,7 @@ function Chart (options, callback) {
// hide previous hoverPoints and set new
if (hoverPoints) {
- each (hoverPoints, function(point) {
+ each(hoverPoints, function(point) {
point.setState();
});
}
@@ -5668,28 +5930,18 @@ function Chart (options, callback) {
pointConfig = [],
tooltipPos = point.tooltipPos,
formatter = options.formatter || defaultFormatter,
- hoverPoints = chart.hoverPoints,
- getConfig = function(point) {
- return {
- series: point.series,
- point: point,
- x: point.category,
- y: point.y,
- percentage: point.percentage,
- total: point.total || point.stackTotal
- };
- };
+ hoverPoints = chart.hoverPoints;
// shared tooltip, array is sent over
if (shared) {
// hide previous hoverPoints and set new
if (hoverPoints) {
- each (hoverPoints, function(point) {
+ each(hoverPoints, function(point) {
point.setState();
});
}
- chart.hoverPoints = point;
+ chart.hoverPoints = point;
each(point, function(item, i) {
/*var series = item.series,
@@ -5701,7 +5953,7 @@ function Chart (options, callback) {
item.setState(HOVER_STATE);
plotY += item.plotY; // for average
- pointConfig.push(getConfig(item));
+ pointConfig.push(item.getLabelConfig());
});
plotX = point[0].plotX;
@@ -5715,7 +5967,7 @@ function Chart (options, callback) {
// single point tooltip
} else {
- textConfig = getConfig(point);
+ textConfig = point.getLabelConfig();
}
text = formatter.call(textConfig);
@@ -5794,7 +6046,8 @@ function Chart (options, callback) {
axis;
while (i--) {
- if (crosshairsOptions[i] && (axis = point.series[i ? 'yAxis' : 'xAxis'])) {
+ axis = point.series[i ? 'yAxis' : 'xAxis'];
+ if (crosshairsOptions[i] && axis) {
path = axis
.getPlotLinePath(point[i ? 'y' : 'x'], 1);
if (crosshairs[i]) {
@@ -5843,15 +6096,20 @@ function Chart (options, callback) {
zoomType = optionsChart.zoomType,
zoomX = /x/.test(zoomType),
zoomY = /y/.test(zoomType),
- zoomHor = zoomX && !inverted || zoomY && inverted,
- zoomVert = zoomY && !inverted || zoomX && inverted;
+ zoomHor = (zoomX && !inverted) || (zoomY && inverted),
+ zoomVert = (zoomY && !inverted) || (zoomX && inverted);
/**
* Add crossbrowser support for chartX and chartY
* @param {Object} e The event object in standard browsers
*/
function normalizeMouseEvent(e) {
- var ePos;
+ var ePos,
+ pageZoomFix = isWebKit && doc.width / doc.documentElement.clientWidth - 1,
+ chartPosLeft,
+ chartPosTop,
+ chartX,
+ chartY;
// common IE normalizing
e = e || win.event;
@@ -5863,25 +6121,36 @@ function Chart (options, callback) {
ePos = e.touches ? e.touches.item(0) : e;
// in certain cases, get mouse position
- if (e.type != 'mousemove' || win.opera) { // only Opera needs position on mouse move, see below
+ if (e.type !== 'mousemove' || win.opera || pageZoomFix) { // only Opera needs position on mouse move, see below
chartPosition = getPosition(container);
+ chartPosLeft = chartPosition.left;
+ chartPosTop = chartPosition.top;
}
-
+
// chartX and chartY
if (isIE) { // IE including IE9 that has chartX but in a different meaning
- e.chartX = e.x;
- e.chartY = e.y;
+ chartX = e.x;
+ chartY = e.y;
} else {
if (ePos.layerX === UNDEFINED) { // Opera and iOS
- e.chartX = ePos.pageX - chartPosition.left;
- e.chartY = ePos.pageY - chartPosition.top;
+ chartX = ePos.pageX - chartPosLeft;
+ chartY = ePos.pageY - chartPosTop;
} else {
- e.chartX = e.layerX;
- e.chartY = e.layerY;
+ chartX = e.layerX;
+ chartY = e.layerY;
}
}
- return e;
+ // correct for page zoom bug in WebKit
+ if (pageZoomFix) {
+ chartX += mathRound((pageZoomFix + 1) * chartPosLeft - chartPosLeft);
+ chartY += mathRound((pageZoomFix + 1) * chartPosTop - chartPosTop);
+ }
+
+ return extend(e, {
+ chartX: chartX,
+ chartY: chartY
+ });
}
/**
@@ -5947,7 +6216,7 @@ function Chart (options, callback) {
}
}
// refresh the tooltip if necessary
- if (points.length && (points[0].plotX != hoverX)) {
+ if (points.length && (points[0].plotX !== hoverX)) {
tooltip.refresh(points);
hoverX = points[0].plotX;
}
@@ -5960,7 +6229,7 @@ function Chart (options, callback) {
point = hoverSeries.tooltipPoints[index];
// a new point is hovered, refresh the tooltip
- if (point && point != hoverPoint) {
+ if (point && point !== hoverPoint) {
// trigger the events
point.onMouseOver();
@@ -6019,18 +6288,24 @@ function Chart (options, callback) {
isHorizontal ?
selectionLeft :
plotHeight - selectionTop - selectionBox.height,
- true
+ true,
+ 0,
+ 0,
+ 1
),
selectionMax = translate(
isHorizontal ?
selectionLeft + selectionBox.width :
plotHeight - selectionTop,
- true
+ true,
+ 0,
+ 0,
+ 1
);
selectionData[isXAxis ? 'xAxis' : 'yAxis'].push({
axis: axis,
- min: mathMin(selectionMin, selectionMax), // for reversed axes
+ min: mathMin(selectionMin, selectionMax), // for reversed axes,
max: mathMax(selectionMin, selectionMax)
});
@@ -6088,7 +6363,7 @@ function Chart (options, callback) {
isOutsidePlot = !isInsidePlot(chartX - plotLeft, chartY - plotTop);
// on touch devices, only trigger click if a handler is defined
- if (hasTouch && e.type == 'touchstart') {
+ if (hasTouch && e.type === 'touchstart') {
if (attr(e.target, 'isTracker')) {
if (!chart.runTrackerClick) {
e.preventDefault();
@@ -6122,13 +6397,13 @@ function Chart (options, callback) {
}
- if (mouseIsDown && e.type != 'touchstart') { // make selection
+ if (mouseIsDown && e.type !== 'touchstart') { // make selection
// determine if the mouse has moved more than 10px
- if ((hasDragged = Math.sqrt(
+ hasDragged = Math.sqrt(
Math.pow(mouseDownX - chartX, 2) +
- Math.pow(mouseDownY - chartY, 2)
- ) > 10)) {
+ Math.pow(mouseDownY - chartY, 2));
+ if (hasDragged > 10) {
// make a selection
if (hasCartesianSeries && (zoomX || zoomY) &&
@@ -6323,7 +6598,7 @@ function Chart (options, callback) {
return;
}
- var horizontal = options.layout == 'horizontal',
+ var horizontal = options.layout === 'horizontal',
symbolWidth = options.symbolWidth,
symbolPadding = options.symbolPadding,
allItems,
@@ -6362,19 +6637,22 @@ function Chart (options, callback) {
legendSymbol = item.legendSymbol,
hiddenColor = itemHiddenStyle.color,
textColor = visible ? options.itemStyle.color : hiddenColor,
- symbolColor = visible ? item.color : hiddenColor;
+ lineColor = visible ? item.color : hiddenColor,
+ symbolAttr = visible ? item.pointAttr[NORMAL_STATE] : {
+ stroke: hiddenColor,
+ fill: hiddenColor
+ };
+
if (legendItem) {
legendItem.css({ fill: textColor });
}
if (legendLine) {
- legendLine.attr({ stroke: symbolColor });
+ legendLine.attr({ stroke: lineColor });
}
if (legendSymbol) {
- legendSymbol.attr({
- stroke: symbolColor,
- fill: symbolColor
- });
+ legendSymbol.attr(symbolAttr);
}
+
}
/**
@@ -6438,11 +6716,12 @@ function Chart (options, callback) {
*/
function positionCheckboxes() {
each(allItems, function(item) {
- var checkbox = item.checkbox;
+ var checkbox = item.checkbox,
+ alignAttr = legendGroup.alignAttr;
if (checkbox) {
css(checkbox, {
- left: (legendGroup.attr('translateX') + item.legendItemWidth + checkbox.x - 40) +PX,
- top: (legendGroup.attr('translateY') + checkbox.y - 11) + PX
+ left: (alignAttr.translateX + item.legendItemWidth + checkbox.x - 40) +PX,
+ top: (alignAttr.translateY + checkbox.y - 11) + PX
});
}
});
@@ -6453,7 +6732,7 @@ function Chart (options, callback) {
* @param {Object} item A series or point
*/
function renderItem(item) {
- var bBox,
+ var bBox,
itemWidth,
legendSymbol,
symbolX,
@@ -6462,8 +6741,9 @@ function Chart (options, callback) {
simpleSymbol,
li = item.legendItem,
series = item.series || item,
- i = allItems.length;
-
+ i = allItems.length,
+ itemOptions = series.options,
+ strokeWidth = (itemOptions && itemOptions.borderWidth) || 0;
if (!li) { // generate it once, later move it
@@ -6502,14 +6782,13 @@ function Chart (options, callback) {
.add(legendGroup);
// draw the line
- if (!simpleSymbol && item.options && item.options.lineWidth) {
- var itemOptions = item.options;
- attribs = {
+ if (!simpleSymbol && itemOptions && itemOptions.lineWidth) {
+ var attrs = {
'stroke-width': itemOptions.lineWidth,
zIndex: 2
};
if (itemOptions.dashStyle) {
- attribs.dashstyle = itemOptions.dashStyle;
+ attrs.dashstyle = itemOptions.dashStyle;
}
item.legendLine = renderer.path([
M,
@@ -6519,13 +6798,13 @@ function Chart (options, callback) {
-symbolPadding,
0
])
- .attr(attribs)
+ .attr(attrs)
.add(legendGroup);
}
// draw a simple symbol
if (simpleSymbol) { // bar|pie|area|column
- //legendLayer.drawRect(
+
legendSymbol = renderer.rect(
(symbolX = -symbolWidth - symbolPadding),
(symbolY = -11),
@@ -6533,28 +6812,27 @@ function Chart (options, callback) {
12,
2
).attr({
- 'stroke-width': 0,
+ //'stroke-width': 0,
zIndex: 3
}).add(legendGroup);
}
// draw the marker
- else if (item.options && item.options.marker && item.options.marker.enabled) {
+ else if (itemOptions && itemOptions.marker && itemOptions.marker.enabled) {
legendSymbol = renderer.symbol(
item.symbol,
(symbolX = -symbolWidth / 2 - symbolPadding),
(symbolY = -4),
- item.options.marker.radius
+ itemOptions.marker.radius
)
- .attr(item.pointAttr[NORMAL_STATE])
+ //.attr(item.pointAttr[NORMAL_STATE])
.attr({ zIndex: 3 })
.add(legendGroup);
-
}
if (legendSymbol) {
- legendSymbol.xOff = symbolX;
- legendSymbol.yOff = symbolY;
+ legendSymbol.xOff = symbolX + (strokeWidth % 2 / 2);
+ legendSymbol.yOff = symbolY + (strokeWidth % 2 / 2);
}
item.legendSymbol = legendSymbol;
@@ -6564,7 +6842,7 @@ function Chart (options, callback) {
// add the HTML checkbox on top
- if (item.options && item.options.showCheckbox) {
+ if (itemOptions && itemOptions.showCheckbox) {
item.checkbox = createElement('input', {
type: 'checkbox',
checked: item.selected,
@@ -6619,7 +6897,7 @@ function Chart (options, callback) {
// add it all to an array to use below
- allItems.push(item);
+ //allItems.push(item);
}
/**
@@ -6633,8 +6911,6 @@ function Chart (options, callback) {
offsetWidth = 0;
lastItemY = 0;
- allItems = [];
-
if (!legendGroup) {
legendGroup = renderer.g('legend')
.attr({ zIndex: 7 })
@@ -6642,26 +6918,36 @@ function Chart (options, callback) {
}
- // add HTML for each series
- if (reversedLegend) {
- series.reverse();
- }
+ // add each series or point
+ allItems = [];
each(series, function(serie) {
- if (!serie.options.showInLegend) {
+ var seriesOptions = serie.options;
+
+ if (!seriesOptions.showInLegend) {
return;
}
// use points or series for the legend item depending on legendType
- var items = (serie.options.legendType == 'point') ?
- serie.data : [serie];
-
- // render all items
- each(items, renderItem);
+ allItems = allItems.concat(seriesOptions.legendType === 'point' ?
+ serie.data :
+ serie
+ );
+
});
- if (reversedLegend) { // restore
- series.reverse();
+
+ // sort by legendIndex
+ allItems.sort(function(a, b) {
+ return (a.options.legendIndex || 0) - (b.options.legendIndex || 0);
+ });
+
+ // reversed legend
+ if (reversedLegend) {
+ allItems.reverse();
}
+ // render the items
+ each(allItems, renderItem);
+
// Draw the border
@@ -6704,7 +6990,7 @@ function Chart (options, callback) {
i = 4;
while(i--) {
prop = props[i];
- if (style[prop] && style[prop] != 'auto') {
+ if (style[prop] && style[prop] !== 'auto') {
options[i < 2 ? 'align' : 'verticalAlign'] = prop;
options[i < 2 ? 'x' : 'y'] = pInt(style[prop]) * (i % 2 ? -1 : 1);
}
@@ -6751,9 +7037,9 @@ function Chart (options, callback) {
// an inverted chart can't take a column series and vice versa
if (hasRendered) {
- if (inverted && type == 'column') {
+ if (inverted && type === 'column') {
typeClass = seriesTypes.bar;
- } else if (!inverted && type == 'bar') {
+ } else if (!inverted && type === 'bar') {
typeClass = seriesTypes.column;
}
}
@@ -6872,7 +7158,7 @@ function Chart (options, callback) {
serie.cleanData();
serie.getSegments();
- if (serie.options.legendType == 'point') {
+ if (serie.options.legendType === 'point') {
redrawLegend = true;
}
}
@@ -7017,14 +7303,14 @@ function Chart (options, callback) {
// search axes
for (i = 0; i < axes.length; i++) {
- if (axes[i].options.id == id) {
+ if (axes[i].options.id === id) {
return axes[i];
}
}
// search series
for (i = 0; i < series.length; i++) {
- if (series[i].options.id == id) {
+ if (series[i].options.id === id) {
return series[i];
}
}
@@ -7033,7 +7319,7 @@ function Chart (options, callback) {
for (i = 0; i < series.length; i++) {
data = series[i].data;
for (j = 0; j < data.length; j++) {
- if (data[j].id == id) {
+ if (data[j].id === id) {
return data[j];
}
}
@@ -7266,8 +7552,8 @@ function Chart (options, callback) {
css(container, { left: 0, top: 0 });
rect = container.getBoundingClientRect();
css(container, {
- left: (-rect.left % 1) + PX,
- top: (-rect.top % 1) + PX
+ left: (-(rect.left - pInt(rect.left))) + PX,
+ top: (-(rect.top - pInt(rect.top))) + PX
});
};
@@ -7303,8 +7589,8 @@ function Chart (options, callback) {
// adjust for title and subtitle
if ((chart.title || chart.subtitle) && !defined(optionsMarginTop)) {
titleOffset = mathMax(
- chart.title && !chartTitleOptions.floating && !chartTitleOptions.verticalAlign && chartTitleOptions.y || 0,
- chart.subtitle && !chartSubtitleOptions.floating && !chartSubtitleOptions.verticalAlign && chartSubtitleOptions.y || 0
+ (chart.title && !chartTitleOptions.floating && !chartTitleOptions.verticalAlign && chartTitleOptions.y) || 0,
+ (chart.subtitle && !chartSubtitleOptions.floating && !chartSubtitleOptions.verticalAlign && chartSubtitleOptions.y) || 0
);
if (titleOffset) {
plotTop = mathMax(plotTop, titleOffset + pick(chartTitleOptions.margin, 15) + spacingTop);
@@ -7312,14 +7598,14 @@ function Chart (options, callback) {
}
// adjust for legend
if (legendOptions.enabled && !legendOptions.floating) {
- if (align == 'right') { // horizontal alignment handled first
+ if (align === 'right') { // horizontal alignment handled first
if (!defined(optionsMarginRight)) {
marginRight = mathMax(
marginRight,
legendWidth - legendX + legendMargin + spacingRight
);
}
- } else if (align == 'left') {
+ } else if (align === 'left') {
if (!defined(optionsMarginLeft)) {
plotLeft = mathMax(
plotLeft,
@@ -7327,7 +7613,7 @@ function Chart (options, callback) {
);
}
- } else if (verticalAlign == 'top') {
+ } else if (verticalAlign === 'top') {
if (!defined(optionsMarginTop)) {
plotTop = mathMax(
plotTop,
@@ -7335,7 +7621,7 @@ function Chart (options, callback) {
);
}
- } else if (verticalAlign == 'bottom') {
+ } else if (verticalAlign === 'bottom') {
if (!defined(optionsMarginBottom)) {
marginBottom = mathMax(
marginBottom,
@@ -7380,7 +7666,7 @@ function Chart (options, callback) {
height = optionsChart.height || renderTo.offsetHeight;
if (width && height) { // means container is display:none
- if (width != containerWidth || height != containerHeight) {
+ if (width !== containerWidth || height !== containerHeight) {
clearTimeout(reflowTimeout);
reflowTimeout = setTimeout(function() {
resize(width, height, false);
@@ -7390,9 +7676,9 @@ function Chart (options, callback) {
containerHeight = height;
}
}
- addEvent(window, 'resize', reflow);
+ addEvent(win, 'resize', reflow);
addEvent(chart, 'destroy', function() {
- removeEvent(window, 'resize', reflow);
+ removeEvent(win, 'resize', reflow);
});
}
@@ -7413,8 +7699,8 @@ function Chart (options, callback) {
oldChartHeight = chartHeight;
oldChartWidth = chartWidth;
- chartWidth = mathRound(width);
- chartHeight = mathRound(height);
+ chart.chartWidth = chartWidth = mathRound(width);
+ chart.chartHeight = chartHeight = mathRound(height);
css(container, {
width: chartWidth + PX,
@@ -7462,7 +7748,7 @@ function Chart (options, callback) {
fireEvent(chart, 'endResize', null, function() {
isResizing -= 1;
});
- }, globalAnimation && globalAnimation.duration || 500);
+ }, (globalAnimation && globalAnimation.duration) || 500);
};
/**
@@ -7746,15 +8032,23 @@ function Chart (options, callback) {
// VML namespaces can't be added until after complete. Listening
// for Perini's doScroll hack is not enough.
- var onreadystatechange = 'onreadystatechange';
- if (!hasSVG && win == win.top && doc.readyState != 'complete') {
- doc.attachEvent(onreadystatechange, function() {
- doc.detachEvent(onreadystatechange, firstRender);
- firstRender();
+ var ONREADYSTATECHANGE = 'onreadystatechange',
+ COMPLETE = 'complete';
+ // Note: in spite of JSLint's complaints, win == win.top is required
+ if (!hasSVG && win == win.top && doc.readyState !== COMPLETE) {
+ doc.attachEvent(ONREADYSTATECHANGE, function() {
+ doc.detachEvent(ONREADYSTATECHANGE, firstRender);
+ if (doc.readyState === COMPLETE) {
+ firstRender();
+ }
});
return;
}
-
+
+ // Set to zero for each new chart
+ colorCounter = 0;
+ symbolCounter = 0;
+
// create the container
getContainer();
@@ -7797,11 +8091,6 @@ function Chart (options, callback) {
// Run chart
-
- // Set to zero for each new chart
- colorCounter = 0;
- symbolCounter = 0;
-
// Destroy the chart and free up memory.
addEvent(win, 'unload', destroy);
@@ -7887,7 +8176,6 @@ function Chart (options, callback) {
// Hook for exporting module
Chart.prototype.callbacks = [];
-
/**
* The Point object and prototype. Inheritable and used as base for PiePoint
*/
@@ -7979,7 +8267,7 @@ Point.prototype = {
series.chart.pointCount--;
- if (point == series.chart.hoverPoint) {
+ if (point === series.chart.hoverPoint) {
point.onMouseOut();
}
series.chart.hoverPoints = null; // remove reference
@@ -8002,8 +8290,23 @@ Point.prototype = {
}
- },
+ },
+ /**
+ * Return the configuration hash needed for the data label and tooltip formatters
+ */
+ getLabelConfig: function() {
+ var point = this;
+ return {
+ x: point.category,
+ y: point.y,
+ series: point.series,
+ point: point,
+ percentage: point.percentage,
+ total: point.total || point.stackTotal
+ };
+ },
+
/**
* Toggle the selection status of a point
* @param {Boolean} selected Whether to select or unselect the point.
@@ -8024,7 +8327,7 @@ Point.prototype = {
// unselect all other points unless Ctrl or Cmd + click
if (!accumulate) {
each(chart.getSelectedPoints(), function (loopPoint) {
- if (loopPoint.selected && loopPoint != point) {
+ if (loopPoint.selected && loopPoint !== point) {
loopPoint.selected = false;
loopPoint.setState(NORMAL_STATE);
loopPoint.firePointEvent('unselect');
@@ -8041,7 +8344,7 @@ Point.prototype = {
hoverPoint = chart.hoverPoint;
// set normal state to previous series
- if (hoverPoint && hoverPoint != point) {
+ if (hoverPoint && hoverPoint !== point) {
hoverPoint.onMouseOut();
}
@@ -8079,27 +8382,10 @@ Point.prototype = {
return ['', (point.name || series.name), ' : ',
(!useHeader ? ('x = '+ (point.name || point.x) + ', ') : ''),
- '', (!useHeader ? 'y = ' : '' ), point.y, ' '].join('');
+ '', (!useHeader ? 'y = ' : '' ), point.y, ' '].join('');
},
- /**
- * Get the formatted text for this point's data label
- *
- * @return {String} The formatted data label pseudo-HTML
- */
- getDataLabelText: function() {
- var point = this;
- return this.series.options.dataLabels.formatter.call({
- x: point.x,
- y: point.y,
- series: point.series,
- point: point,
- percentage: point.percentage,
- total: point.total || point.stackTotal
- });
- },
-
/**
* Update the point with new options (typically x/y data) and optionally redraw the series.
*
@@ -8123,12 +8409,6 @@ Point.prototype = {
point.applyOptions(options);
- if (dataLabel) {
- dataLabel.attr({
- text: point.getDataLabelText()
- })
- }
-
// update visuals
if (isObject(options)) {
series.getAttribs();
@@ -8197,7 +8477,7 @@ Point.prototype = {
}
// add default handler if in selection mode
- if (eventType == 'click' && seriesOptions.allowPointSelect) {
+ if (eventType === 'click' && seriesOptions.allowPointSelect) {
defaultFunction = function (event) {
// Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera
point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
@@ -8243,19 +8523,17 @@ Point.prototype = {
chart = series.chart,
pointAttr = point.pointAttr;
- if (!state) {
- state = NORMAL_STATE; // empty string
- }
+ state = state || NORMAL_STATE; // empty string
if (
// already has this state
- state == point.state ||
+ state === point.state ||
// selected points don't respond to hover
- (point.selected && state != SELECT_STATE) ||
+ (point.selected && state !== SELECT_STATE) ||
// series' state options is disabled
(stateOptions[state] && stateOptions[state].enabled === false) ||
// point marker's state options is disabled
- (state && (stateDisabled || normalDisabled && !markerStateOptions.enabled))
+ (state && (stateDisabled || (normalDisabled && !markerStateOptions.enabled)))
) {
return;
@@ -8347,6 +8625,7 @@ Series.prototype = {
series.getColor();
series.getSymbol();
+
// set the data
series.setData(options.data, false);
@@ -8390,21 +8669,29 @@ Series.prototype = {
// remove points with equal x values
// record the closest distance for calculation of column widths
- for (i = data.length - 1; i >= 0; i--) {
+ /*for (i = data.length - 1; i >= 0; i--) {
if (data[i - 1]) {
if (data[i - 1].x == data[i].x) {
+ data[i - 1].destroy();
data.splice(i - 1, 1); // remove the duplicate
}
-
+ }
+ }*/
+
+ // connect nulls
+ if (series.options.connectNulls) {
+ for (i = data.length - 1; i >= 0; i--) {
+ if (data[i].y === null && data[i - 1] && data[i + 1]) {
+ data.splice(i, 1);
+ }
}
}
-
// find the closes pair of points
for (i = data.length - 1; i >= 0; i--) {
if (data[i - 1]) {
interval = data[i].x - data[i - 1].x;
- if (smallestInterval === UNDEFINED || interval < smallestInterval) {
+ if (interval > 0 && (smallestInterval === UNDEFINED || interval < smallestInterval)) {
smallestInterval = interval;
closestPoints = i;
}
@@ -8433,7 +8720,7 @@ Series.prototype = {
segments.push(data.slice(lastNull + 1, i));
}
lastNull = i;
- } else if (i == data.length - 1) { // last value
+ } else if (i === data.length - 1) { // last value
segments.push(data.slice(lastNull + 1, i + 1));
}
});
@@ -8511,6 +8798,7 @@ Series.prototype = {
if (shift) {
data[0].remove(false);
}
+ series.getAttribs();
// redraw
@@ -8530,7 +8818,7 @@ Series.prototype = {
oldData = series.data,
initialColor = series.initialColor,
chart = series.chart,
- i = oldData && oldData.length || 0;
+ i = (oldData && oldData.length) || 0;
series.xIncrement = null; // reset for new data
if (defined(initialColor)) { // reset colors for pie
@@ -8552,6 +8840,10 @@ Series.prototype = {
series.cleanData();
series.getSegments();
+
+ // cache attributes for shapes
+ series.getAttribs();
+
// redraw
series.isDirty = true;
chart.isDirtyBox = true;
@@ -8627,7 +8919,7 @@ Series.prototype = {
pointStack.cum = yBottom = pointStack.cum - yValue; // start from top
yValue = yBottom + yValue;
- if (stacking == 'percent') {
+ if (stacking === 'percent') {
yBottom = pointStackTotal ? yBottom * 100 / pointStackTotal : 0;
yValue = pointStackTotal ? yValue * 100 / pointStackTotal : 0;
}
@@ -8637,12 +8929,12 @@ Series.prototype = {
}
if (defined(yBottom)) {
- point.yBottom = yAxis.translate(yBottom, 0, 1);
+ point.yBottom = yAxis.translate(yBottom, 0, 1, 0, 1);
}
// set the y value
if (yValue !== null) {
- point.plotY = yAxis.translate(yValue, 0, 1);
+ point.plotY = yAxis.translate(yValue, 0, 1, 0, 1);
}
// set client related positions for mouse tracking
@@ -8687,8 +8979,8 @@ Series.prototype = {
each(data, function(point, i) {
- low = data[i - 1] ? data[i - 1].high + 1 : 0;
- high = point.high = data[i + 1] ? (
+ low = data[i - 1] ? data[i - 1]._high + 1 : 0;
+ high = point._high = data[i + 1] ? (
mathFloor((point.plotX + (data[i + 1] ?
data[i + 1].plotX : plotSize)) / 2)) :
plotSize;
@@ -8716,7 +9008,7 @@ Series.prototype = {
}
// set normal state to previous series
- if (hoverSeries && hoverSeries != series) {
+ if (hoverSeries && hoverSeries !== series) {
hoverSeries.onMouseOut();
}
@@ -8906,7 +9198,8 @@ Series.prototype = {
seriesPointAttr = [],
pointAttr,
pointAttrToOptions = series.pointAttrToOptions,
- hasPointSpecificOptions;
+ hasPointSpecificOptions,
+ key;
// series type specific modifications
if (series.options.marker) { // line, spline, area, areaspline, scatter
@@ -8950,7 +9243,7 @@ Series.prototype = {
// check if the point has specific visual options
if (point.options) {
- for (var key in pointAttrToOptions) {
+ for (key in pointAttrToOptions) {
if (defined(normalOptions[pointAttrToOptions[key]])) {
hasPointSpecificOptions = true;
}
@@ -9036,7 +9329,7 @@ Series.prototype = {
if (series[prop]) {
// issue 134 workaround
- destroy = issue134 && prop == 'group' ?
+ destroy = issue134 && prop === 'group' ?
'hide' :
'destroy';
@@ -9045,7 +9338,7 @@ Series.prototype = {
});
// remove from hoverSeries
- if (chart.hoverSeries == series) {
+ if (chart.hoverSeries === series) {
chart.hoverSeries = null;
}
erase(chart.series, series);
@@ -9071,15 +9364,43 @@ Series.prototype = {
chart = series.chart,
inverted = chart.inverted,
seriesType = series.type,
- color;
-
+ color,
+ stacking = series.options.stacking,
+ isBarLike = seriesType === 'column' || seriesType === 'bar',
+ vAlignIsNull = options.verticalAlign === null,
+ yIsNull = options.y === null;
+
+ if (isBarLike) {
+ if (stacking) {
+ // In stacked series the default label placement is inside the bars
+ if (vAlignIsNull) {
+ options = merge(options, {verticalAlign: 'middle'});
+ }
+
+ // If no y delta is specified, try to create a good default
+ if (yIsNull) {
+ options = merge(options, {y: {top: 14, middle: 4, bottom: -6}[options.verticalAlign]});
+ }
+ } else {
+ // In non stacked series the default label placement is on top of the bars
+ if (vAlignIsNull) {
+ options = merge(options, {verticalAlign: 'top'});
+ }
+
+ // If no y delta is specified, set the default
+ if (yIsNull) {
+ options = merge(options, {y: -6});
+ }
+ }
+ }
+
// create a separate group for the data labels to avoid rotation
if (!dataLabelsGroup) {
dataLabelsGroup = series.dataLabelsGroup =
- chart.renderer.g(PREFIX +'data-labels')
+ chart.renderer.g('data-labels')
.attr({
visibility: series.visible ? VISIBLE : HIDDEN,
- zIndex: 5
+ zIndex: 6
})
.translate(chart.plotLeft, chart.plotTop)
.add();
@@ -9087,7 +9408,7 @@ Series.prototype = {
// determine the color
color = options.color;
- if (color == 'auto') { // 1.0 backwards compatibility
+ if (color === 'auto') { // 1.0 backwards compatibility
color = null;
}
options.style.color = pick(color, series.color);
@@ -9095,27 +9416,35 @@ Series.prototype = {
// make the labels for each point
each(data, function(point, i){
var barX = point.barX,
- plotX = barX && barX + point.barW / 2 || point.plotX || -999,
+ plotX = (barX && barX + point.barW / 2) || point.plotX || -999,
plotY = pick(point.plotY, -999),
dataLabel = point.dataLabel,
align = options.align;
// get the string
- str = point.getDataLabelText();
+ str = options.formatter.call(point.getLabelConfig());
x = (inverted ? chart.plotWidth - plotY : plotX) + options.x;
y = (inverted ? chart.plotHeight - plotX : plotY) + options.y;
// in columns, align the string to the column
- if (seriesType == 'column') {
+ if (seriesType === 'column') {
x += { left: -1, right: 1 }[align] * point.barW / 2 || 0;
}
-
+ // update existing label
if (dataLabel) {
- dataLabel.animate({
- x: x,
- y: y
- });
+ // vertically centered
+ if (inverted && !options.y) {
+ y = y + pInt(dataLabel.styles.lineHeight) * 0.9 - dataLabel.getBBox().height / 2;
+ }
+ dataLabel
+ .attr({
+ text: str
+ }).animate({
+ x: x,
+ y: y
+ });
+ // create new label
} else if (defined(str)) {
dataLabel = point.dataLabel = chart.renderer.text(
str,
@@ -9129,19 +9458,32 @@ Series.prototype = {
})
.css(options.style)
.add(dataLabelsGroup);
+ // vertically centered
+ if (inverted && !options.y) {
+ dataLabel.attr({
+ y: y + pInt(dataLabel.styles.lineHeight) * 0.9 - dataLabel.getBBox().height / 2
+ });
+ }
}
- // vertically centered
- if (inverted && !options.y) {
- dataLabel.attr({
- y: y + parseInt(dataLabel.styles.lineHeight) * 0.9 - dataLabel.getBBox().height / 2
- });
- }
/*if (series.isCartesian) {
dataLabel[chart.isInsidePlot(plotX, plotY) ? 'show' : 'hide']();
}*/
-
+
+ if (isBarLike && series.options.stacking) {
+ var barY = point.barY,
+ barW = point.barW,
+ barH = point.barH;
+
+ dataLabel.align(options, null,
+ {
+ x: inverted ? chart.plotWidth - barY - barH : barX,
+ y: inverted ? chart.plotHeight - barX - barW : barY,
+ width: inverted ? barH : barW,
+ height: inverted ? barW : barH
+ });
+ }
});
}
},
@@ -9217,10 +9559,10 @@ Series.prototype = {
for (i = 0; i < segLength; i++) {
areaSegmentPath.push(segmentPath[i]);
}
- if (segLength == 3) { // for animation from 1 to two points
+ if (segLength === 3) { // for animation from 1 to two points
areaSegmentPath.push(L, segmentPath[1], segmentPath[2]);
}
- if (options.stacking && series.type != 'areaspline') {
+ if (options.stacking && series.type !== 'areaspline') {
// follow stack back. Todo: implement areaspline
for (i = segment.length - 1; i >= 0; i--) {
areaSegmentPath.push(segment[i].plotX, segment[i].yBottom);
@@ -9295,7 +9637,7 @@ Series.prototype = {
options = series.options,
animation = options.animation,
doAnimation = animation && series.animate,
- duration = doAnimation ? animation && animation.duration || 500 : 0,
+ duration = doAnimation ? (animation && animation.duration) || 500 : 0,
clipRect = series.clipRect,
renderer = chart.renderer;
@@ -9344,7 +9686,7 @@ Series.prototype = {
}
// cache attributes for shapes
- series.getAttribs();
+ //series.getAttribs();
// draw the graph if any
if (series.drawGraph) {
@@ -9368,7 +9710,7 @@ Series.prototype = {
setTimeout(function() {
clipRect.isAnimating = false;
group = series.group; // can be destroyed during the timeout
- if (group && clipRect != chart.clipRect && clipRect.renderer) {
+ if (group && clipRect !== chart.clipRect && clipRect.renderer) {
group.clip((series.clipRect = chart.clipRect));
clipRect.destroy();
}
@@ -9428,7 +9770,7 @@ Series.prototype = {
state = state || NORMAL_STATE;
- if (series.state != state) {
+ if (series.state !== state) {
series.state = state;
if (stateOptions[state] && stateOptions[state].enabled === false) {
@@ -9579,10 +9921,10 @@ Series.prototype = {
if (trackerPathLength) {
i = trackerPathLength + 1;
while (i--) {
- if (trackerPath[i] == M) { // extend left side
+ if (trackerPath[i] === M) { // extend left side
trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], L);
}
- if ((i && trackerPath[i] == M) || i == trackerPathLength) { // extend right side
+ if ((i && trackerPath[i] === M) || i === trackerPathLength) { // extend right side
trackerPath.splice(i, 0, L, trackerPath[i - 2] + snap, trackerPath[i - 1]);
}
}
@@ -9610,7 +9952,7 @@ Series.prototype = {
zIndex: 1
})
.on(hasTouch ? 'touchstart' : 'mouseover', function() {
- if (chart.hoverSeries != series) {
+ if (chart.hoverSeries !== series) {
series.onMouseOver();
}
})
@@ -9767,7 +10109,7 @@ var ColumnSeries = extendClass(Series, {
// series affected by a new column
if (chart.hasRendered) {
each(chart.series, function(otherSeries) {
- if (otherSeries.type == series.type) {
+ if (otherSeries.type === series.type) {
otherSeries.isDirty = true;
}
});
@@ -9780,6 +10122,9 @@ var ColumnSeries = extendClass(Series, {
translate: function() {
var series = this,
chart = series.chart,
+ options = series.options,
+ stacking = options.stacking,
+ borderWidth = options.borderWidth,
columnCount = 0,
reversedXAxis = series.xAxis.reversed,
categories = series.xAxis.categories,
@@ -9793,14 +10138,14 @@ var ColumnSeries = extendClass(Series, {
// This is called on every series. Consider moving this logic to a
// chart.orderStacks() function and call it on init, addSeries and removeSeries
each(chart.series, function(otherSeries) {
- if (otherSeries.type == series.type) {
+ if (otherSeries.type === series.type && otherSeries.visible) {
if (otherSeries.options.stacking) {
stackKey = otherSeries.stackKey;
if (stackGroups[stackKey] === UNDEFINED) {
stackGroups[stackKey] = columnCount++;
}
columnIndex = stackGroups[stackKey];
- } else if (otherSeries.visible){
+ } else {
columnIndex = columnCount++;
}
otherSeries.columnIndex = columnIndex;
@@ -9810,12 +10155,11 @@ var ColumnSeries = extendClass(Series, {
// calculate the width and position of each column based on
// the number of column series in the plot, the groupPadding
// and the pointPadding options
- var options = series.options,
- data = series.data,
+ var data = series.data,
closestPoints = series.closestPoints,
categoryWidth = mathAbs(
data[1] ? data[closestPoints].plotX - data[closestPoints - 1].plotX :
- chart.plotSizeX / (categories ? categories.length : 1)
+ chart.plotSizeX / ((categories && categories.length) || 1)
),
groupPadding = categoryWidth * options.groupPadding,
groupWidth = categoryWidth - 2 * groupPadding,
@@ -9831,8 +10175,8 @@ var ColumnSeries = extendClass(Series, {
(reversedXAxis ? -1 : 1),
threshold = options.threshold || 0,
translatedThreshold = series.yAxis.getThreshold(threshold),
- minPointLength = pick(options.minPointLength, 5);
-
+ minPointLength = pick(options.minPointLength, 5);
+
// record the new values
each(data, function(point) {
var plotY = point.plotY,
@@ -9840,7 +10184,14 @@ var ColumnSeries = extendClass(Series, {
barX = point.plotX + pointXOffset,
barY = mathCeil(mathMin(plotY, yBottom)),
barH = mathCeil(mathMax(plotY, yBottom) - barY),
- trackerY;
+ stack = series.yAxis.stacks[(point.y < 0 ? '-' : '') + series.stackKey],
+ trackerY,
+ shapeArgs;
+
+ // Record the offset'ed position and width of the bar to be able to align the stacking total correctly
+ if (stacking && series.visible && stack && stack[point.x]) {
+ stack[point.x].setOffset(pointXOffset, pointWidth);
+ }
// handle options.minPointLength and tracker for small points
if (mathAbs(barH) < minPointLength) {
@@ -9860,14 +10211,23 @@ var ColumnSeries = extendClass(Series, {
barW: pointWidth,
barH: barH
});
+
+ // create shape type and shape args that are reused in drawPoints and drawTracker
point.shapeType = 'rect';
- point.shapeArgs = {
- x: barX,
- y: barY,
- width: pointWidth,
- height: barH,
+ shapeArgs = extend(chart.renderer.Element.prototype.crisp.apply({}, [
+ borderWidth,
+ barX,
+ barY,
+ pointWidth,
+ barH
+ ]), {
r: options.borderRadius
- };
+ });
+ if (borderWidth % 2) { // correct for shorting in crisp method, visible in stacked columns with 1px border
+ shapeArgs.y -= 1;
+ shapeArgs.height += 1;
+ }
+ point.shapeArgs = shapeArgs;
// make small columns responsive to mouse
point.trackerArgs = defined(trackerY) && merge(point.shapeArgs, {
@@ -9902,7 +10262,7 @@ var ColumnSeries = extendClass(Series, {
// draw the columns
each(series.data, function(point) {
var plotY = point.plotY;
- if (plotY !== UNDEFINED && !isNaN(plotY)) {
+ if (plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) {
graphic = point.graphic;
shapeArgs = point.shapeArgs;
if (graphic) { // update
@@ -9937,6 +10297,7 @@ var ColumnSeries = extendClass(Series, {
each(series.data, function(point) {
tracker = point.tracker;
shapeArgs = point.trackerArgs || point.shapeArgs;
+ delete shapeArgs.strokeWidth;
if (point.y !== null) {
if (tracker) {// update
tracker.attr(shapeArgs);
@@ -9952,7 +10313,7 @@ var ColumnSeries = extendClass(Series, {
})
.on(hasTouch ? 'touchstart' : 'mouseover', function(event) {
rel = event.relatedTarget || event.fromElement;
- if (chart.hoverSeries != series && attr(rel, 'isTracker') != trackerLabel) {
+ if (chart.hoverSeries !== series && attr(rel, 'isTracker') !== trackerLabel) {
series.onMouseOver();
}
point.onMouseOver();
@@ -9961,16 +10322,16 @@ var ColumnSeries = extendClass(Series, {
.on('mouseout', function(event) {
if (!series.options.stickyTracking) {
rel = event.relatedTarget || event.toElement;
- if (attr(rel, 'isTracker') != trackerLabel) {
+ if (attr(rel, 'isTracker') !== trackerLabel) {
series.onMouseOut();
}
}
})
.css(css)
- .add(chart.trackerGroup);
+ .add(point.group || chart.trackerGroup); // pies have point group - see issue #118
}
}
- });
+ });
},
@@ -9992,7 +10353,8 @@ var ColumnSeries = extendClass(Series, {
*/
each(data, function(point) {
- var graphic = point.graphic;
+ var graphic = point.graphic,
+ shapeArgs = point.shapeArgs;
if (graphic) {
// start values
@@ -10003,8 +10365,8 @@ var ColumnSeries = extendClass(Series, {
// animate
graphic.animate({
- height: point.barH,
- y: point.barY
+ height: shapeArgs.height,
+ y: shapeArgs.y
}, series.options.animation);
}
});
@@ -10026,7 +10388,7 @@ var ColumnSeries = extendClass(Series, {
// as they are either stacked or grouped
if (chart.hasRendered) {
each(chart.series, function(otherSeries) {
- if (otherSeries.type == series.type) {
+ if (otherSeries.type === series.type) {
otherSeries.isDirty = true;
}
});
@@ -10181,7 +10543,8 @@ var PiePoint = extendClass(Point, {
var point = this,
series = point.series,
chart = series.chart,
- slicedTranslation = point.slicedTranslation;
+ slicedTranslation = point.slicedTranslation,
+ translation;
setAnimation(animation, chart);
@@ -10191,10 +10554,14 @@ var PiePoint = extendClass(Point, {
// if called without an argument, toggle
sliced = point.sliced = defined(sliced) ? sliced : !point.sliced;
- point.group.animate({
+ translation = {
translateX: (sliced ? slicedTranslation[0] : chart.plotLeft),
translateY: (sliced ? slicedTranslation[1] : chart.plotTop)
- });
+ };
+ point.group.animate(translation);
+ if (point.shadowGroup) {
+ point.shadowGroup.animate(translation);
+ }
}
});
@@ -10265,7 +10632,7 @@ var PieSeries = extendClass(Series, {
options = series.options,
slicedOffset = options.slicedOffset,
connectorOffset = slicedOffset + options.borderWidth,
- positions = options.center,
+ positions = options.center.concat([options.size, options.innerSize || 0]),
chart = series.chart,
plotWidth = chart.plotWidth,
plotHeight = chart.plotHeight,
@@ -10282,7 +10649,6 @@ var PieSeries = extendClass(Series, {
labelDistance = options.dataLabels.distance;
// get positions - either an integer or a percentage string must be given
- positions.push(options.size, options.innerSize || 0);
positions = map(positions, function(length, i) {
isPercent = /%$/.test(length);
@@ -10290,6 +10656,7 @@ var PieSeries = extendClass(Series, {
// i == 0: centerX, relative to width
// i == 1: centerY, relative to height
// i == 2: size, relative to smallestSize
+ // i == 4: innerSize, relative to smallestSize
[plotWidth, plotHeight, smallestSize, smallestSize][i] *
pInt(length) / 100:
length;
@@ -10377,7 +10744,7 @@ var PieSeries = extendClass(Series, {
var series = this;
// cache attributes for shapes
- series.getAttribs();
+ //series.getAttribs();
this.drawPoints();
@@ -10406,14 +10773,25 @@ var PieSeries = extendClass(Series, {
//center,
graphic,
group,
+ shadow = series.options.shadow,
+ shadowGroup,
shapeArgs;
+
// draw the slices
each(series.data, function(point) {
graphic = point.graphic;
shapeArgs = point.shapeArgs;
group = point.group;
+ shadowGroup = point.shadowGroup;
+ // put the shadow behind all points
+ if (shadow && !shadowGroup) {
+ shadowGroup = point.shadowGroup = renderer.g('shadow')
+ .attr({ zIndex: 4 })
+ .add();
+ }
+
// create the group the first time
if (!group) {
group = point.group = renderer.g('point')
@@ -10423,7 +10801,10 @@ var PieSeries = extendClass(Series, {
// if the point is sliced, use special translation, else use plot area traslation
groupTranslation = point.sliced ? point.slicedTranslation : [chart.plotLeft, chart.plotTop];
- group.translate(groupTranslation[0], groupTranslation[1])
+ group.translate(groupTranslation[0], groupTranslation[1]);
+ if (shadowGroup) {
+ shadowGroup.translate(groupTranslation[0], groupTranslation[1]);
+ }
// draw the slice
@@ -10436,7 +10817,8 @@ var PieSeries = extendClass(Series, {
point.pointAttr[NORMAL_STATE],
{ 'stroke-linejoin': 'round' }
))
- .add(point.group);
+ .add(point.group)
+ .shadow(shadow, shadowGroup);
}
// detect point specific visibility
@@ -10535,7 +10917,8 @@ var PieSeries = extendClass(Series, {
for (j = 0; j < quarters[i].length; j++) {
point = quarters[i][j];
- if ((dataLabel = point.dataLabel)) {
+ dataLabel = point.dataLabel;
+ if (dataLabel) {
labelPos = point.labelPos;
visibility = VISIBLE;
x = labelPos[0];
@@ -10570,7 +10953,7 @@ var PieSeries = extendClass(Series, {
visibility = HIDDEN;
}
- if (visibility == VISIBLE) {
+ if (visibility === VISIBLE) {
lastY = y;
}
@@ -10581,8 +10964,7 @@ var PieSeries = extendClass(Series, {
.attr({
visibility: visibility,
align: labelPos[6]
- })
- [dataLabel.moved ? 'animate' : 'attr']({
+ })[dataLabel.moved ? 'animate' : 'attr']({
x: x + options.x +
({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0),
y: y + options.y
@@ -10595,7 +10977,7 @@ var PieSeries = extendClass(Series, {
connectorPath = [
M,
- x + (labelPos[6] == 'left' ? 5 : -5), y, // end of the string at the label
+ x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
L,
x, y, // first break, next to the label
L,
@@ -10665,7 +11047,6 @@ win.Highcharts = {
merge: merge,
pick: pick,
extendClass: extendClass,
- version: '2.1.4'
+ version: '2.1.5'
};
-})();
-
+}());
diff --git a/js/jquery/jquery.sortableTable.js b/js/jquery/jquery.sortableTable.js
new file mode 100644
index 0000000000..8fa6eb66c3
--- /dev/null
+++ b/js/jquery/jquery.sortableTable.js
@@ -0,0 +1,262 @@
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * @fileoverview A jquery plugin that allows drag&drop sorting in tables.
+ * Coded because JQuery UI sortable doesn't support tables. Also it has no animation
+ *
+ * @name Sortable Table JQuery plugin
+ *
+ * @requires jQuery
+ *
+ */
+
+/* Options:
+ $('table').sortableTable({
+ ignoreRect: { top, left, width, height } - relative coordinates on each element. If the user clicks
+ in this area, it is not seen as a drag&drop request. Useful for toolbars etc.
+ events: {
+ start: callback function when the user starts dragging
+ drop: callback function after an element has been dropped
+ }
+ })
+*/
+
+/* Commands:
+ $('table').sortableTable('init') - equivalent to $('table').sortableTable()
+ $('table').sortableTable('refresh') - if the table has been changed, refresh correctly assigns all events again
+ $('table').sortableTable('destroy') - removes all events from the table
+*/
+(function($) {
+ jQuery.fn.sortableTable = function(method) {
+
+ var methods = {
+ init : function(options) {
+ var tb = new sortableTableInstance(this, options);
+ tb.init();
+ $(this).data('sortableTable',tb);
+ },
+ refresh : function( ) {
+ $(this).data('sortableTable').refresh();
+ },
+ destroy : function( ) {
+ $(this).data('sortableTable').destroy();
+ },
+ };
+
+ if ( methods[method] ) {
+ return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
+ } else if ( typeof method === 'object' || ! method ) {
+ return methods.init.apply( this, arguments );
+ } else {
+ $.error( 'Method ' + method + ' does not exist on jQuery.sortableTable' );
+ }
+
+ function sortableTableInstance(table, options) {
+ var down = false;
+ var $draggedEl, oldCell, previewMove, id;
+
+ if(!options) options = {};
+
+ /* Mouse handlers on the child elements */
+ var onMouseUp = function(e) {
+ dropAt(e.pageX, e.pageY);
+ }
+
+ var onMouseDown = function(e) {
+ $draggedEl = $(this).children();
+ if($draggedEl.length == 0) return;
+ if(options.ignoreRect && insideRect({x: e.pageX - $draggedEl.offset().left, y: e.pageY - $draggedEl.offset().top}, options.ignoreRect)) return;
+
+ down = true;
+ oldCell = this;
+ //move(e.pageX,e.pageY);
+
+ if(options.events && options.events.start)
+ options.events.start(this);
+
+ return false;
+ }
+
+ var globalMouseMove = function(e) {
+ if(down) {
+ move(e.pageX,e.pageY);
+
+ if(inside($(oldCell), e.pageX, e.pageY)) {
+ if(previewMove != null) {
+ moveTo(previewMove);
+ previewMove = null;
+ }
+ } else
+ $(table).find('td').each(function() {
+ if(inside($(this), e.pageX, e.pageY)) {
+ if($(previewMove).attr('class') != $(this).children().first().attr('class')) {
+ if(previewMove != null) moveTo(previewMove);
+ previewMove = $(this).children().first();
+ if(previewMove.length > 0)
+ moveTo($(previewMove), { pos: {
+ top: $(oldCell).offset().top - $(previewMove).parent().offset().top,
+ left: $(oldCell).offset().left - $(previewMove).parent().offset().left
+ } });
+ }
+
+ return false;
+ }
+ });
+ }
+
+ return false;
+ }
+
+ var globalMouseOut = function() {
+ if(down) {
+ down = false;
+ if(previewMove) moveTo(previewMove);
+ moveTo($draggedEl);
+ previewMove = null;
+ }
+ }
+
+ // Initialize sortable table
+ this.init = function() {
+ id = 1;
+ // Add some required css to each child element in the s
+ $(table).find('td').children().each(function() {
+ // Remove any old occurences of our added draggable-num class
+ $(this).attr('class',$(this).attr('class').replace(/\s*draggable\-\d+/g,''));
+ $(this).addClass('draggable-' + (id++));
+ });
+
+ // Mouse events
+ $(table).find('td').bind('mouseup',onMouseUp);
+ $(table).find('td').bind('mousedown',onMouseDown);
+
+ $(document).mousemove(globalMouseMove);
+ $(document).bind('mouseleave', globalMouseOut);
+ }
+
+ // Call this when the table has been updated
+ this.refresh = function() {
+ this.destroy();
+ this.init();
+ }
+
+ this.destroy = function() {
+ // Add some required css to each child element in the s
+ $(table).find('td').children().each(function() {
+ // Remove any old occurences of our added draggable-num class
+ $(this).attr('class',$(this).attr('class').replace(/\s*draggable\-\d+/g,''));
+ });
+
+ // Mouse events
+ $(table).find('td').unbind('mouseup',onMouseUp)
+ $(table).find('td').unbind('mousedown',onMouseDown);
+
+ $(document).unbind('mousemove',globalMouseMove);
+ $(document).unbind('mouseleave',globalMouseOut);
+ }
+
+ function switchElement(drag, dropTo) {
+ var dragPosDiff = {
+ left: $(drag).children().first().offset().left - $(dropTo).offset().left,
+ top: $(drag).children().first().offset().top - $(dropTo).offset().top
+ };
+
+ var dropPosDiff = null;
+ if($(dropTo).children().length > 0) {
+ dropPosDiff = {
+ left: $(dropTo).children().first().offset().left - $(drag).offset().left,
+ top: $(dropTo).children().first().offset().top - $(drag).offset().top
+ };
+ }
+
+ /* I love you append(). It moves the DOM Elements so gracefully <3 */
+ // Put the element in the way to old place
+ $(drag).append($(dropTo).children().first()).children()
+ .stop(true,true)
+ .bind('mouseup',onMouseUp);
+
+ if(dropPosDiff)
+ $(drag).append($(dropTo).children().first()).children()
+ .css('left',dropPosDiff.left + 'px')
+ .css('top',dropPosDiff.top + 'px');
+
+ // Put our dragged element into the space we just freed up
+ $(dropTo).append($(drag).children().first()).children()
+ .bind('mouseup',onMouseUp)
+ .css('left',dragPosDiff.left + 'px')
+ .css('top',dragPosDiff.top + 'px');
+
+ moveTo($(dropTo).children().first(), { duration: 100 });
+ moveTo($(drag).children().first(), { duration: 100 });
+
+ if(options.events && options.events.drop) {
+ // Drop event. The drag child element is moved into the drop element
+ // and vice versa. So the parameters are switched.
+
+ // Calculate row and column index
+ colIdx = $(dropTo).prevAll().length;
+ rowIdx = $(dropTo).parent().prevAll().length;
+
+ options.events.drop(drag,dropTo, { col: colIdx, row: rowIdx });
+ }
+ }
+
+ function move(x,y) {
+ $draggedEl.offset({
+ top: Math.min($(document).height(), Math.max(0, y - $draggedEl.height()/2)),
+ left: Math.min($(document).width(), Math.max(0, x - $draggedEl.width()/2))
+ });
+ }
+
+ function inside($el, x,y) {
+ var off = $el.offset();
+ return y >= off.top && x >= off.left && x < off.left + $el.width() && y < off.top + $el.height();
+ }
+
+ function insideRect(pos, r) {
+ return pos.y > r.top && pos.x > r.left && pos.y < r.top + r.height && pos.x < r.left + r.width;
+ }
+
+ function dropAt(x,y) {
+ if(!down) return;
+ down = false;
+
+ var switched = false;
+
+ $(table).find('td').each(function() {
+ if($(this).children().first().attr('class') != $(oldCell).children().first().attr('class') && inside($(this), x, y)) {
+ switchElement(oldCell, this);
+ switched = true;
+ return;
+ }
+ });
+
+ if(!switched) {
+ if(previewMove) moveTo(previewMove);
+ moveTo($draggedEl);
+ }
+
+ previewMove = null;
+ }
+
+ function moveTo(elem, opts) {
+ if(!opts) opts = {};
+ if(!opts.pos) opts.pos = { left: 0, top: 0 };
+ if(!opts.duration) opts.duration = 200;
+
+ $(elem).css('position','relative');
+ $(elem).animate({ top: opts.pos.top, left: opts.pos.left }, {
+ duration: opts.duration,
+ complete: function() {
+ if(opts.pos.left == 0 && opts.pos.top == 0) {
+ $(elem)
+ .css('position','')
+ .css('left','')
+ .css('top','');
+ }
+ }
+ });
+ }
+ }
+ }
+
+})( jQuery );
\ No newline at end of file
diff --git a/js/messages.php b/js/messages.php
index 4bf5d7c142..2ab89ddef3 100644
--- a/js/messages.php
+++ b/js/messages.php
@@ -81,11 +81,73 @@ $js_messages['strChartServerTraffic'] = __('Server traffic (in KiB)');
$js_messages['strChartConnections'] = __('Connections since last refresh');
$js_messages['strChartProcesses'] = __('Processes');
$js_messages['strChartConnectionsTitle'] = __('Connections / Processes');
-$js_messages['strChartIssuedQueries'] = __('Issued queries since last refresh');
-$js_messages['strChartIssuedQueriesTitle'] = __('Issued queries');
+/* l10n: Questions is the name of a MySQL Status variable */
+$js_messages['strChartIssuedQueries'] = __('Questions since last refresh');
+/* l10n: Questions is the name of a MySQL Status variable */
+$js_messages['strChartIssuedQueriesTitle'] = __('Questions (executed statements by the server)');
$js_messages['strChartQueryPie'] = __('Query statistics');
+/* server status monitor */
+$js_messages['strSystemCPUUsage'] = __('System CPU Usage');
+$js_messages['strSystemMemory'] = __('System memory');
+$js_messages['strSystemSwap'] = __('System swap');
+$js_messages['strMiB'] = __('MiB');
+$js_messages['strKiB'] = __('KiB');
+
+$js_messages['strAverageLoad'] = __('Average load');
+/* l10n: Questions is the name of a MySQL Status variable */
+$js_messages['strQuestions'] = __('Questions');
+$js_messages['strTraffic'] = __('Traffic');
+$js_messages['strSettings'] = __('Settings');
+$js_messages['strRemoveChart'] = __('Remove chart');
+$js_messages['strEditChart'] = __('Edit labels and series');
+$js_messages['strAddChart'] = __('Add chart to grid');
+$js_messages['strClose'] = __('Close');
+$js_messages['strAddOneSeriesWarning'] = __('Please add at least one variable to the series');
+$js_messages['strNone'] = __('None');
+$js_messages['strResumeMonitor'] = __('Resume monitor');
+$js_messages['strPauseMonitor'] = __('Pause monitor');
+/* Monitor: Instructions Dialog */
+$js_messages['strBothLogOn'] = __('general_log and slow_query_log is enabled.');
+$js_messages['strGenLogOn'] = __('general_log is enabled.');
+$js_messages['strSlowLogOn'] = __('slow_query_log is enabled.');
+$js_messages['strBothLogOff'] = __('slow_query_log and general_log is disabled.');
+$js_messages['strLogOutNotTable'] = __('log_output is not set to TABLE.');
+$js_messages['strLogOutIsTable'] = __('log_output is set to TABLE.');
+$js_messages['strSmallerLongQueryTimeAdvice'] = __('slow_query_log is enabled, but the server logs only queries that take longer than %d seconds. It is advisable to set this long_query_time 0-2 seconds, depending on your system.');
+$js_messages['strLongQueryTimeSet'] = __('long_query_time is set to %d second(s).');
+$js_messages['strSettingsAppliedGlobal'] = __('Following settings will be applied globally and reset to default on server restart:');
+/* l10n: %s is FILE or TABLE */
+$js_messages['strSetLogOutput'] = __('Set log_output to %s');
+/* l10n: Enable in this context means setting a status variable to ON */
+$js_messages['strEnableVar'] = __('Enable %s');
+/* l10n: Disable in this context means setting a status variable to OFF */
+$js_messages['strDisableVar'] = __('Disable %s');
+/* l10n: %d seconds */
+$js_messages['setSetLongQueryTime'] = __('Set long_query_time to %ds');
+$js_messages['strNoSuperUser'] = __('You don\'t have super user rights to change this variables. Please log in as root account or contact your database administrator.');
+$js_messages['strChangeSettings'] = __('Change settings');
+$js_messages['strCurrentSettings'] = __('Current settings');
+
+$js_messages['strChartTitle'] = __('Chart Title');
+/* l10n: As in differential values */
+$js_messages['strDifferential'] = __('Differential');
+$js_messages['strDividedBy'] = __('Divided by %s:');
+
+$js_messages['strSelectedTimeRange'] = __('Selected time range:');
+$js_messages['strGroupInserts'] = __('Group together INSERTs into same table');
+$js_messages['strLogAnalyseInfo'] = __('Choose from which log you want the statistics to be generated from.
Results are grouped by query text.');
+$js_messages['strFromSlowLog'] = __('From slow log');
+$js_messages['strFromGeneralLog'] = __('From general log');
+$js_messages['strAnalysingLogs'] = __('Analysing & loading logs. This may take a while.');
+$js_messages['strCountColumnExplanation'] = __('This columns shows the amount of identical queries that are grouped together. However only the SQL Text is being compared, thus the queries other attributes such as start time may differ.');
+$js_messages['strMoreCountColumnExplanation'] = __('Since grouping of INSERTs queries has been selected, INSERT queries into the same table are also being grouped together, disregarding of the inserted data.');
+$js_messages['strLogDataLoaded'] = __('Log data loaded. Queries executed in this time span:');
+
+$js_messages['strJumpToTable'] = __('Jump to Log table');
+$js_messages['strNoDataFound'] = __('Log analysed, but not data found in this time span.');
+
/* For inline query editing */
$js_messages['strGo'] = __('Go');
$js_messages['strCancel'] = __('Cancel');
diff --git a/js/server_status.js b/js/server_status.js
index af8f93cbc6..2fda29f784 100644
--- a/js/server_status.js
+++ b/js/server_status.js
@@ -40,8 +40,32 @@ $(function() {
},
type: "numeric"
});
-});
+
+ // Popup behaviour
+ $('a[rel="popupLink"]').click( function() {
+ var $link = $(this);
+
+ $('.' + $link.attr('href').substr(1))
+ .show()
+ .offset({ top: $link.offset().top + $link.height() + 5, left: $link.offset().left })
+ .addClass('openedPopup');
+
+ return false;
+ });
+
+ $(document).click( function(event) {
+ $('.openedPopup').each(function() {
+ var $cnt = $(this);
+ var pos = $(this).offset();
+
+ // Hide if the mouseclick is outside the popupcontent
+ if(event.pageX < pos.left || event.pageY < pos.top || event.pageX > pos.left + $cnt.outerWidth() || event.pageY > pos.top + $cnt.outerHeight())
+ $cnt.hide().removeClass('openedPopup');
+ });
+ });
+});
+
$(function() {
// Filters for status variables
var textFilter=null;
@@ -57,6 +81,47 @@ $(function() {
// Holds the current chart instances for each tab
var tabChart = new Object();
+
+ /*** Table sort tooltip ***/
+
+ var $tableSortHint = $('' + 'Click to sort' + '
');
+ $('body').append($tableSortHint);
+
+ $('table.sortable thead th').live('mouseover mouseout',function(e) {
+ if(e.type == 'mouseover') {
+ $tableSortHint
+ .stop(true, true)
+ .css({
+ top: e.clientY + 15,
+ left: e.clientX + 15
+ })
+ .show('fast')
+ .data('shown',true);
+ } else {
+ $tableSortHint
+ .stop(true, true)
+ .hide(300,function() {
+ $(this).data('shown',false);
+ });
+ }
+ });
+
+ $(document).mousemove(function(e) {
+ if($tableSortHint.data('shown') == true)
+ $tableSortHint.css({
+ top: e.clientY + 15,
+ left: e.clientX + 15
+ })
+ });
+
+
+ // Tell highcarts not to use UTC dates (global setting)
+ Highcharts.setOptions({
+ global: {
+ useUTC: false
+ }
+ });
+
$.ajaxSetup({
cache:false
});
@@ -78,19 +143,26 @@ $(function() {
tabStatus[$(this).attr('id')] = 'static';
});
+ // Display button links
+ $('div.buttonlinks').show();
+
// Handles refresh rate changing
- $('.statuslinks select').change(function() {
+ $('.buttonlinks select').change(function() {
var chart=tabChart[$(this).parents('div.ui-tabs-panel').attr('id')];
+
+ // Clear current timeout and set timeout with the new refresh rate
+ clearTimeout(chart_activeTimeouts[chart.options.chart.renderTo]);
+ if(chart.options.realtime.postRequest)
+ chart.options.realtime.postRequest.abort();
+
chart.options.realtime.refreshRate = 1000*parseInt(this.value);
chart.xAxis[0].setExtremes(
- new Date().getTime() - chart.options.realtime.numMaxPoints * chart.options.realtime.refreshRate,
- new Date().getTime() + chart.options.realtime.refreshRate / 4,
+ new Date().getTime() - server_time_diff - chart.options.realtime.numMaxPoints * chart.options.realtime.refreshRate,
+ new Date().getTime() - server_time_diff,
true
);
-
- // Clear current timeout and set timeout with the new refresh rate
- clearTimeout(chart_activeTimeouts[chart.options.chart.renderTo]);
+
chart_activeTimeouts[chart.options.chart.renderTo] = setTimeout(
chart.options.realtime.timeoutCallBack,
chart.options.realtime.refreshRate
@@ -98,7 +170,7 @@ $(function() {
});
// Ajax refresh of variables (always the first element in each tab)
- $('.statuslinks a.tabRefresh').click(function() {
+ $('.buttonlinks a.tabRefresh').click(function() {
// ui-tabs-panel class is added by the jquery tabs feature
var tab=$(this).parents('div.ui-tabs-panel');
var that = this;
@@ -120,7 +192,7 @@ $(function() {
/** Realtime charting of variables **/
// Live traffic charting
- $('.statuslinks a.livetrafficLink').click(function() {
+ $('.buttonlinks a.livetrafficLink').click(function() {
// ui-tabs-panel class is added by the jquery tabs feature
var $tab=$(this).parents('div.ui-tabs-panel');
var tabstat = tabStatus[$tab.attr('id')];
@@ -152,7 +224,7 @@ $(function() {
setupLiveChart($tab,this,settings);
if(tabstat == 'liveconnections')
- $tab.find('.statuslinks a.liveconnectionsLink').html(PMA_messages['strLiveConnChart']);
+ $tab.find('.buttonlinks a.liveconnectionsLink').html(PMA_messages['strLiveConnChart']);
tabStatus[$tab.attr('id')]='livetraffic';
} else {
$(this).html(PMA_messages['strLiveTrafficChart']);
@@ -163,7 +235,7 @@ $(function() {
});
// Live connection/process charting
- $('.statuslinks a.liveconnectionsLink').click(function() {
+ $('.buttonlinks a.liveconnectionsLink').click(function() {
var $tab=$(this).parents('div.ui-tabs-panel');
var tabstat = tabStatus[$tab.attr('id')];
@@ -194,7 +266,7 @@ $(function() {
setupLiveChart($tab,this,settings);
if(tabstat == 'livetraffic')
- $tab.find('.statuslinks a.livetrafficLink').html(PMA_messages['strLiveTrafficChart']);
+ $tab.find('.buttonlinks a.livetrafficLink').html(PMA_messages['strLiveTrafficChart']);
tabStatus[$tab.attr('id')]='liveconnections';
} else {
$(this).html(PMA_messages['strLiveConnChart']);
@@ -205,7 +277,7 @@ $(function() {
});
// Live query statistics
- $('.statuslinks a.livequeriesLink').click(function() {
+ $('.buttonlinks a.livequeriesLink').click(function() {
var $tab = $(this).parents('div.ui-tabs-panel');
var settings = null;
@@ -243,7 +315,7 @@ $(function() {
chart_activeTimeouts[$tab.attr('id')+"_chart_cnt"] = null;
tabChart[$tab.attr('id')].destroy();
// Also reset the select list
- $tab.find('.statuslinks select').get(0).selectedIndex = 2;
+ $tab.find('.buttonlinks select').get(0).selectedIndex = 2;
}
if(! settings.chart) settings.chart = {};
@@ -254,8 +326,8 @@ $(function() {
.after('
');
tabChart[$tab.attr('id')] = PMA_createChart(settings);
$(link).html(PMA_messages['strStaticData']);
- $tab.find('.statuslinks a.tabRefresh').hide();
- $tab.find('.statuslinks .refreshList').show();
+ $tab.find('.buttonlinks a.tabRefresh').hide();
+ $tab.find('.buttonlinks .refreshList').show();
} else {
clearTimeout(chart_activeTimeouts[$tab.attr('id') + "_chart_cnt"]);
chart_activeTimeouts[$tab.attr('id') + "_chart_cnt"]=null;
@@ -263,9 +335,9 @@ $(function() {
$tab.find('div#'+$tab.attr('id') + '_chart_cnt').remove();
tabStatus[$tab.attr('id')]='static';
tabChart[$tab.attr('id')].destroy();
- $tab.find('.statuslinks a.tabRefresh').show();
- $tab.find('.statuslinks select').get(0).selectedIndex=2;
- $tab.find('.statuslinks .refreshList').hide();
+ $tab.find('.buttonlinks a.tabRefresh').show();
+ $tab.find('.buttonlinks select').get(0).selectedIndex=2;
+ $tab.find('.buttonlinks .refreshList').hide();
}
}
@@ -276,10 +348,13 @@ $(function() {
});
$('#filterText').keyup(function(e) {
- if($(this).val().length == 0) textFilter = null;
- else textFilter = new RegExp("(^|_)" + $(this).val(),'i');
+ word = $(this).val().replace('_',' ');
+
+ if(word.length == 0) textFilter = null;
+ else textFilter = new RegExp("(^|_)" + word,'i');
+
+ text = word;
- text = $(this).val();
filterVariables();
});
@@ -303,7 +378,7 @@ $(function() {
// Build query statistics chart
var cdata = new Array();
- $.each(jQuery.parseJSON($('#serverstatusquerieschart').html()),function(key,value) {
+ $.each(jQuery.parseJSON($('#serverstatusquerieschart span').html()),function(key,value) {
cdata.push([key,parseInt(value)]);
});
@@ -364,7 +439,7 @@ $(function() {
});
$('#serverstatusqueriesdetails tr:first th')
- .append(' ');
+ .append(' ');
break;
@@ -378,7 +453,7 @@ $(function() {
});
$('#serverstatusvariables tr:first th')
- .append(' ');
+ .append(' ');
break;
}
@@ -469,5 +544,1055 @@ $(function() {
return pointInfo;
}
+
+
+
+
+ /**** Monitor charting implementation ****/
+ /* Saves the previous ajax response for differential values */
+ var oldChartData = null;
+ // Holds about to created chart
+ var newChart = null;
+ var chartSpacing;
+
+ // Runtime parameter of the monitor
+ var runtime = {
+ // Holds all visible charts in the grid
+ charts: null,
+ // Current max points per chart (needed for auto calculation)
+ gridMaxPoints: 20,
+ // displayed time frame
+ xmin: -1,
+ xmax: -1,
+ // Stores the timeout handler so it can be cleared
+ refreshTimeout: null,
+ // Stores the GET request to refresh the charts
+ refreshRequest: null,
+ // Chart auto increment
+ chartAI: 0,
+ // To play/pause the monitor
+ redrawCharts: false,
+ // Object that contains a list of nodes that need to be retrieved from the server for chart updates
+ dataList: []
+ }
+
+ var monitorSettings = null;
+
+ var defaultMonitorSettings = {
+ columns: 4,
+ chartSize: { width: 295, height: 250 },
+ // Max points in each chart. Settings it to 'auto' sets gridMaxPoints to (chartwidth - 40) / 12
+ gridMaxPoints: 'auto',
+ /* Refresh rate of all grid charts in ms */
+ gridRefresh: 5000
+ }
+
+ // Allows drag and drop rearrange and print/edit icons on charts
+ var editMode = false;
+
+ var presetCharts = {
+ 'cpu-WINNT': {
+ title: PMA_messages['strSystemCPUUsage'],
+ nodes: [{ dataType: 'cpu', name: 'loadavg', unit: '%'}]
+ },
+ 'memory-WINNT': {
+ title: PMA_messages['strSystemMemory'],
+ nodes: [
+ { dataType: 'memory', name: 'MemTotal', valueDivisor: 1024, unit: PMA_messages['strMiB'] },
+ { dataType: 'memory', name: 'MemUsed', valueDivisor: 1024, unit: PMA_messages['strMiB'] },
+ ]
+ },
+ 'swap-WINNT': {
+ title: PMA_messages['strSystemSwap'],
+ nodes: [
+ { dataType: 'memory', name: 'SwapTotal', valueDivisor: 1024, unit: PMA_messages['strMiB'] },
+ { dataType: 'memory', name: 'SwapUsed', valueDivisor: 1024, unit: PMA_messages['strMiB'] },
+ ]
+ },
+ 'cpu-Linux': {
+ title: PMA_messages['strSystemCPUUsage'],
+ nodes: [
+ { dataType: 'cpu',
+ name: PMA_messages['strAverageLoad'],
+ unit: '%',
+ transformFn: function(cur, prev) {
+ if(prev == null) return undefined;
+ var diff_total = cur.busy + cur.idle - (prev.busy + prev.idle);
+ var diff_idle = cur.idle - prev.idle;
+ return 100*(diff_total - diff_idle) / diff_total;
+ }
+ }
+ ]
+ },
+ 'memory-Linux': {
+ title: PMA_messages['strSystemMemory'],
+ nodes: [
+ { dataType: 'memory', name: 'MemUsed', valueDivisor: 1024, unit: PMA_messages['strMiB'] },
+ { dataType: 'memory', name: 'Cached', valueDivisor: 1024, unit: PMA_messages['strMiB'] },
+ { dataType: 'memory', name: 'Buffers', valueDivisor: 1024, unit: PMA_messages['strMiB'] },
+ { dataType: 'memory', name: 'MemFree', valueDivisor: 1024, unit: PMA_messages['strMiB'] },
+ ],
+ settings: {
+ chart: {
+ type: 'area',
+ animation: false
+ },
+ plotOptions: {
+ area: {
+ stacking: 'percent'
+ }
+ }
+ }
+ },
+ 'swap-Linux': {
+ title: PMA_messages['strSystemSwap'],
+ nodes: [
+ { dataType: 'memory', name: 'SwapUsed', valueDivisor: 1024, unit: PMA_messages['strMiB'] },
+ { dataType: 'memory', name: 'SwapCached', valueDivisor: 1024, unit: PMA_messages['strMiB'] },
+ { dataType: 'memory', name: 'SwapFree', valueDivisor: 1024, unit: PMA_messages['strMiB'] },
+ ],
+ settings: {
+ chart: {
+ type: 'area',
+ animation: false
+ },
+ plotOptions: {
+ area: {
+ stacking: 'percent'
+ }
+ }
+ }
+ }
+ }
+
+ // Default setting
+ defaultChartGrid = {
+ 'c0': { title: PMA_messages['strQuestions'],
+ nodes: [{ dataType: 'statusvar', name: 'Questions', display: 'differential' }]
+ },
+ 'c1': {
+ title: PMA_messages['strChartConnectionsTitle'],
+ nodes: [ { dataType: 'statusvar', name: 'Connections', display: 'differential' },
+ { dataType: 'proc', name: 'Processes'} ]
+ },
+ 'c2': {
+ title: PMA_messages['strTraffic'],
+ nodes: [
+ { dataType: 'statusvar', name: 'Bytes_sent', display: 'differential', valueDivisor: 1024, unit: PMA_messages['strKiB'] },
+ { dataType: 'statusvar', name: 'Bytes_received', display: 'differential', valueDivisor: 1024, unit: PMA_messages['strKiB'] }
+ ]
+ }
+ };
+
+ // Server is localhost => We can add cpu/memory/swap
+ if(server_db_isLocal) {
+ defaultChartGrid['c3'] = presetCharts['cpu-' + server_os];
+ defaultChartGrid['c4'] = presetCharts['memory-' + server_os];
+ defaultChartGrid['c5'] = presetCharts['swap-' + server_os];
+ }
+
+ var gridbuttons = {
+ cogButton: {
+ //enabled: true,
+ symbol: 'url(' + pmaThemeImage + 's_cog.png)',
+ x: -36,
+ symbolFill: '#B5C9DF',
+ hoverSymbolFill: '#779ABF',
+ _titleKey: 'settings',
+ menuName: 'gridsettings',
+ menuItems: [{
+ textKey: 'editChart',
+ onclick: function() {
+ alert('tbi');
+ }
+ }, {
+ textKey: 'removeChart',
+ onclick: function() {
+ removeChart(this);
+ }
+ }]
+ }
+ };
+
+ Highcharts.setOptions({
+ lang: {
+ settings: PMA_messages['strSettings'],
+ removeChart: PMA_messages['strRemoveChart'],
+ editChart: PMA_messages['strEditChart']
+ }
+ });
+
+ $('a[href="#rearrangeCharts"], a[href="#endChartEditMode"]').click(function() {
+ editMode = !editMode;
+ if($(this).attr('href') == '#endChartEditMode') editMode = false;
+
+ // Icon graphics have zIndex 19,20 and 21. Let's just hope nothing else has the same zIndex
+ $('table#chartGrid div svg').find('*[zIndex=20], *[zIndex=21], *[zIndex=19]').toggle(editMode)
+
+ $('a[href="#endChartEditMode"]').toggle(editMode);
+
+ if(editMode) {
+ // Close the settings popup
+ $('#statustabs_charting .popupContent').hide().removeClass('openedPopup');
+
+ $("#chartGrid").sortableTable({
+ ignoreRect: {
+ top: 8,
+ left: chartSize().width - 63,
+ width: 54,
+ height: 24
+ },
+ events: {
+ start: function() {
+ // console.log('start.');
+ },
+ // Drop event. The drag child element is moved into the drop element
+ // and vice versa. So the parameters are switched.
+ drop: function(drag, drop, pos) {
+ var dragKey, dropKey, dropRender;
+ var dragRender = $(drag).children().first().attr('id');
+
+ if($(drop).children().length > 0)
+ dropRender = $(drop).children().first().attr('id');
+
+ // Find the charts in the array
+ $.each(runtime.charts, function(key, value) {
+ if(value.chart.options.chart.renderTo == dragRender)
+ dragKey = key;
+ if(dropRender && value.chart.options.chart.renderTo == dropRender)
+ dropKey = key;
+ });
+
+ // Case 1: drag and drop are charts -> Switch keys
+ if(dropKey) {
+ if(dragKey) {
+ dragChart = runtime.charts[dragKey];
+ runtime.charts[dragKey] = runtime.charts[dropKey];
+ runtime.charts[dropKey] = dragChart;
+ } else {
+ // Case 2: drop is a empty cell => just completely rebuild the ids
+ var keys = [];
+ var dropKeyNum = parseInt(dropKey.substr(1));
+ var insertBefore = pos.col + pos.row * monitorSettings.columns;
+ var values = [];
+ var newChartList = {};
+ var c = 0;
+
+ $.each(runtime.charts, function(key, value) {
+ if(key != dropKey)
+ keys.push(key);
+ });
+
+ keys.sort();
+
+ // Rebuilds all ids, with the dragged chart correctly inserted
+ for(var i=0; i put at the end
+ if(insertBefore != -1)
+ newChartList['c' + (c++)] = runtime.charts[dropKey];
+
+ runtime.charts = newChartList;
+ }
+
+ saveMonitor();
+ }
+ }
+ }
+ });
+
+ } else {
+ $("#chartGrid").sortableTable('destroy');
+ saveMonitor(); // Save settings
+ }
+
+ return false;
+ });
+
+ // global settings
+ $('div#statustabs_charting div.popupContent select[name="chartColumns"]').change(function() {
+ monitorSettings.columns = parseInt(this.value);
+
+ var newSize = chartSize();
+
+ // Empty cells should keep their size so you can drop onto them
+ $('table#chartGrid tr td').css('width',newSize.width + 'px');
+
+ /* Reorder all charts that it fills all column cells */
+ var numColumns;
+ var $tr = $('table#chartGrid tr:first');
+ var row=0;
+ while($tr.length != 0) {
+ numColumns = 1;
+ // To many cells in one row => put into next row
+ $tr.find('td').each(function() {
+ if(numColumns > monitorSettings.columns) {
+ if($tr.next().length == 0) $tr.after(' ');
+ $tr.next().prepend($(this));
+ }
+ numColumns++;
+ });
+
+ // To little cells in one row => for each cell to little, move all cells backwards by 1
+ if($tr.next().length > 0) {
+ var cnt = monitorSettings.columns - $tr.find('td').length;
+ for(var i=0; i < cnt; i++) {
+ $tr.append($tr.next().find('td:first'));
+ $tr.nextAll().each(function() {
+ if($(this).next().length != 0)
+ $(this).append($(this).next().find('td:first'));
+ });
+ }
+ }
+
+ $tr = $tr.next();
+ row++;
+ }
+
+ /* Apply new chart size to all charts */
+ $.each(runtime.charts, function(key, value) {
+ value.chart.setSize(
+ newSize.width,
+ newSize.height,
+ false
+ );
+ });
+
+ if(monitorSettings.gridMaxPoints == 'auto')
+ runtime.gridMaxPoints = Math.round((newSize.width - 40) / 12);
+
+ runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
+ runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
+
+ if(editMode)
+ $("#chartGrid").sortableTable('refresh');
+
+ saveMonitor(); // Save settings
+ });
+
+ $('div#statustabs_charting div.popupContent select[name="gridChartRefresh"]').change(function() {
+ monitorSettings.gridRefresh = parseInt(this.value) * 1000;
+ clearTimeout(runtime.refreshTimeout);
+
+ if(runtime.refreshRequest)
+ runtime.refreshRequest.abort();
+
+ runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
+ runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
+
+ $.each(runtime.charts, function(key, value) {
+ value.chart.xAxis[0].setExtremes(runtime.xmin, runtime.xmax, false);
+ });
+
+ runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
+
+ saveMonitor(); // Save settings
+ });
+
+ $('a[href="#addNewChart"]').click(function() {
+ var dlgButtons = { };
+
+ dlgButtons[PMA_messages['strAddChart']] = function() {
+ var type = $('input[name="chartType"]:checked').val();
+
+ if(type == 'cpu' || type == 'memory' || type=='swap')
+ newChart = presetCharts[type + '-' + server_os];
+ else {
+ if(! newChart || ! newChart.nodes || newChart.nodes.length == 0) {
+ alert(PMA_messages['strAddOneSeriesWarning']);
+ return;
+ }
+ }
+
+ newChart.title = $('input[name="chartTitle"]').attr('value');
+ // Add a cloned object to the chart grid
+ addChart($.extend(true, {}, newChart));
+
+ newChart = null;
+
+ saveMonitor(); // Save settings
+
+ $(this).dialog("close");
+ }
+
+ dlgButtons[PMA_messages['strClose']] = function() {
+ newChart = null;
+ $('span#clearSeriesLink').hide();
+ $('#seriesPreview').html('');
+ $(this).dialog("close");
+ }
+
+ $('div#addChartDialog').dialog({
+ width:'auto',
+ height:'auto',
+ buttons: dlgButtons
+ });
+
+ $('div#addChartDialog #seriesPreview').html('' + PMA_messages['strNone'] + ' ');
+
+ return false;
+ });
+
+ $('a[href="#pauseCharts"]').click(function() {
+ runtime.redrawCharts = ! runtime.redrawCharts;
+ if(! runtime.redrawCharts)
+ $(this).html(' ' + PMA_messages['strResumeMonitor']);
+ else {
+ $(this).html(' ' + PMA_messages['strPauseMonitor']);
+ if(runtime.charts == null) {
+ initGrid();
+ $('a[href="#settingsPopup"]').show();
+ }
+ }
+ return false;
+ });
+
+ $('a[href="#monitorInstructionsDialog"]').click(function() {
+ var $dialog = $('div#monitorInstructionsDialog');
+
+ $dialog.dialog({
+ width: 595,
+ height: 'auto'
+ }).find('img.ajaxIcon').show();
+
+ var loadLogVars = function(getvars) {
+ vars = { ajax_request: true, logging_vars: true };
+ if(getvars) $.extend(vars,getvars);
+
+ $.get('server_status.php?' + url_query, vars,
+ function(data) {
+ var logVars = $.parseJSON(data),
+ icon = 's_success.png', msg='', str='';
+
+ if(logVars['general_log'] == 'ON') {
+ if(logVars['slow_query_log'] == 'ON')
+ msg = PMA_messages['strBothLogOn'];
+ else
+ msg = PMA_messages['strGenLogOn'];
+ }
+
+ if(msg.length == 0 && logVars['slow_query_log'] == 'ON') {
+ msg = PMA_messages['strSlowLogOn'];
+ }
+
+ if(msg.length == 0) {
+ icon = 's_error.png';
+ msg = PMA_messages['strBothLogOff'];
+ }
+
+ str = '' + PMA_messages['strCurrentSettings'] + ' ';
+ str += '
' + msg + '
';
+
+ if(logVars['log_output'] != 'TABLE')
+ str += '
' + PMA_messages['strLogOutNotTable'] + '
';
+ else
+ str += '
' + PMA_messages['strLogOutIsTable'] + '
';
+
+ if(logVars['slow_query_log'] == 'ON') {
+ if(logVars['long_query_time'] > 2)
+ str += '
'
+ + $.sprintf(PMA_messages['strSmallerLongQueryTimeAdvice'], logVars['long_query_time'])
+ + '
';
+
+ if(logVars['long_query_time'] < 2)
+ str += '
'
+ + $.sprintf(PMA_messages['strLongQueryTimeSet'], logVars['long_query_time'])
+ + '
';
+ }
+
+ str += '
';
+
+ if(is_superuser) {
+ str += '
Change settings ';
+ str += '';
+
+ $dialog.find('div.monitorUse').toggle(
+ logVars['log_output'] == 'TABLE' && (logVars['slow_query_log'] == 'ON' || logVars['general_log'] == 'ON')
+ );
+
+ $dialog.find('div.ajaxContent').html(str);
+ $dialog.find('img.ajaxIcon').hide();
+ $dialog.find('a.set').click(function() {
+ var nameValue = $(this).attr('href').split('-');
+ loadLogVars({ varName: nameValue[0].substr(1), varValue: nameValue[1]});
+ $dialog.find('img.ajaxIcon').show();
+ });
+ }
+ );
+ }
+
+
+ loadLogVars();
+
+ return false;
+ });
+
+ $('input[name="chartType"]').change(function() {
+ $('#chartVariableSettings').toggle(this.checked && this.value == 'variable');
+ var title = $('input[name="chartTitle"]').attr('value');
+ if(title == PMA_messages['strChartTitle'] || title == $('label[for="'+$('input[name="chartTitle"]').data('lastRadio')+'"]').text()) {
+ $('input[name="chartTitle"]').data('lastRadio',$(this).attr('id'));
+ $('input[name="chartTitle"]').attr('value',$('label[for="'+$(this).attr('id')+'"]').text());
+ }
+
+ });
+
+ $('input[name="useDivisor"]').change(function() {
+ $('span.divisorInput').toggle(this.checked);
+ });
+ $('input[name="useUnit"]').change(function() {
+ $('span.unitInput').toggle(this.checked);
+ });
+
+ $('select[name="varChartList"]').change(function () {
+ if(this.selectedIndex!=0)
+ $('#variableInput').attr('value',this.value);
+ });
+
+ $('a[href="#kibDivisor"]').click(function() {
+ $('input[name="valueDivisor"]').attr('value',1024);
+ $('input[name="valueUnit"]').attr('value',PMA_messages['strKiB']);
+ $('span.unitInput').toggle(true);
+ $('input[name="useUnit"]').prop('checked',true);
+ return false;
+ });
+
+ $('a[href="#mibDivisor"]').click(function() {
+ $('input[name="valueDivisor"]').attr('value',1024*1024);
+ $('input[name="valueUnit"]').attr('value',PMA_messages['strMiB']);
+ $('span.unitInput').toggle(true);
+ $('input[name="useUnit"]').prop('checked',true);
+ return false;
+ });
+
+ $('a[href="#submitClearSeries"]').click(function() {
+ $('#seriesPreview').html('' + PMA_messages['strNone'] + ' ');
+ newChart = null;
+ $('span#clearSeriesLink').hide();
+ });
+
+ $('a[href="#submitAddSeries"]').click(function() {
+ if($('input#variableInput').attr('value').length == 0) return false;
+
+ if(newChart == null) {
+ $('#seriesPreview').html('');
+
+ newChart = {
+ title: $('input[name="chartTitle"]').attr('value'),
+ nodes: []
+ }
+ }
+
+ var serie = {
+ dataType:'statusvar',
+ name: $('input#variableInput').attr('value'),
+ display: $('input[name="differentialValue"]').attr('checked') ? 'differential' : '',
+ };
+
+ if(serie.name == 'Processes') serie.dataType='proc';
+
+ if($('input[name="useDivisor"]').attr('checked'))
+ serie.valueDivisor = parseInt($('input[name="valueDivisor"]').attr('value'));
+
+ if($('input[name="useUnit"]').attr('checked'))
+ serie.unit = $('input[name="valueUnit"]').attr('value');
+
+
+
+ var str = serie.display == 'differential' ? ', ' + PMA_messages['strDifferential'] : '';
+ str += serie.valueDivisor ? (', ' + $.sprintf(PMA_messages['strDividedBy'], serie.valueDivisor)) : '';
+
+ $('#seriesPreview').append('- ' + serie.name + str + ' ');
+
+ newChart.nodes.push(serie);
+
+ $('input#variableInput').attr('value','');
+ $('input[name="differentialValue"]').attr('checked',true);
+ $('input[name="useDivisor"]').attr('checked',false);
+ $('input[name="useUnit"]').attr('checked',false);
+ $('input[name="useDivisor"]').trigger('change');
+ $('input[name="useUnit"]').trigger('change');
+ $('select[name="varChartList"]').get(0).selectedIndex=0;
+
+ $('span#clearSeriesLink').show();
+
+ return false;
+ });
+
+ $("input#variableInput").autocomplete({
+ source: variableNames
+ });
+
+
+ function initGrid() {
+ var settings;
+ var series;
+
+ /* Apply default values & config */
+ if(window.localStorage) {
+ if(window.localStorage['monitorCharts'])
+ runtime.charts = $.parseJSON(window.localStorage['monitorCharts']);
+ if(window.localStorage['monitorSettings'])
+ monitorSettings = $.parseJSON(window.localStorage['monitorSettings']);
+
+ $('a[href="#clearMonitorConfig"]').toggle(runtime.charts != null);
+ }
+
+ if(runtime.charts == null)
+ runtime.charts = defaultChartGrid;
+ if(monitorSettings == null)
+ monitorSettings = defaultMonitorSettings;
+
+ $('select[name="gridChartRefresh"]').attr('value',monitorSettings.gridRefresh / 1000);
+ $('select[name="chartColumns"]').attr('value',monitorSettings.columns);
+
+ if(monitorSettings.gridMaxPoints == 'auto')
+ runtime.gridMaxPoints = Math.round((monitorSettings.chartSize.width - 40) / 12);
+ else
+ runtime.gridMaxPoints = monitorSettings.gridMaxPoints;
+
+ runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
+ runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
+
+ /* Calculate how much spacing there is between each chart */
+ $('table#chartGrid').html(' ');
+ chartSpacing = {
+ width: $('table#chartGrid td:nth-child(2)').offset().left - $('table#chartGrid td:nth-child(1)').offset().left,
+ height: $('table#chartGrid tr:nth-child(2) td:nth-child(2)').offset().top - $('table#chartGrid tr:nth-child(1) td:nth-child(1)').offset().top
+ }
+ $('table#chartGrid').html('');
+
+ /* Add all charts - in correct order */
+ var keys = [];
+ $.each(runtime.charts, function(key, value) {
+ keys.push(key);
+ });
+ keys.sort();
+ for(var i=0; i ');
+ }
+
+ // Empty cells should keep their size so you can drop onto them
+ $('table#chartGrid tr td').css('width',chartSize().width + 'px');
+
+
+ buildRequiredDataList();
+ refreshChartGrid();
+ }
+
+ function chartSize() {
+ var wdt = $('div#logTable').innerWidth() / monitorSettings.columns - (monitorSettings.columns - 1) * chartSpacing.width;
+ return {
+ width: wdt,
+ height: 0.75 * wdt
+ }
+ }
+
+ function addChart(chartObj, initialize) {
+ series = [];
+ for(var j=0; j' + PMA_messages['strSelectedTimeRange']
+ + Highcharts.dateFormat('%H:%M:%S',new Date(min)) + ' - '
+ + Highcharts.dateFormat('%H:%M:%S',new Date(max)) + '
'
+ + ' '
+ + '' + PMA_messages['strGroupInserts'] + ' '
+ + PMA_messages['strLogAnalyseInfo']
+ );
+
+ var dlgBtns = { };
+
+ dlgBtns[PMA_messages['strFromSlowLog']] = function() {
+ loadLogStatistics(
+ { src: 'slow', start: min, end: max, groupInserts: $('input#groupInserts').attr('checked') }
+ );
+
+ $(this).dialog("close");
+ }
+
+ dlgBtns[PMA_messages['strFromGeneralLog']] = function() {
+ loadLogStatistics(
+ { src: 'general', start: min, end: max, groupInserts: $('input#groupInserts').attr('checked') }
+ );
+
+ $(this).dialog("close");
+ }
+
+ $('#logAnalyseDialog').dialog({
+ width: 'auto',
+ height: 'auto',
+ buttons: dlgBtns
+ });
+
+ return false;
+ }
+ }
+ },
+ xAxis: {
+ min: runtime.xmin,
+ max: runtime.xmax
+ },
+
+ yAxis: {
+ title: {
+ text: ''
+ }
+ },
+ tooltip: {
+ formatter: function() {
+ var s = ''+Highcharts.dateFormat('%H:%M:%S', this.x)+' ';
+
+ $.each(this.points, function(i, point) {
+ s += ''+ point.series.name +': '+
+ ((parseInt(point.y) == point.y) ? point.y : Highcharts.numberFormat(this.y, 2)) + ' ' + (point.series.options.unit || '');
+ });
+
+ return s;
+ },
+ shared: true
+ },
+ legend: {
+ enabled: false
+ },
+ series: series,
+ buttons: gridbuttons,
+ title: { text: chartObj.title },
+ };
+
+ if(chartObj.settings)
+ $.extend(true,settings,chartObj.settings);
+
+ if($('#'+settings.chart.renderTo).length==0) {
+ var numCharts = $('table#chartGrid .monitorChart').length;
+
+ if(numCharts == 0 || !( numCharts % monitorSettings.columns))
+ $('table#chartGrid').append(' ');
+
+ $('table#chartGrid tr:last').append('
');
+ }
+
+ chartObj.chart = PMA_createChart(settings);
+ chartObj.numPoints = 0;
+
+ if(initialize != true) {
+ runtime.charts['c'+runtime.chartAI] = chartObj;
+ buildRequiredDataList();
+ }
+
+ // Edit,Print icon only in edit mode
+ $('table#chartGrid div svg').find('*[zIndex=20], *[zIndex=21], *[zIndex=19]').toggle(editMode)
+
+ runtime.chartAI++;
+ }
+
+ function removeChart(chartObj) {
+ var htmlnode = chartObj.options.chart.renderTo;
+ if(! htmlnode ) return;
+
+
+ $.each(runtime.charts, function(key, value) {
+ if(value.chart.options.chart.renderTo == htmlnode) {
+ delete runtime.charts[key];
+ return false;
+ }
+ });
+
+ buildRequiredDataList();
+
+ // Using settimeout() because clicking the remove link fires an onclick event
+ // which throws an error when the chart is destroyed
+ setTimeout(function() {
+ chartObj.destroy();
+ $('li#' + htmlnode).remove();
+ },10);
+
+ saveMonitor(); // Save settings
+ }
+
+ function refreshChartGrid() {
+ /* Send to server */
+ runtime.refreshRequest = $.post('server_status.php?'+url_query, { ajax_request: true, chart_data: 1, type: 'chartgrid', requiredData: $.toJSON(runtime.dataList) },function(data) {
+ var chartData = $.parseJSON(data);
+ var value, i=0;
+ var diff;
+
+ /* Update values in each graph */
+ $.each(runtime.charts, function(orderKey, elem) {
+ var key = elem.chartID;
+ // If newly added chart, we have no data for it yet
+ if(! chartData[key]) return;
+ // Draw all points
+ for(var j=0; j < elem.nodes.length; j++) {
+ value = chartData[key][j].y;
+
+ if(i==0 && j==0) {
+ if(oldChartData==null) diff = chartData.x - runtime.xmax;
+ else diff = parseInt(chartData.x - oldChartData.x);
+
+ runtime.xmin+= diff;
+ runtime.xmax+= diff;
+ }
+
+ elem.chart.xAxis[0].setExtremes(runtime.xmin, runtime.xmax, false);
+
+ if(elem.nodes[j].display == 'differential') {
+ if(oldChartData == null || oldChartData[key] == null) continue;
+ value -= oldChartData[key][j].y;
+ }
+
+ if(elem.nodes[j].valueDivisor)
+ value = value / elem.nodes[j].valueDivisor;
+
+ if(elem.nodes[j].transformFn) {
+ value = elem.nodes[j].transformFn(
+ chartData[key][j],
+ (oldChartData == null) ? null : oldChartData[key][j]
+ );
+ }
+
+ if(value != undefined)
+ elem.chart.series[j].addPoint(
+ { x: chartData.x, y: value },
+ false,
+ elem.numPoints >= runtime.gridMaxPoints
+ );
+ }
+
+ i++;
+
+ runtime.charts[orderKey].numPoints++;
+ if(runtime.redrawCharts)
+ elem.chart.redraw();
+ });
+
+ oldChartData = chartData;
+
+ runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
+ });
+ }
+
+ /* Build list of nodes that need to be retrieved */
+ function buildRequiredDataList() {
+ runtime.dataList = {};
+ // Store an own id, because the property name is subject of reordering, thus destroying our mapping with runtime.charts <=> runtime.dataList
+ var chartID = 0;
+ $.each(runtime.charts, function(key, chart) {
+ runtime.dataList[chartID] = chart.nodes;
+ runtime.charts[key].chartID = chartID;
+ chartID++;
+ });
+ }
+
+ function loadLogStatistics(opts) {
+ var tableStr = '';
+ var logRequest = null;
+ var groupInsert = false;
+
+ if(opts.groupInserts)
+ groupInserts = true;
+
+ $('#loadingLogsDialog').html(PMA_messages['strAnalysingLogs'] + ' ');
+
+ $('#loadingLogsDialog').dialog({
+ width: 'auto',
+ height: 'auto',
+ buttons: {
+ 'Cancel request': function() {
+ if(logRequest != null)
+ logRequest.abort();
+
+ $(this).dialog("close");
+ }
+ }
+ });
+
+
+ var formatValue = function(name, value) {
+ switch(name) {
+ case 'user_host':
+ return value.replace(/(\[.*?\])+/g,'');
+ }
+ return value;
+ }
+
+ logRequest = $.get('server_status.php?'+url_query,
+ { ajax_request: true, log_data: 1, type: opts.src, time_start: Math.round(opts.start / 1000), time_end: Math.round(opts.end / 1000), groupInserts: groupInserts },
+ function(data) {
+ var data = $.parseJSON(data);
+ var rows = data.rows;
+ var cols = new Array();
+
+ if(rows.length != 0) {
+ tableStr = '';
+
+ for(var i=0; i < rows.length; i++) {
+ if(i == 0) {
+ tableStr += '';
+ $.each(rows[0],function(key, value) {
+ cols.push(key);
+ });
+ tableStr += '' + cols.join(' ') + ' ';
+ tableStr += ' ';
+ }
+
+ tableStr += '';
+ for(var j=0; j < cols.length; j++)
+ tableStr += '' + formatValue(cols[j], rows[i][cols[j]]) + ' ';
+ tableStr += ' ';
+ }
+
+ tableStr+='
';
+
+ $('#logTable').html(tableStr);
+
+ // Append a tooltip to the count column, if there exist one
+ if($('#logTable th:last').html() == '#') {
+ $('#logTable th:last').append(' ');
+
+ var qtipContent = PMA_messages['strCountColumnExplanation'];
+ if(groupInserts) qtipContent += '' + PMA_messages['strMoreCountColumnExplanation'] + '
';
+
+ $('img.qroupedQueryInfoIcon').qtip({
+ content: qtipContent,
+ position: {
+ corner: {
+ target: 'bottomMiddle',
+ tooltip: 'topRight'
+ }
+
+ },
+ hide: { delay: 1000 }
+ })
+ }
+
+ $('div#logTable table').tablesorter({
+ sortList: [[0,1]],
+ widgets: ['zebra']
+ });
+
+ $('div#logTable table thead th')
+ .append(' ');
+
+
+ $('#loadingLogsDialog').html('' + PMA_messages['strLogDataLoaded'] + '
');
+ $.each(data.sum, function(key, value) {
+ key = key.charAt(0).toUpperCase() + key.slice(1).toLowerCase();
+ if(key == 'Total') key = '' + key + ' ';
+ $('#loadingLogsDialog').append(key + ': ' + value + ' ');
+ });
+
+ var dlgBtns = {};
+ dlgBtns[PMA_messages['strJumpToTable']] = function() {
+ $(this).dialog("close");
+ $(document).scrollTop($('div#logTable').offset().top);
+ }
+
+ $('#loadingLogsDialog').dialog( "option", "buttons", dlgBtns);
+
+ } else {
+ $('#loadingLogsDialog').html('' + PMA_messages['strNoDataFound'] + '
');
+
+ var dlgBtns = {};
+ dlgBtns[PMA_messages['strClose']] = function() {
+ $(this).dialog("close");
+ }
+
+ $('#loadingLogsDialog').dialog( "option", "buttons", dlgBtns );
+ }
+ }
+ );
+ }
+
+ function saveMonitor() {
+ var gridCopy = {};
+
+ $.each(runtime.charts, function(key, elem) {
+ gridCopy[key] = {};
+ gridCopy[key].nodes = elem.nodes;
+ gridCopy[key].settings = elem.settings;
+ gridCopy[key].title = elem.title;
+ });
+
+ if(window.localStorage) {
+ window.localStorage['monitorCharts'] = $.toJSON(gridCopy);
+ window.localStorage['monitorSettings'] = $.toJSON(monitorSettings);
+ }
+
+ $('a[href="#clearMonitorConfig"]').show();
+ }
+
+ $('a[href="#clearMonitorConfig"]').click(function() {
+ window.localStorage.removeItem('monitorCharts');
+ window.localStorage.removeItem('monitorSettings');
+ $(this).hide();
+ });
});
\ No newline at end of file
diff --git a/js/server_variables.js b/js/server_variables.js
index a2c078811e..e7e469c0e0 100644
--- a/js/server_variables.js
+++ b/js/server_variables.js
@@ -54,9 +54,9 @@ $(function() {
var charWidth;
// Global vars
- editLink = ' '+PMA_messages['strEdit']+' ';
- saveLink = ' '+PMA_messages['strSave']+' ';
- cancelLink = ' '+PMA_messages['strCancel']+' ';
+ editLink = ' '+PMA_messages['strEdit']+' ';
+ saveLink = ' '+PMA_messages['strSave']+' ';
+ cancelLink = ' '+PMA_messages['strCancel']+' ';
$.ajaxSetup({
@@ -129,7 +129,7 @@ $(function() {
$('#filterText').keyup(function(e) {
if($(this).val().length==0) textFilter=null;
- else textFilter = new RegExp("(^| )"+$(this).val(),'i');
+ else textFilter = new RegExp("(^| )"+$(this).val().replace('_',' '),'i');
filterVariables();
});
diff --git a/js/sql.js b/js/sql.js
index 68039f4ad8..d16abb0841 100644
--- a/js/sql.js
+++ b/js/sql.js
@@ -162,6 +162,24 @@ $(document).ready(function() {
$('#table_results').makegrid();
})
+ /**
+ * Attach the {@link refreshgrid} function to a custom event, which will be
+ * triggered manually everytime the table of results is manipulated (e.g., by inline edit)
+ * @memberOf jQuery
+ */
+ $("#sqlqueryresults").live('refreshgrid', function() {
+ $('#table_results').refreshgrid();
+ })
+
+ /**
+ * Attach the {@link makegrid} function to a custom event, which will be
+ * triggered manually everytime the table of results is reloaded
+ * @memberOf jQuery
+ */
+ $("#sqlqueryresults").live('makegrid', function() {
+ $('#table_results').makegrid();
+ })
+
/**
* Attach the {@link refreshgrid} function to a custom event, which will be
* triggered manually everytime the table of results is manipulated (e.g., by inline edit)
diff --git a/js/tbl_chart.js b/js/tbl_chart.js
index 7c870d0a24..3c406d9342 100644
--- a/js/tbl_chart.js
+++ b/js/tbl_chart.js
@@ -3,7 +3,7 @@ var chart_series;
var chart_series_index = -1;
$(document).ready(function() {
- var currentChart=null;
+ var currentChart = null;
var chart_data = jQuery.parseJSON($('#querychart').html());
chart_series = 'columns';
chart_xaxis_idx = $('select[name="chartXAxis"]').attr('value');
@@ -26,9 +26,9 @@ $(document).ready(function() {
var currentSettings = {
chart: {
- type:'line',
- width:$('#resizer').width()-20,
- height:$('#resizer').height()-20
+ type: 'line',
+ width: $('#resizer').width() - 20,
+ height: $('#resizer').height() - 20
},
xAxis: {
title: { text: $('input[name="xaxis_label"]').attr('value') }
@@ -36,7 +36,10 @@ $(document).ready(function() {
yAxis: {
title: { text: $('input[name="yaxis_label"]').attr('value') }
},
- title: { text: $('input[name="chartTitle"]').attr('value'), margin:20 },
+ title: {
+ text: $('input[name="chartTitle"]').attr('value'),
+ margin:20
+ },
plotOptions: {
series: {}
}
@@ -45,11 +48,11 @@ $(document).ready(function() {
$('#querychart').html('');
$('input[name="chartType"]').click(function() {
- currentSettings.chart.type=$(this).attr('value');
+ currentSettings.chart.type = $(this).attr('value');
drawChart();
- if($(this).attr('value')=='bar' || $(this).attr('value')=='column')
+ if($(this).attr('value') == 'bar' || $(this).attr('value') == 'column')
$('span.barStacked').show();
else
$('span.barStacked').hide();
@@ -64,9 +67,9 @@ $(document).ready(function() {
});
$('input[name="chartTitle"]').keyup(function() {
- var title=$(this).attr('value');
- if(title.length==0) title=' ';
- currentChart.setTitle({text: title});
+ var title = $(this).attr('value');
+ if(title.length == 0) title = ' ';
+ currentChart.setTitle({ text: title });
});
$('select[name="chartXAxis"]').change(function() {
@@ -90,10 +93,10 @@ $(document).ready(function() {
});
function drawChart(noAnimation) {
- currentSettings.chart.width=$('#resizer').width()-20;
- currentSettings.chart.height=$('#resizer').height()-20;
+ currentSettings.chart.width = $('#resizer').width() - 20;
+ currentSettings.chart.height = $('#resizer').height() - 20;
- if(currentChart!=null) currentChart.destroy();
+ if(currentChart != null) currentChart.destroy();
if(noAnimation) currentSettings.plotOptions.series.animation = false;
currentChart = PMA_queryChart(chart_data,currentSettings);
@@ -101,22 +104,22 @@ $(document).ready(function() {
}
drawChart();
- $('#querychart').show();
+ $('#querychart').show();
});
function in_array(element,array) {
- for(var i=0; i'+this.series.name+' '+this.point.name+' '+this.y;
@@ -226,11 +230,11 @@ function PMA_queryChart(data,passedSettings) {
}
};
- if(passedSettings.chart.type=='pie')
+ if(passedSettings.chart.type == 'pie')
settings.tooltip.formatter = function() { return ''+columnNames[0]+' '+this.y; }
// Overwrite/Merge default settings with passedsettings
$.extend(true,settings,passedSettings);
- return new Highcharts.Chart(settings);
+ return PMA_createChart(settings);
}
diff --git a/libraries/display_tbl.lib.php b/libraries/display_tbl.lib.php
index e231b8434d..b5457ce27a 100644
--- a/libraries/display_tbl.lib.php
+++ b/libraries/display_tbl.lib.php
@@ -1391,7 +1391,7 @@ function PMA_displayTableBody(&$dt_result, &$is_display, $map, $analyzed_sql) {
$is_field_truncated = false;
//If the previous column had blob data, we need to reset the class
// to $inline_edit_class
- $class = 'data ' . $inline_edit_class . ' ' . $not_null_class . ' ' . $alternating_color_class . ' ' . $relation_class . ' ' . $hide_class;
+ $class = 'data ' . $inline_edit_class . ' ' . $not_null_class . ' ' . $relation_class; //' ' . $alternating_color_class .
// See if this column should get highlight because it's used in the
// where-query.
diff --git a/libraries/sysinfo.lib.php b/libraries/sysinfo.lib.php
new file mode 100644
index 0000000000..7a96397e3b
--- /dev/null
+++ b/libraries/sysinfo.lib.php
@@ -0,0 +1,123 @@
+_wmi = $objLocator->ConnectServer();
+ }
+
+ function loadavg() {
+ $loadavg = "";
+ $sum = 0;
+ $buffer = $this->_getWMI('Win32_Processor', array('LoadPercentage'));
+
+ foreach ($buffer as $load) {
+ $value = $load['LoadPercentage'];
+ $loadavg .= $value.' ';
+ $sum += $value;
+ }
+
+ return array('loadavg' => $sum / count($buffer));
+ }
+
+ private function _getWMI($strClass, $strValue = array()) {
+ $arrData = array();
+ $value = "";
+
+ $objWEBM = $this->_wmi->Get($strClass);
+ $arrProp = $objWEBM->Properties_;
+ $arrWEBMCol = $objWEBM->Instances_();
+ foreach ($arrWEBMCol as $objItem) {
+ if (is_array($arrProp)) {
+ reset($arrProp);
+ }
+ $arrInstance = array();
+ foreach ($arrProp as $propItem) {
+ if ( empty($strValue)) {
+ eval("\$value = \$objItem->".$propItem->Name.";");
+ $arrInstance[$propItem->Name] = trim($value);
+ } else {
+ if (in_array($propItem->Name, $strValue)) {
+ eval("\$value = \$objItem->".$propItem->Name.";");
+ $arrInstance[$propItem->Name] = trim($value);
+ }
+ }
+ }
+ $arrData[] = $arrInstance;
+ }
+ return $arrData;
+ }
+
+
+ function memory() {
+ $buffer = $this->_getWMI("Win32_OperatingSystem", array('TotalVisibleMemorySize', 'FreePhysicalMemory'));
+ $mem = Array();
+ $mem['MemTotal'] = $buffer[0]['TotalVisibleMemorySize'];
+ $mem['MemFree'] = $buffer[0]['FreePhysicalMemory'];
+ $mem['MemUsed'] = $mem['MemTotal'] - $mem['MemFree'];
+
+ $buffer = $this->_getWMI('Win32_PageFileUsage');
+
+ $mem['SwapTotal'] = 0;
+ $mem['SwapUsed'] = 0;
+ $mem['SwapPeak'] = 0;
+
+ foreach ($buffer as $swapdevice) {
+ $mem['SwapTotal'] += $swapdevice['AllocatedBaseSize'] * 1024;
+ $mem['SwapUsed'] += $swapdevice['CurrentUsage'] * 1024;
+ $mem['SwapPeak'] += $swapdevice['PeakUsage'] * 1024;
+ }
+
+ return $mem;
+ }
+}
+
+class Linux {
+ public $os = 'Linux';
+
+ function loadavg() {
+ $buf = file_get_contents('/proc/stat');
+ $nums=preg_split("/\s+/", substr($buf,0,strpos($buf,"\n")));
+ return Array('busy' => $nums[1]+$nums[2]+$nums[3], 'idle' => intval($nums[4]));
+ }
+
+ function memory() {
+ preg_match_all('/^(MemTotal|MemFree|Cached|Buffers|SwapCached|SwapTotal|SwapFree):\s+(.*)\s*kB/im', file_get_contents('/proc/meminfo'), $matches);
+
+ $mem = array_combine( $matches[1], $matches[2] );
+ $mem['MemUsed'] = $mem['MemTotal'] - $mem['MemFree'] - $mem['Cached'] - $mem['Buffers'];
+ $mem['SwapUsed'] = $mem['SwapTotal'] - $mem['SwapFree'] - $mem['SwapCached'];
+
+ foreach($mem as $idx=>$value)
+ $mem[$idx] = intval($value);
+
+ return $mem;
+ }
+}
diff --git a/libraries/tbl_links.inc.php b/libraries/tbl_links.inc.php
index 4008fba535..82c9784a38 100644
--- a/libraries/tbl_links.inc.php
+++ b/libraries/tbl_links.inc.php
@@ -101,7 +101,7 @@ if (!$db_is_information_schema && !PMA_DRIZZLE) {
if (PMA_currentUserHasPrivilege('TRIGGER', $db, $table)) {
$tabs['triggers']['link'] = 'tbl_triggers.php';
$tabs['triggers']['text'] = __('Triggers');
- $tabs['triggers']['icon'] = 'b_triggers.png';
+ $tabs['triggers']['icon'] = 'ic_b_triggers';
}
}
diff --git a/po/phpmyadmin.pot b/po/phpmyadmin.pot
index 213d823405..9a04012064 100644
--- a/po/phpmyadmin.pot
+++ b/po/phpmyadmin.pot
@@ -4936,6 +4936,37 @@ msgstr ""
msgid "Link not found"
msgstr ""
+#: libraries/display_triggers.inc.php:35
+#, possible-php-format
+msgid "Export of trigger %s"
+msgstr ""
+
+#: libraries/display_triggers.inc.php:39
+#, possible-php-format
+msgid "Export of trigger \"%s\""
+msgstr ""
+
+#: libraries/display_triggers.inc.php:47
+#, possible-php-format
+msgid "No trigger with name %s found"
+msgstr ""
+
+#: libraries/display_triggers.inc.php:64 libraries/display_triggers.inc.php:66
+msgid "There are no triggers to display."
+msgstr ""
+
+#: libraries/display_triggers.inc.php:77 server_status.php:800 sql.php:943
+msgid "Time"
+msgstr ""
+
+#: libraries/display_triggers.inc.php:78
+msgid "Event"
+msgstr ""
+
+#: libraries/display_triggers.inc.php:120
+msgid "Add a trigger"
+msgstr ""
+
#: libraries/engines/bdb.lib.php:20 main.php:211
msgid "Version information"
msgstr ""
diff --git a/server_status.php b/server_status.php
index 303a87bfe2..004a536490 100644
--- a/server_status.php
+++ b/server_status.php
@@ -43,6 +43,20 @@ if (isset($_REQUEST['ajax_request']) && $_REQUEST['ajax_request'] == true) {
// Send with correct charset
header('Content-Type: text/html; charset=UTF-8');
+ if (isset($_REQUEST['logging_vars'])) {
+ if(isset($_REQUEST['varName']) && isset($_REQUEST['varValue'])) {
+ $value = PMA_sqlAddslashes($_REQUEST['varValue']);
+ if(!is_numeric($value)) $value="'".$value."'";
+
+ if(! preg_match("/[^a-zA-Z0-9_]+/",$_REQUEST['varName']))
+ PMA_DBI_query('SET GLOBAL '.$_REQUEST['varName'].' = '.$value);
+
+ }
+
+ $loggingVars = PMA_DBI_fetch_result('SHOW GLOBAL VARIABLES WHERE Variable_name IN ("general_log","slow_query_log","long_query_time","log_output")', 0, 1);
+ exit(json_encode($loggingVars));
+ }
+
// real-time charting data
if (isset($_REQUEST['chart_data'])) {
switch($_REQUEST['type']) {
@@ -58,20 +72,24 @@ if (isset($_REQUEST['ajax_request']) && $_REQUEST['ajax_request'] == true) {
);
exit(json_encode($ret));
+
case 'queries':
- $queries = PMA_DBI_fetch_result('SHOW GLOBAL STATUS WHERE Variable_name LIKE "Com_%" AND Value>0', 0, 1);
+ $queries = PMA_DBI_fetch_result('SHOW GLOBAL STATUS WHERE Variable_name LIKE "Com_%" OR Variable_name="Questions" AND Value>0', 0, 1);
cleanDeprecated($queries);
// admin commands are not queries
unset($queries['Com_admin_commands']);
+ $questions = $queries['Questions'];
+ unset($queries['Questions']);
- $sum = array_sum($queries);
+ //$sum=array_sum($queries);
$ret = array(
'x' => microtime(true)*1000,
- 'y' => $sum,
+ 'y' => $questions,
'pointInfo' => $queries
);
exit(json_encode($ret));
+
case 'traffic':
$traffic = PMA_DBI_fetch_result('SHOW GLOBAL STATUS WHERE Variable_name="Bytes_received" OR Variable_name="Bytes_sent"', 0, 1);
@@ -82,7 +100,150 @@ if (isset($_REQUEST['ajax_request']) && $_REQUEST['ajax_request'] == true) {
);
exit(json_encode($ret));
+
+ case 'chartgrid':
+ $ret = json_decode($_REQUEST['requiredData'],true);
+ $statusVars = Array();
+ $sysinfo = $cpuload = $memory = 0;
+
+ foreach($ret as $chart_id=>$chartNodes) {
+ foreach($chartNodes as $node_id=>$node) {
+ switch($node['dataType']) {
+ case 'statusvar':
+ // Some white list filtering
+ if(! preg_match('/[^a-zA-Z_]+/',$node['name']))
+ $statusVars[] = $node['name'];
+ break;
+
+ case 'proc':
+ $result = PMA_DBI_query('SHOW PROCESSLIST');
+ $ret[$chart_id][$node_id]['y'] = PMA_DBI_num_rows($result);
+ break;
+
+ case 'cpu':
+ if(! $sysinfo) {
+ require_once('libraries/sysinfo.lib.php');
+ $sysinfo = getSysInfo();
+ }
+ if(! $cpuload)
+ $cpuload = $sysinfo->loadavg();
+
+ if(PHP_OS == 'Linux') {
+ $ret[$chart_id][$node_id]['idle'] = $cpuload['idle'];
+ $ret[$chart_id][$node_id]['busy'] = $cpuload['busy'];
+ } else
+ $ret[$chart_id][$node_id]['y'] = $cpuload['loadavg'];
+
+ break;
+ case 'memory':
+ if(! $sysinfo) {
+ require_once('libraries/sysinfo.lib.php');
+ $sysinfo = getSysInfo();
+ }
+ if(! $memory)
+ $memory = $sysinfo->memory();
+
+ $ret[$chart_id][$node_id]['y'] = $memory[$node['name']];
+ break;
+ }
+ }
+ }
+
+ $vars = PMA_DBI_fetch_result('SHOW GLOBAL STATUS WHERE Variable_name="' . implode('" OR Variable_name="',$statusVars) . '"', 0, 1);
+
+ foreach($ret as $chart_id=>$chartNodes) {
+ foreach($chartNodes as $node_id=>$node) {
+ if($node['dataType'] == 'statusvar')
+ $ret[$chart_id][$node_id]['y'] = $vars[$node['name']];
+ }
+ }
+
+ $ret['x'] = microtime(true)*1000;
+
+ exit(json_encode($ret));
+ }
+ }
+
+ if(isset($_REQUEST['log_data'])) {
+ $start = intval($_REQUEST['time_start']);
+ $end = intval($_REQUEST['time_end']);
+
+ if($_REQUEST['type'] == 'slow') {
+ $q = 'SELECT SUM(query_time) AS TIME(query_time), SUM(lock_time) as lock_time, '.
+ 'SUM(rows_sent) AS rows_sent, SUM(rows_examined) AS rows_examined, sql_text, COUNT(sql_text) AS \'#\' '.
+ 'FROM `mysql`.`slow_log` WHERE event_time > FROM_UNIXTIME('.$start.') '.
+ 'AND event_time < FROM_UNIXTIME('.$end.') GROUP BY sql_text';
+
+ $result = PMA_DBI_try_query($q);
+
+ $return = array( 'rows' => array(), 'sum' => array());
+ $type = '';
+
+ while ($row = PMA_DBI_fetch_assoc($result)) {
+ $type = substr($row['sql_text'],0,strpos($row['sql_text'],' '));
+ $return['sum'][$type]++;
+ $return['rows'][] = $row;
+ }
+
+ $return['sum']['TOTAL'] = array_sum($return['sum']);
+
+ PMA_DBI_free_result($result);
+
+ exit(json_encode($return));
+ }
+
+ if($_REQUEST['type'] == 'general') {
+ $q = 'SELECT TIME(event_time) as event_time, user_host, thread_id, server_id, argument, count(argument) as \'#\' FROM `mysql`.`general_log` WHERE command_type=\'Query\' '.
+ 'AND event_time > FROM_UNIXTIME('.$start.') AND event_time < FROM_UNIXTIME('.$end.') '.
+ 'AND argument REGEXP \'^(INSERT|SELECT|UPDATE|DELETE)\' GROUP by argument'; // HAVING count > 1';
+
+ $result = PMA_DBI_try_query($q);
+
+ $return = array( 'rows' => array(), 'sum' => array());
+ $type = '';
+ $insertTables = array();
+ $insertTablesFirst = -1;
+ $i = 0;
+
+ while ($row = PMA_DBI_fetch_assoc($result)) {
+ preg_match('/^(\w+)\s/',$row['argument'],$match);
+ $type = strtolower($match[1]);
+ // Ignore undefined index warning, just increase counter by one
+ @$return['sum'][$type]++;
+ if($type=='insert' || $type=='update') {
+ // Group inserts if selected
+ if($type=='insert' && isset($_REQUEST['groupInserts']) && $_REQUEST['groupInserts'] && preg_match('/^INSERT INTO (`|\'|"|)([^\s\\1]+)\\1/i',$row['argument'],$matches)) {
+ $insertTables[$matches[2]]++;
+ if ($insertTables[$matches[2]] > 1) {
+ $return['rows'][$insertTablesFirst]['#'] = $insertTables[$matches[2]];
+
+ // Add a ... to the end of this query to indicate that there's been other queries
+ $return['rows'][$insertTablesFirst]['argument'][strlen($return['rows'][$insertTablesFirst]['argument'])-1] != '.';
+ $return['rows'][$insertTablesFirst]['argument'] .= ' ...';
+
+ // Group this value, thus do not add to the result list
+ continue;
+ } else {
+ $insertTablesFirst = $i;
+ $insertTables[$matches[2]] += $row['#'] - 1;
+ }
+ }
+
+ // Cut off big selects, but append byte count therefor
+ if(strlen($row['argument']) > 180)
+ $row['argument'] = substr($row['argument'],0,160) . '... [' .
+ PMA_formatByteDown(strlen($row['argument']), 2).']';
+ }
+ $return['rows'][] = $row;
+ $i++;
+ }
+
+ $return['sum']['TOTAL'] = array_sum($return['sum']);
+
+ PMA_DBI_free_result($result);
+
+ exit(json_encode($return));
}
}
}
@@ -102,6 +263,10 @@ $GLOBALS['js_include'][] = 'server_status.js';
$GLOBALS['js_include'][] = 'jquery/jquery-ui-1.8.custom.js';
$GLOBALS['js_include'][] = 'jquery/jquery.tablesorter.js';
$GLOBALS['js_include'][] = 'jquery/jquery.cookie.js'; // For tab persistence
+$GLOBALS['js_include'][] = 'jquery/jquery.json-2.2.js';
+$GLOBALS['js_include'][] = 'jquery/jquery.sprintf.js';
+$GLOBALS['js_include'][] = 'jquery/jquery.sortableTable.js';
+// Charting
$GLOBALS['js_include'][] = 'highcharts/highcharts.js';
/* Files required for chart exporting */
$GLOBALS['js_include'][] = 'highcharts/exporting.js';
@@ -109,7 +274,6 @@ $GLOBALS['js_include'][] = 'canvg/flashcanvas.js';
$GLOBALS['js_include'][] = 'canvg/canvg.js';
$GLOBALS['js_include'][] = 'canvg/rgbcolor.js';
-
/**
* flush status variables if requested
*/
@@ -377,15 +541,23 @@ require './libraries/server_common.inc.php';
*/
require './libraries/server_links.inc.php';
+$server = 1;
+if(isset($_REQUEST['server']) && intval($_REQUEST['server'])) $server = intval($_REQUEST['server']);
+
+$server_db_isLocal = strtolower($cfg['Servers'][$server]['host']) == 'localhost'
+ || $cfg['Servers'][$server]['host'] == '127.0.0.1';
+
?>
+
-
+
@@ -430,16 +599,14 @@ echo __('Runtime Information');
-
+
-
+
-
+
$section_links) {
echo ' ';
@@ -505,6 +672,10 @@ echo __('Runtime Information');
+
+
+
+
@@ -519,28 +690,30 @@ function printQueryStatistics() {
?>
-
';
-
- echo 'ø'.__('per minute').':';
- echo PMA_formatNumber( $total_queries * 60 / $server_status['Uptime'], 0);
+
+ echo 'ø '.__('per minute').': ';
+ echo PMA_formatNumber( $total_queries * 60 / $server_status['Uptime'], 0);
echo ' ';
-
- if ($total_queries / $server_status['Uptime'] >= 1) {
- echo 'ø'.__('per second').':';
- echo PMA_formatNumber( $total_queries / $server_status['Uptime'], 0);
+
+ if($total_queries / $server_status['Uptime'] >= 1) {
+ echo 'ø '.__('per second').': ';
+ echo PMA_formatNumber( $total_queries / $server_status['Uptime'], 0);
+ }
?>
-
+
-
+
6)
$other_sum += $value;
else $chart_json[$name] = $value;
?>
@@ -597,12 +771,14 @@ function printQueryStatistics() {
+
0)
$chart_json[__('Other')] = $other_sum;
echo json_encode($chart_json);
?>
+
-
+
@@ -638,7 +814,7 @@ function printServerTraffic() {
';
+ echo '
';
if ($server_master_status && $server_slave_status) {
echo __('This MySQL server works as master and slave in replication process.');
} elseif ($server_master_status) {
@@ -853,6 +1029,7 @@ function printVariablesTable() {
* Messages are built using the message name
*/
$strShowStatus = array(
+ 'Aborted_clients' => __('The number of connections that were aborted because the client died without closing the connection properly.'),
'Aborted_connects' => __('The number of failed attempts to connect to the MySQL server.'),
'Binlog_cache_disk_use' => __('The number of transactions that used the temporary binary log cache but that exceeded the value of binlog_cache_size and used a temporary file to store statements from the transaction.'),
'Binlog_cache_use' => __('The number of transactions that used the temporary binary log cache.'),
@@ -1038,7 +1215,7 @@ function printVariablesTable() {
$odd_row = !$odd_row;
?>
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Using the monitor:
+ Ok, you are good to go! Once you click \'Start monitor\' your browser will refresh all displayed charts
+ in a regular interval. You may add charts and change the refresh rate under \'Settings\', or remove any chart
+ using the cog icon on each respective chart.
+
When you get to see a sudden spike in activity, select the relevant time span on any chart by holding down the
+ left mouse button and panning over the chart. This will load statistics from the logs helping you find what caused the
+ activity spike.
+
Please note:
+ Enabling the general_log may increase the server load by up to 5-15%. Also be aware that generating statistics out of the logs is a
+ very load intensive task, thus it is advisable to select only a small time span.
+
'); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ list for refresh rates */
+function refreshList($name,$defaultRate=5, $refreshRates=Array(1, 2, 5, 10, 20, 40, 60, 120, 300, 600)) {
+?>
+
+ '.sprintf(_ngettext('%d second', '%d seconds', $rate), $rate).'';
+ else
+ echo ''.sprintf(_ngettext('%d minute', '%d minutes', $rate/60), $rate/60).' ';
+ }
+ ?>
+
+ true,
@@ -92,7 +92,6 @@ $serverVars = PMA_DBI_fetch_result('SHOW GLOBAL VARIABLES;', 0, 1);
diff --git a/themes/original/img/col_pointer_ver.png b/themes/original/img/col_pointer_ver.png
new file mode 100644
index 0000000000..46079124d0
Binary files /dev/null and b/themes/original/img/col_pointer_ver.png differ
diff --git a/themes/original/img/s_process.png b/themes/original/img/s_cog.png
similarity index 100%
rename from themes/original/img/s_process.png
rename to themes/original/img/s_cog.png
diff --git a/themes/original/jquery/jquery-ui-1.8.custom.css b/themes/original/jquery/jquery-ui-1.8.custom.css
index f9cc5b7807..bc1fb0e7a0 100644
--- a/themes/original/jquery/jquery-ui-1.8.custom.css
+++ b/themes/original/jquery/jquery-ui-1.8.custom.css
@@ -419,6 +419,8 @@ button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra pad
.ui-tabs .ui-tabs-hide {
position: absolute;
left: -10000px;
+ /* required so that overflowing content doesn't cause scrolling */
+ top: -10000px;
}
/* Datepicker
----------------------------------*/
diff --git a/themes/pmahomme/css/theme_right.css.php b/themes/pmahomme/css/theme_right.css.php
index a224e1404f..95f5cd67d8 100644
--- a/themes/pmahomme/css/theme_right.css.php
+++ b/themes/pmahomme/css/theme_right.css.php
@@ -1185,9 +1185,10 @@ th.headerSortDown img.sortableIcon, th.headerSortDown img.sortableIcon {
background-image:url(getImgPath(); ?>s_asc.png);
}
-.statuslinks {
+.buttonlinks {
float: ;
white-space: nowrap;
+ display: none; /* Made visible with js */
}
/* Also used for the variables page */
@@ -1210,10 +1211,14 @@ div#serverstatusquerieschart {
padding-: 30px;
}
-div#serverstatus table#serverstatusqueriesdetails {
+table#serverstatusqueriesdetails, table#serverstatustraffic {
float: ;
}
+table#serverstatusqueriesdetails th {
+ min-width: 35px;
+}
+
.clearfloat {
clear: both;
}
@@ -1228,9 +1233,6 @@ table#serverstatusvariables .name {
table#serverstatusvariables .value {
width: 6em;
}
-table#serverstatustraffic {
- float: ;
-}
table#serverstatusconnections {
float: ;
margin-: 30px;
@@ -1247,6 +1249,52 @@ div.liveChart {
height:400px;
padding-bottom:80px;
}
+
+#addChartDialog input[type="text"] {
+ margin:0px;
+ padding:3px;
+}
+
+div#chartVariableSettings {
+ border:1px solid #ddd;
+ background-color:#E6E6E6;
+ margin-left:10px;
+}
+
+table#chartGrid div.monitorChart {
+ background: #EBEBEB;
+}
+
+div#statustabs_charting div.monitorLinks {
+ float:;
+}
+
+.popupContent {
+ display: none;
+ position: absolute;
+ border: 1px solid #CCC;
+ margin:0;
+ padding:3px;
+ -moz-box-shadow: 1px 1px 6px #ddd;
+ -webkit-box-shadow: 2px 2px 3px #666;
+ box-shadow: 2px 2px 3px #666;
+ background-color:white;
+ z-index: 2;
+}
+
+div#logTable {
+ padding-top: 10px;
+ clear: both;
+}
+
+div#logTable table {
+ width:100%;
+}
+
+.smallIndent {
+ padding-left: 7px;
+}
+
/* end serverstatus */
/* server variables */
@@ -1257,23 +1305,23 @@ a.editLink {
}
table.serverVariableEditTable {
- border:0;
- margin:0;
- padding:0;
- width:100%;
+ border:0;
+ margin:0;
+ padding:0;
+ width:100%;
}
table.serverVariableEditTable td {
- border:0;
- margin:0;
- padding:0;
+ border:0;
+ margin:0;
+ padding:0;
}
table.serverVariableEditTable td:first-child {
- white-space:nowrap;
- vertical-align:middle;
+ white-space:nowrap;
+ vertical-align:middle;
}
table.serverVariableEditTable input {
- width:95%;
+ width:95%;
}
table#serverVariables td {
@@ -1282,6 +1330,37 @@ table#serverVariables td {
/* end server variables */
+
+p.notice {
+ margin: 1.5em 0px;
+ border: 1px solid #000;
+
+ background-repeat: no-repeat;
+
+ background-position: 10px 50%;
+ padding: 10px 10px 10px 25px;
+
+ background-position: 99% 50%;
+ padding: 25px 10px 10px 10px
+
+
+ padding: 0.3em;
+
+ -moz-border-radius:5px;
+ -webkit-border-radius:5px;
+ border-radius:5px;
+ -moz-box-shadow: 0px 1px 2px #fff inset;
+ -webkit-box-shadow: 0px 1px 2px #fff inset;
+ box-shadow:0px 1px 2px #fff; inset;
+ background:#555;
+ color:#d4fb6a;
+}
+
+p.notice a {
+ color:#fff;
+ text-decoration:underline;
+}
+
/* querywindow */
body#bodyquerywindow {
margin: 0;
@@ -1326,32 +1405,6 @@ div#profilingchart {
#togglequerybox{margin:0 10px}
-#serverstatus p {
-
- margin: 1.5em 0px;
- border: 1px solid #000;
-
- background-repeat: no-repeat;
-
- background-position: 10px 50%;
- padding: 10px 10px 10px 25px;
-
- background-position: 99% 50%;
- padding: 25px 10px 10px 10px
-
-
- padding: 0.3em;
-
- -moz-border-radius:5px;
- -webkit-border-radius:5px;
- border-radius:5px;
- -moz-box-shadow: 0px 1px 2px #fff inset;
- -webkit-box-shadow: 0px 1px 2px #fff inset;
- box-shadow:0px 1px 2px #fff; inset;
- background:#555;
- color:#d4fb6a;
-}
-#serverstatus p a{color:#fff;text-decoration:underline;}
#serverstatus h3
{
margin: 15px 0;
@@ -1370,7 +1423,7 @@ div#profilingchart {
-webkit-box-shadow:0px 1px 1px #fff inset;
-moz-box-shadow:0px 1px 1px #fff inset;
}
-#sectionlinks a, .statuslinks a{
+#sectionlinks a, .buttonlinks a, a.button {
font-size:0.88em;
font-weight:bold;
text-shadow: 0px 1px 0px #fff;
@@ -1398,7 +1451,7 @@ div#profilingchart {
background: -o-linear-gradient(top, #ffffff, #cccccc);
}
-#sectionlinks a:hover, .statuslinks a:hover{
+#sectionlinks a:hover, .buttonlinks a:hover, a.button:hover {
background-image: url(./themes/svg_gradient.php?from=cccccc&to=dddddd);
background-size: 100% 100%;
background: -webkit-gradient(linear, left top, left bottom, from(#cccccc), to(#dddddd));
diff --git a/themes/pmahomme/img/col_pointer_ver.png b/themes/pmahomme/img/col_pointer_ver.png
new file mode 100644
index 0000000000..896475449d
Binary files /dev/null and b/themes/pmahomme/img/col_pointer_ver.png differ
diff --git a/themes/pmahomme/img/pause.png b/themes/pmahomme/img/pause.png
new file mode 100644
index 0000000000..a131617599
Binary files /dev/null and b/themes/pmahomme/img/pause.png differ
diff --git a/themes/pmahomme/img/play.png b/themes/pmahomme/img/play.png
new file mode 100644
index 0000000000..e252606d3e
Binary files /dev/null and b/themes/pmahomme/img/play.png differ
diff --git a/themes/pmahomme/img/s_process.png b/themes/pmahomme/img/s_cog.png
similarity index 100%
rename from themes/pmahomme/img/s_process.png
rename to themes/pmahomme/img/s_cog.png
diff --git a/themes/pmahomme/jquery/jquery-ui-1.8.custom.css b/themes/pmahomme/jquery/jquery-ui-1.8.custom.css
index 9946953858..a3504611d3 100644
--- a/themes/pmahomme/jquery/jquery-ui-1.8.custom.css
+++ b/themes/pmahomme/jquery/jquery-ui-1.8.custom.css
@@ -419,6 +419,8 @@ button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra pad
.ui-tabs .ui-tabs-hide {
position: absolute;
left: -10000px;
+ /* required so that overflowing content doesn't cause scrolling */
+ top: -10000px;
}
/* Datepicker
----------------------------------*/