From 8a6834f3e78093612d846e39bc24af68fceaf18a Mon Sep 17 00:00:00 2001 From: Mark Robert Miller Date: Thu, 24 Jan 2013 15:06:00 +0000 Subject: [PATCH 1/6] SOLR-4348: Make the lock type configurable by system property by default. git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1438036 13f79535-47bb-0310-9956-ffa450edef68 --- solr/CHANGES.txt | 3 ++- solr/example/solr/collection1/conf/solrconfig.xml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index a42a8bd25c4..eee92fa135a 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -88,7 +88,8 @@ Optimizations Other Changes ---------------------- - +* SOLR-4348: Make the lock type configurable by system property by default. + (Mark Miller) ================== 4.1.0 ================== diff --git a/solr/example/solr/collection1/conf/solrconfig.xml b/solr/example/solr/collection1/conf/solrconfig.xml index fa13413c956..70f08fdb1d6 100755 --- a/solr/example/solr/collection1/conf/solrconfig.xml +++ b/solr/example/solr/collection1/conf/solrconfig.xml @@ -211,7 +211,7 @@ More details on the nuances of each LockFactory... http://wiki.apache.org/lucene-java/AvailableLockFactories --> - + ${solr.lock.type:native} \s*$)|\s+/g, '').split(','); - } else { - values = uservalues; - } - - width = options.get('width')=='auto' ? values.length*options.get('defaultPixelsPerValue') : options.get('width'); - if (options.get('height') == 'auto') { - if (!options.get('composite') || !this.VCanvas) { - // must be a better way to get the line height - var tmp = document.createElement('span'); - tmp.innerHTML = 'a'; - $(this).html(tmp); - height = $(tmp).innerHeight(); - $(tmp).remove(); - } - } else { - height = options.get('height'); - } - - $.fn.sparkline[options.get('type')].call(this, values, options, width, height); - }; - // jQuery 1.3.0 completely changed the meaning of :hidden :-/ - if (($(this).html() && $(this).is(':hidden')) || ($.fn.jquery < "1.3.0" && $(this).parents().is(':hidden')) || !$(this).parents('body').length) { - pending.push([this, render]); - } else { - render.call(this); - } - }); - }; - - $.fn.sparkline.defaults = defaults; - - - $.sparkline_display_visible = function() { - for (var i=pending.length-1; i>=0; i--) { - var el = pending[i][0]; - if ($(el).is(':visible') && !$(el).parents().is(':hidden')) { - pending[i][1].call(el); - pending.splice(i, 1); - } - } - }; - + var UNSET_OPTION = {}, + getDefaults, createClass, SPFormat, clipval, quartile, normalizeValue, normalizeValues, + remove, isNumber, all, sum, addCSS, ensureArray, formatNumber, RangeMap, + MouseHandler, Tooltip, barHighlightMixin, + line, bar, tristate, discrete, bullet, pie, box, defaultStyles, initStyles, + VShape, VCanvas_base, VCanvas_canvas, VCanvas_vml, pending, shapeCount = 0; /** - * User option handler + * Default configuration settings */ - var UNSET_OPTION = {}; - var normalizeValue = function(val) { - switch(val) { + getDefaults = function () { + return { + // Settings common to most/all chart types + common: { + type: 'line', + lineColor: '#00f', + fillColor: '#cdf', + defaultPixelsPerValue: 3, + width: 'auto', + height: 'auto', + composite: false, + tagValuesAttribute: 'values', + tagOptionsPrefix: 'spark', + enableTagOptions: false, + enableHighlight: true, + highlightLighten: 1.4, + tooltipSkipNull: true, + tooltipPrefix: '', + tooltipSuffix: '', + disableHiddenCheck: false, + numberFormatter: false, + numberDigitGroupCount: 3, + numberDigitGroupSep: ',', + numberDecimalMark: '.', + disableTooltips: false, + disableInteraction: false + }, + // Defaults for line charts + line: { + spotColor: '#f80', + highlightSpotColor: '#5f5', + highlightLineColor: '#f22', + spotRadius: 1.5, + minSpotColor: '#f80', + maxSpotColor: '#f80', + lineWidth: 1, + normalRangeMin: undefined, + normalRangeMax: undefined, + normalRangeColor: '#ccc', + drawNormalOnTop: false, + chartRangeMin: undefined, + chartRangeMax: undefined, + chartRangeMinX: undefined, + chartRangeMaxX: undefined, + tooltipFormat: new SPFormat(' {{prefix}}{{y}}{{suffix}}') + }, + // Defaults for bar charts + bar: { + barColor: '#3366cc', + negBarColor: '#f44', + stackedBarColor: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00', + '#dd4477', '#0099c6', '#990099'], + zeroColor: undefined, + nullColor: undefined, + zeroAxis: true, + barWidth: 4, + barSpacing: 1, + chartRangeMax: undefined, + chartRangeMin: undefined, + chartRangeClip: false, + colorMap: undefined, + tooltipFormat: new SPFormat(' {{prefix}}{{value}}{{suffix}}') + }, + // Defaults for tristate charts + tristate: { + barWidth: 4, + barSpacing: 1, + posBarColor: '#6f6', + negBarColor: '#f44', + zeroBarColor: '#999', + colorMap: {}, + tooltipFormat: new SPFormat(' {{value:map}}'), + tooltipValueLookups: { map: { '-1': 'Loss', '0': 'Draw', '1': 'Win' } } + }, + // Defaults for discrete charts + discrete: { + lineHeight: 'auto', + thresholdColor: undefined, + thresholdValue: 0, + chartRangeMax: undefined, + chartRangeMin: undefined, + chartRangeClip: false, + tooltipFormat: new SPFormat('{{prefix}}{{value}}{{suffix}}') + }, + // Defaults for bullet charts + bullet: { + targetColor: '#f33', + targetWidth: 3, // width of the target bar in pixels + performanceColor: '#33f', + rangeColors: ['#d3dafe', '#a8b6ff', '#7f94ff'], + base: undefined, // set this to a number to change the base start number + tooltipFormat: new SPFormat('{{fieldkey:fields}} - {{value}}'), + tooltipValueLookups: { fields: {r: 'Range', p: 'Performance', t: 'Target'} } + }, + // Defaults for pie charts + pie: { + offset: 0, + sliceColors: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00', + '#dd4477', '#0099c6', '#990099'], + borderWidth: 0, + borderColor: '#000', + tooltipFormat: new SPFormat(' {{value}} ({{percent.1}}%)') + }, + // Defaults for box plots + box: { + raw: false, + boxLineColor: '#000', + boxFillColor: '#cdf', + whiskerColor: '#000', + outlierLineColor: '#333', + outlierFillColor: '#fff', + medianColor: '#f00', + showOutliers: true, + outlierIQR: 1.5, + spotRadius: 1.5, + target: undefined, + targetColor: '#4a2', + chartRangeMax: undefined, + chartRangeMin: undefined, + tooltipFormat: new SPFormat('{{field:fields}}: {{value}}'), + tooltipFormatFieldlistKey: 'field', + tooltipValueLookups: { fields: { lq: 'Lower Quartile', med: 'Median', + uq: 'Upper Quartile', lo: 'Left Outlier', ro: 'Right Outlier', + lw: 'Left Whisker', rw: 'Right Whisker'} } + } + }; + }; + + // You can have tooltips use a css class other than jqstooltip by specifying tooltipClassname + defaultStyles = '.jqstooltip { ' + + 'position: absolute;' + + 'left: 0px;' + + 'top: 0px;' + + 'visibility: hidden;' + + 'background: rgb(0, 0, 0) transparent;' + + 'background-color: rgba(0,0,0,0.6);' + + 'filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000);' + + '-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)";' + + 'color: white;' + + 'font: 10px arial, san serif;' + + 'text-align: left;' + + 'white-space: nowrap;' + + 'padding: 5px;' + + 'border: 1px solid white;' + + 'z-index: 10000;' + + '}' + + '.jqsfield { ' + + 'color: white;' + + 'font: 10px arial, san serif;' + + 'text-align: left;' + + '}'; + + /** + * Utilities + */ + + createClass = function (/* [baseclass, [mixin, ...]], definition */) { + var Class, args; + Class = function () { + this.init.apply(this, arguments); + }; + if (arguments.length > 1) { + if (arguments[0]) { + Class.prototype = $.extend(new arguments[0](), arguments[arguments.length - 1]); + Class._super = arguments[0].prototype; + } else { + Class.prototype = arguments[arguments.length - 1]; + } + if (arguments.length > 2) { + args = Array.prototype.slice.call(arguments, 1, -1); + args.unshift(Class.prototype); + $.extend.apply($, args); + } + } else { + Class.prototype = arguments[0]; + } + Class.prototype.cls = Class; + return Class; + }; + + /** + * Wraps a format string for tooltips + * {{x}} + * {{x.2} + * {{x:months}} + */ + $.SPFormatClass = SPFormat = createClass({ + fre: /\{\{([\w.]+?)(:(.+?))?\}\}/g, + precre: /(\w+)\.(\d+)/, + + init: function (format, fclass) { + this.format = format; + this.fclass = fclass; + }, + + render: function (fieldset, lookups, options) { + var self = this, + fields = fieldset, + match, token, lookupkey, fieldvalue, prec; + return this.format.replace(this.fre, function () { + var lookup; + token = arguments[1]; + lookupkey = arguments[3]; + match = self.precre.exec(token); + if (match) { + prec = match[2]; + token = match[1]; + } else { + prec = false; + } + fieldvalue = fields[token]; + if (fieldvalue === undefined) { + return ''; + } + if (lookupkey && lookups && lookups[lookupkey]) { + lookup = lookups[lookupkey]; + if (lookup.get) { // RangeMap + return lookups[lookupkey].get(fieldvalue) || fieldvalue; + } else { + return lookups[lookupkey][fieldvalue] || fieldvalue; + } + } + if (isNumber(fieldvalue)) { + if (options.get('numberFormatter')) { + fieldvalue = options.get('numberFormatter')(fieldvalue); + } else { + fieldvalue = formatNumber(fieldvalue, prec, + options.get('numberDigitGroupCount'), + options.get('numberDigitGroupSep'), + options.get('numberDecimalMark')); + } + } + return fieldvalue; + }); + } + }); + + // convience method to avoid needing the new operator + $.spformat = function(format, fclass) { + return new SPFormat(format, fclass); + }; + + clipval = function (val, min, max) { + if (val < min) { + return min; + } + if (val > max) { + return max; + } + return val; + }; + + quartile = function (values, q) { + var vl; + if (q === 2) { + vl = Math.floor(values.length / 2); + return values.length % 2 ? values[vl] : (values[vl-1] + values[vl]) / 2; + } else { + if (values.length % 2 ) { // odd + vl = (values.length * q + q) / 4; + return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 : values[vl-1]; + } else { //even + vl = (values.length * q + 2) / 4; + return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 : values[vl-1]; + + } + } + }; + + normalizeValue = function (val) { + var nf; + switch (val) { case 'undefined': val = undefined; break; @@ -363,775 +508,2245 @@ val = false; break; default: - var nf = parseFloat(val); + nf = parseFloat(val); if (val == nf) { val = nf; } } return val; }; - $.fn.sparkline.options = function(tag, userOptions) { - var extendedOptions; - this.userOptions = userOptions = userOptions || {}; - this.tag = tag; - this.tagValCache = {}; - var defaults = $.fn.sparkline.defaults; - var base = defaults.common; - this.tagOptionsPrefix = userOptions.enableTagOptions && (userOptions.tagOptionsPrefix || base.tagOptionsPrefix); - - var tagOptionType = this.getTagSetting('type'); - if (tagOptionType === UNSET_OPTION) { - extendedOptions = defaults[userOptions.type || base.type]; - } else { - extendedOptions = defaults[tagOptionType]; + + normalizeValues = function (vals) { + var i, result = []; + for (i = vals.length; i--;) { + result[i] = normalizeValue(vals[i]); } - this.mergedOptions = $.extend({}, base, extendedOptions, userOptions); + return result; }; - - $.fn.sparkline.options.prototype.getTagSetting = function(key) { - var val, i, prefix = this.tagOptionsPrefix; - if (prefix === false || prefix === undefined) { - return UNSET_OPTION; - } - if (this.tagValCache.hasOwnProperty(key)) { - val = this.tagValCache.key; - } else { - val = this.tag.getAttribute(prefix + key); - if (val === undefined || val === null) { - val = UNSET_OPTION; - } else if (val.substr(0, 1) == '[') { - val = val.substr(1, val.length-2).split(','); - for(i=val.length; i--;) { - val[i] = normalizeValue(val[i].replace(/(^\s*)|(\s*$)/g, '')); - } - } else if (val.substr(0, 1) == '{') { - var pairs= val.substr(1, val.length-2).split(','); - val = {}; - for(i=pairs.length; i--;) { - var keyval = pairs[i].split(':', 2); - val[keyval[0].replace(/(^\s*)|(\s*$)/g, '')] = normalizeValue(keyval[1].replace(/(^\s*)|(\s*$)/g, '')); - } - } else { - val = normalizeValue(val); + remove = function (vals, filter) { + var i, vl, result = []; + for (i = 0, vl = vals.length; i < vl; i++) { + if (vals[i] !== filter) { + result.push(vals[i]); } - this.tagValCache.key = val; } - return val; + return result; }; - $.fn.sparkline.options.prototype.get = function(key) { - var tagOption = this.getTagSetting(key); - if (tagOption !== UNSET_OPTION) { - return tagOption; - } - return this.mergedOptions[key]; + isNumber = function (num) { + return !isNaN(parseFloat(num)) && isFinite(num); }; + formatNumber = function (num, prec, groupsize, groupsep, decsep) { + var p, i; + num = (prec === false ? parseFloat(num).toString() : num.toFixed(prec)).split(''); + p = (p = $.inArray('.', num)) < 0 ? num.length : p; + if (p < num.length) { + num[p] = decsep; + } + for (i = p - groupsize; i > 0; i -= groupsize) { + num.splice(i, 0, groupsep); + } + return num.join(''); + }; + + // determine if all values of an array match a value + // returns true if the array is empty + all = function (val, arr, ignoreNull) { + var i; + for (i = arr.length; i--; ) { + if (ignoreNull && arr[i] === null) continue; + if (arr[i] !== val) { + return false; + } + } + return true; + }; + + // sums the numeric values in an array, ignoring other values + sum = function (vals) { + var total = 0, i; + for (i = vals.length; i--;) { + total += typeof vals[i] === 'number' ? vals[i] : 0; + } + return total; + }; + + ensureArray = function (val) { + return $.isArray(val) ? val : [val]; + }; + + // http://paulirish.com/2008/bookmarklet-inject-new-css-rules/ + addCSS = function(css) { + var tag; + //if ('\v' == 'v') /* ie only */ { + if (document.createStyleSheet) { + document.createStyleSheet().cssText = css; + } else { + tag = document.createElement('style'); + tag.type = 'text/css'; + document.getElementsByTagName('head')[0].appendChild(tag); + tag[(typeof document.body.style.WebkitAppearance == 'string') /* webkit only */ ? 'innerText' : 'innerHTML'] = css; + } + }; + + // Provide a cross-browser interface to a few simple drawing primitives + $.fn.simpledraw = function (width, height, useExisting, interact) { + var target, mhandler; + if (useExisting && (target = this.data('_jqs_vcanvas'))) { + return target; + } + if (width === undefined) { + width = $(this).innerWidth(); + } + if (height === undefined) { + height = $(this).innerHeight(); + } + if ($.browser.hasCanvas) { + target = new VCanvas_canvas(width, height, this, interact); + } else if ($.browser.msie) { + target = new VCanvas_vml(width, height, this); + } else { + return false; + } + mhandler = $(this).data('_jqs_mhandler'); + if (mhandler) { + mhandler.registerCanvas(target); + } + return target; + }; + + $.fn.cleardraw = function () { + var target = this.data('_jqs_vcanvas'); + if (target) { + target.reset(); + } + }; + + $.RangeMapClass = RangeMap = createClass({ + init: function (map) { + var key, range, rangelist = []; + for (key in map) { + if (map.hasOwnProperty(key) && typeof key === 'string' && key.indexOf(':') > -1) { + range = key.split(':'); + range[0] = range[0].length === 0 ? -Infinity : parseFloat(range[0]); + range[1] = range[1].length === 0 ? Infinity : parseFloat(range[1]); + range[2] = map[key]; + rangelist.push(range); + } + } + this.map = map; + this.rangelist = rangelist || false; + }, + + get: function (value) { + var rangelist = this.rangelist, + i, range, result; + if ((result = this.map[value]) !== undefined) { + return result; + } + if (rangelist) { + for (i = rangelist.length; i--;) { + range = rangelist[i]; + if (range[0] <= value && range[1] >= value) { + return range[2]; + } + } + } + return undefined; + } + }); + + // Convenience function + $.range_map = function(map) { + return new RangeMap(map); + }; + + MouseHandler = createClass({ + init: function (el, options) { + var $el = $(el); + this.$el = $el; + this.options = options; + this.currentPageX = 0; + this.currentPageY = 0; + this.el = el; + this.splist = []; + this.tooltip = null; + this.over = false; + this.displayTooltips = !options.get('disableTooltips'); + this.highlightEnabled = !options.get('disableHighlight'); + }, + + registerSparkline: function (sp) { + this.splist.push(sp); + if (this.over) { + this.updateDisplay(); + } + }, + + registerCanvas: function (canvas) { + var $canvas = $(canvas.canvas); + this.canvas = canvas; + this.$canvas = $canvas; + $canvas.mouseenter($.proxy(this.mouseenter, this)); + $canvas.mouseleave($.proxy(this.mouseleave, this)); + $canvas.click($.proxy(this.mouseclick, this)); + }, + + reset: function (removeTooltip) { + this.splist = []; + if (this.tooltip && removeTooltip) { + this.tooltip.remove(); + this.tooltip = undefined; + } + }, + + mouseclick: function (e) { + var clickEvent = $.Event('sparklineClick'); + clickEvent.originalEvent = e; + clickEvent.sparklines = this.splist; + this.$el.trigger(clickEvent); + }, + + mouseenter: function (e) { + $(document.body).unbind('mousemove.jqs'); + $(document.body).bind('mousemove.jqs', $.proxy(this.mousemove, this)); + this.over = true; + this.currentPageX = e.pageX; + this.currentPageY = e.pageY; + this.currentEl = e.target; + if (!this.tooltip && this.displayTooltips) { + this.tooltip = new Tooltip(this.options); + this.tooltip.updatePosition(e.pageX, e.pageY); + } + this.updateDisplay(); + }, + + mouseleave: function () { + $(document.body).unbind('mousemove.jqs'); + var splist = this.splist, + spcount = splist.length, + needsRefresh = false, + sp, i; + this.over = false; + this.currentEl = null; + + if (this.tooltip) { + this.tooltip.remove(); + this.tooltip = null; + } + + for (i = 0; i < spcount; i++) { + sp = splist[i]; + if (sp.clearRegionHighlight()) { + needsRefresh = true; + } + } + + if (needsRefresh) { + this.canvas.render(); + } + }, + + mousemove: function (e) { + this.currentPageX = e.pageX; + this.currentPageY = e.pageY; + this.currentEl = e.target; + if (this.tooltip) { + this.tooltip.updatePosition(e.pageX, e.pageY); + } + this.updateDisplay(); + }, + + updateDisplay: function () { + var splist = this.splist, + spcount = splist.length, + needsRefresh = false, + offset = this.$canvas.offset(), + localX = this.currentPageX - offset.left, + localY = this.currentPageY - offset.top, + tooltiphtml, sp, i, result, changeEvent; + if (!this.over) { + return; + } + for (i = 0; i < spcount; i++) { + sp = splist[i]; + result = sp.setRegionHighlight(this.currentEl, localX, localY); + if (result) { + needsRefresh = true; + } + } + if (needsRefresh) { + changeEvent = $.Event('sparklineRegionChange'); + changeEvent.sparklines = this.splist; + this.$el.trigger(changeEvent); + if (this.tooltip) { + tooltiphtml = ''; + for (i = 0; i < spcount; i++) { + sp = splist[i]; + tooltiphtml += sp.getCurrentRegionTooltip(); + } + this.tooltip.setContent(tooltiphtml); + } + if (!this.disableHighlight) { + this.canvas.render(); + } + } + if (result === null) { + this.mouseleave(); + } + } + }); + + + Tooltip = createClass({ + sizeStyle: 'position: static !important;' + + 'display: block !important;' + + 'visibility: hidden !important;' + + 'float: left !important;', + + init: function (options) { + var tooltipClassname = options.get('tooltipClassname', 'jqstooltip'), + sizetipStyle = this.sizeStyle, + offset; + this.container = options.get('tooltipContainer') || document.body; + this.tooltipOffsetX = options.get('tooltipOffsetX', 10); + this.tooltipOffsetY = options.get('tooltipOffsetY', 12); + // remove any previous lingering tooltip + $('#jqssizetip').remove(); + $('#jqstooltip').remove(); + this.sizetip = $('
', { + id: 'jqssizetip', + style: sizetipStyle, + 'class': tooltipClassname + }); + this.tooltip = $('
', { + id: 'jqstooltip', + 'class': tooltipClassname + }).appendTo(this.container); + // account for the container's location + offset = this.tooltip.offset(); + this.offsetLeft = offset.left; + this.offsetTop = offset.top; + this.hidden = true; + $(window).unbind('resize.jqs scroll.jqs'); + $(window).bind('resize.jqs scroll.jqs', $.proxy(this.updateWindowDims, this)); + this.updateWindowDims(); + }, + + updateWindowDims: function () { + this.scrollTop = $(window).scrollTop(); + this.scrollLeft = $(window).scrollLeft(); + this.scrollRight = this.scrollLeft + $(window).width(); + this.updatePosition(); + }, + + getSize: function (content) { + this.sizetip.html(content).appendTo(this.container); + this.width = this.sizetip.width() + 1; + this.height = this.sizetip.height(); + this.sizetip.remove(); + }, + + setContent: function (content) { + if (!content) { + this.tooltip.css('visibility', 'hidden'); + this.hidden = true; + return; + } + this.getSize(content); + this.tooltip.html(content) + .css({ + 'width': this.width, + 'height': this.height, + 'visibility': 'visible' + }); + if (this.hidden) { + this.hidden = false; + this.updatePosition(); + } + }, + + updatePosition: function (x, y) { + if (x === undefined) { + if (this.mousex === undefined) { + return; + } + x = this.mousex - this.offsetLeft; + y = this.mousey - this.offsetTop; + + } else { + this.mousex = x = x - this.offsetLeft; + this.mousey = y = y - this.offsetTop; + } + if (!this.height || !this.width || this.hidden) { + return; + } + + y -= this.height + this.tooltipOffsetY; + x += this.tooltipOffsetX; + + if (y < this.scrollTop) { + y = this.scrollTop; + } + if (x < this.scrollLeft) { + x = this.scrollLeft; + } else if (x + this.width > this.scrollRight) { + x = this.scrollRight - this.width; + } + + this.tooltip.css({ + 'left': x, + 'top': y + }); + }, + + remove: function () { + this.tooltip.remove(); + this.sizetip.remove(); + this.sizetip = this.tooltip = undefined; + $(window).unbind('resize.jqs scroll.jqs'); + } + }); + + initStyles = function() { + addCSS(defaultStyles); + }; + + $(initStyles); + + pending = []; + $.fn.sparkline = function (userValues, userOptions) { + return this.each(function () { + var options = new $.fn.sparkline.options(this, userOptions), + $this = $(this), + render, i; + render = function () { + var values, width, height, tmp, mhandler, sp, vals; + if (userValues === 'html' || userValues === undefined) { + vals = this.getAttribute(options.get('tagValuesAttribute')); + if (vals === undefined || vals === null) { + vals = $this.html(); + } + values = vals.replace(/(^\s*\s*$)|\s+/g, '').split(','); + } else { + values = userValues; + } + + width = options.get('width') === 'auto' ? values.length * options.get('defaultPixelsPerValue') : options.get('width'); + if (options.get('height') === 'auto') { + if (!options.get('composite') || !$.data(this, '_jqs_vcanvas')) { + // must be a better way to get the line height + tmp = document.createElement('span'); + tmp.innerHTML = 'a'; + $this.html(tmp); + height = $(tmp).innerHeight() || $(tmp).height(); + $(tmp).remove(); + tmp = null; + } + } else { + height = options.get('height'); + } + + if (!options.get('disableInteraction')) { + mhandler = $.data(this, '_jqs_mhandler'); + if (!mhandler) { + mhandler = new MouseHandler(this, options); + $.data(this, '_jqs_mhandler', mhandler); + } else if (!options.get('composite')) { + mhandler.reset(); + } + } else { + mhandler = false; + } + + if (options.get('composite') && !$.data(this, '_jqs_vcanvas')) { + if (!$.data(this, '_jqs_errnotify')) { + alert('Attempted to attach a composite sparkline to an element with no existing sparkline'); + $.data(this, '_jqs_errnotify', true); + } + return; + } + + sp = new $.fn.sparkline[options.get('type')](this, values, options, width, height); + + sp.render(); + + if (mhandler) { + mhandler.registerSparkline(sp); + } + }; + // jQuery 1.3.0 completely changed the meaning of :hidden :-/ + if (($(this).html() && !options.get('disableHiddenCheck') && $(this).is(':hidden')) || ($.fn.jquery < '1.3.0' && $(this).parents().is(':hidden')) || !$(this).parents('body').length) { + if (!options.get('composite') && $.data(this, '_jqs_pending')) { + // remove any existing references to the element + for (i = pending.length; i; i--) { + if (pending[i - 1][0] == this) { + pending.splice(i - 1, 1); + } + } + } + pending.push([this, render]); + $.data(this, '_jqs_pending', true); + } else { + render.call(this); + } + }); + }; + + $.fn.sparkline.defaults = getDefaults(); + + + $.sparkline_display_visible = function () { + var el, i, pl; + var done = []; + for (i = 0, pl = pending.length; i < pl; i++) { + el = pending[i][0]; + if ($(el).is(':visible') && !$(el).parents().is(':hidden')) { + pending[i][1].call(el); + $.data(pending[i][0], '_jqs_pending', false); + done.push(i); + } else if (!$(el).closest('html').length && !$.data(el, '_jqs_pending')) { + // element has been inserted and removed from the DOM + // If it was not yet inserted into the dom then the .data request + // will return true. + // removing from the dom causes the data to be removed. + $.data(pending[i][0], '_jqs_pending', false); + done.push(i); + } + } + for (i = done.length; i; i--) { + pending.splice(done[i - 1], 1); + } + }; + + + /** + * User option handler + */ + $.fn.sparkline.options = createClass({ + init: function (tag, userOptions) { + var extendedOptions, defaults, base, tagOptionType; + this.userOptions = userOptions = userOptions || {}; + this.tag = tag; + this.tagValCache = {}; + defaults = $.fn.sparkline.defaults; + base = defaults.common; + this.tagOptionsPrefix = userOptions.enableTagOptions && (userOptions.tagOptionsPrefix || base.tagOptionsPrefix); + + tagOptionType = this.getTagSetting('type'); + if (tagOptionType === UNSET_OPTION) { + extendedOptions = defaults[userOptions.type || base.type]; + } else { + extendedOptions = defaults[tagOptionType]; + } + this.mergedOptions = $.extend({}, base, extendedOptions, userOptions); + }, + + + getTagSetting: function (key) { + var prefix = this.tagOptionsPrefix, + val, i, pairs, keyval; + if (prefix === false || prefix === undefined) { + return UNSET_OPTION; + } + if (this.tagValCache.hasOwnProperty(key)) { + val = this.tagValCache.key; + } else { + val = this.tag.getAttribute(prefix + key); + if (val === undefined || val === null) { + val = UNSET_OPTION; + } else if (val.substr(0, 1) === '[') { + val = val.substr(1, val.length - 2).split(','); + for (i = val.length; i--;) { + val[i] = normalizeValue(val[i].replace(/(^\s*)|(\s*$)/g, '')); + } + } else if (val.substr(0, 1) === '{') { + pairs = val.substr(1, val.length - 2).split(','); + val = {}; + for (i = pairs.length; i--;) { + keyval = pairs[i].split(':', 2); + val[keyval[0].replace(/(^\s*)|(\s*$)/g, '')] = normalizeValue(keyval[1].replace(/(^\s*)|(\s*$)/g, '')); + } + } else { + val = normalizeValue(val); + } + this.tagValCache.key = val; + } + return val; + }, + + get: function (key, defaultval) { + var tagOption = this.getTagSetting(key), + result; + if (tagOption !== UNSET_OPTION) { + return tagOption; + } + return (result = this.mergedOptions[key]) === undefined ? defaultval : result; + } + }); + + + $.fn.sparkline._base = createClass({ + disabled: false, + + init: function (el, values, options, width, height) { + this.el = el; + this.$el = $(el); + this.values = values; + this.options = options; + this.width = width; + this.height = height; + this.currentRegion = undefined; + }, + + /** + * Setup the canvas + */ + initTarget: function () { + var interactive = !this.options.get('disableInteraction'); + if (!(this.target = this.$el.simpledraw(this.width, this.height, this.options.get('composite'), interactive))) { + this.disabled = true; + } else { + this.canvasWidth = this.target.pixelWidth; + this.canvasHeight = this.target.pixelHeight; + } + }, + + /** + * Actually render the chart to the canvas + */ + render: function () { + if (this.disabled) { + this.el.innerHTML = ''; + return false; + } + return true; + }, + + /** + * Return a region id for a given x/y co-ordinate + */ + getRegion: function (x, y) { + }, + + /** + * Highlight an item based on the moused-over x,y co-ordinate + */ + setRegionHighlight: function (el, x, y) { + var currentRegion = this.currentRegion, + highlightEnabled = !this.options.get('disableHighlight'), + newRegion; + if (x > this.canvasWidth || y > this.canvasHeight || x < 0 || y < 0) { + return null; + } + newRegion = this.getRegion(el, x, y); + if (currentRegion !== newRegion) { + if (currentRegion !== undefined && highlightEnabled) { + this.removeHighlight(); + } + this.currentRegion = newRegion; + if (newRegion !== undefined && highlightEnabled) { + this.renderHighlight(); + } + return true; + } + return false; + }, + + /** + * Reset any currently highlighted item + */ + clearRegionHighlight: function () { + if (this.currentRegion !== undefined) { + this.removeHighlight(); + this.currentRegion = undefined; + return true; + } + return false; + }, + + renderHighlight: function () { + this.changeHighlight(true); + }, + + removeHighlight: function () { + this.changeHighlight(false); + }, + + changeHighlight: function (highlight) {}, + + /** + * Fetch the HTML to display as a tooltip + */ + getCurrentRegionTooltip: function () { + var options = this.options, + header = '', + entries = [], + fields, formats, formatlen, fclass, text, i, + showFields, showFieldsKey, newFields, fv, + formatter, format, fieldlen, j; + if (this.currentRegion === undefined) { + return ''; + } + fields = this.getCurrentRegionFields(); + formatter = options.get('tooltipFormatter'); + if (formatter) { + return formatter(this, options, fields); + } + if (options.get('tooltipChartTitle')) { + header += '
' + options.get('tooltipChartTitle') + '
\n'; + } + formats = this.options.get('tooltipFormat'); + if (!formats) { + return ''; + } + if (!$.isArray(formats)) { + formats = [formats]; + } + if (!$.isArray(fields)) { + fields = [fields]; + } + showFields = this.options.get('tooltipFormatFieldlist'); + showFieldsKey = this.options.get('tooltipFormatFieldlistKey'); + if (showFields && showFieldsKey) { + // user-selected ordering of fields + newFields = []; + for (i = fields.length; i--;) { + fv = fields[i][showFieldsKey]; + if ((j = $.inArray(fv, showFields)) != -1) { + newFields[j] = fields[i]; + } + } + fields = newFields; + } + formatlen = formats.length; + fieldlen = fields.length; + for (i = 0; i < formatlen; i++) { + format = formats[i]; + if (typeof format === 'string') { + format = new SPFormat(format); + } + fclass = format.fclass || 'jqsfield'; + for (j = 0; j < fieldlen; j++) { + if (!fields[j].isNull || !options.get('tooltipSkipNull')) { + $.extend(fields[j], { + prefix: options.get('tooltipPrefix'), + suffix: options.get('tooltipSuffix') + }); + text = format.render(fields[j], options.get('tooltipValueLookups'), options); + entries.push('
' + text + '
'); + } + } + } + if (entries.length) { + return header + entries.join('\n'); + } + return ''; + }, + + getCurrentRegionFields: function () {}, + + calcHighlightColor: function (color, options) { + var highlightColor = options.get('highlightColor'), + lighten = options.get('highlightLighten'), + parse, mult, rgbnew, i; + if (highlightColor) { + return highlightColor; + } + if (lighten) { + // extract RGB values + parse = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(color) || /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(color); + if (parse) { + rgbnew = []; + mult = color.length === 4 ? 16 : 1; + for (i = 0; i < 3; i++) { + rgbnew[i] = clipval(Math.round(parseInt(parse[i + 1], 16) * mult * lighten), 0, 255); + } + return 'rgb(' + rgbnew.join(',') + ')'; + } + + } + return color; + } + + }); + + barHighlightMixin = { + changeHighlight: function (highlight) { + var currentRegion = this.currentRegion, + target = this.target, + shapeids = this.regionShapes[currentRegion], + newShapes; + // will be null if the region value was null + if (shapeids) { + newShapes = this.renderRegion(currentRegion, highlight); + if ($.isArray(newShapes) || $.isArray(shapeids)) { + target.replaceWithShapes(shapeids, newShapes); + this.regionShapes[currentRegion] = $.map(newShapes, function (newShape) { + return newShape.id; + }); + } else { + target.replaceWithShape(shapeids, newShapes); + this.regionShapes[currentRegion] = newShapes.id; + } + } + }, + + render: function () { + var values = this.values, + target = this.target, + regionShapes = this.regionShapes, + shapes, ids, i, j; + + if (!this.cls._super.render.call(this)) { + return; + } + for (i = values.length; i--;) { + shapes = this.renderRegion(i); + if (shapes) { + if ($.isArray(shapes)) { + ids = []; + for (j = shapes.length; j--;) { + shapes[j].append(); + ids.push(shapes[j].id); + } + regionShapes[i] = ids; + } else { + shapes.append(); + regionShapes[i] = shapes.id; // store just the shapeid + } + } else { + // null value + regionShapes[i] = null; + } + } + target.render(); + } + }; /** * Line charts */ - $.fn.sparkline.line = function(values, options, width, height) { - var xvalues = [], yvalues = [], yminmax = []; - for (var i=0; i= regionMap[i][0] && x <= regionMap[i][1]) { + return regionMap[i][2]; } } - } - if (options.get('xvalues')) { - xvalues = options.get('xvalues'); - } + return undefined; + }, - var maxy = Math.max.apply(Math, yminmax); - var maxyval = maxy; - var miny = Math.min.apply(Math, yminmax); - var minyval = miny; + getCurrentRegionFields: function () { + var currentRegion = this.currentRegion; + return { + isNull: this.yvalues[currentRegion] === null, + x: this.xvalues[currentRegion], + y: this.yvalues[currentRegion], + color: this.options.get('lineColor'), + fillColor: this.options.get('fillColor'), + offset: currentRegion + }; + }, - var maxx = Math.max.apply(Math, xvalues); - var minx = Math.min.apply(Math, xvalues); + renderHighlight: function () { + var currentRegion = this.currentRegion, + target = this.target, + vertex = this.vertices[currentRegion], + options = this.options, + spotRadius = options.get('spotRadius'), + highlightSpotColor = options.get('highlightSpotColor'), + highlightLineColor = options.get('highlightLineColor'), + highlightSpot, highlightLine; - var normalRangeMin = options.get('normalRangeMin'); - var normalRangeMax = options.get('normalRangeMax'); - - if (normalRangeMin!==undefined) { - if (normalRangeMinmaxy) { - maxy = normalRangeMax; + if (spotRadius && highlightSpotColor) { + highlightSpot = target.drawCircle(vertex[0], vertex[1], + spotRadius, undefined, highlightSpotColor); + this.highlightSpotId = highlightSpot.id; + target.insertAfterShape(this.lastShapeId, highlightSpot); } - } - if (options.get('chartRangeMin')!==undefined && (options.get('chartRangeClip') || options.get('chartRangeMin')maxy)) { - maxy = options.get('chartRangeMax'); - } - if (options.get('chartRangeMinX')!==undefined && (options.get('chartRangeClipX') || options.get('chartRangeMinX')maxx)) { - maxx = options.get('chartRangeMaxX'); - } - var rangex = maxx-minx === 0 ? 1 : maxx-minx; - var rangey = maxy-miny === 0 ? 1 : maxy-miny; - var vl = yvalues.length-1; + if (highlightLineColor) { + highlightLine = target.drawLine(vertex[0], this.canvasTop, vertex[0], + this.canvasTop + this.canvasHeight, highlightLineColor); + this.highlightLineId = highlightLine.id; + target.insertAfterShape(this.lastShapeId, highlightLine); + } + }, - if (vl<1) { - this.innerHTML = ''; - return; - } + removeHighlight: function () { + var target = this.target; + if (this.highlightSpotId) { + target.removeShapeId(this.highlightSpotId); + this.highlightSpotId = null; + } + if (this.highlightLineId) { + target.removeShapeId(this.highlightLineId); + this.highlightLineId = null; + } + }, - var target = $(this).simpledraw(width, height, options.get('composite')); - if (target) { - var canvas_width = target.pixel_width; - var canvas_height = target.pixel_height; - var canvas_top = 0; - var canvas_left = 0; + scanValues: function () { + var values = this.values, + valcount = values.length, + xvalues = this.xvalues, + yvalues = this.yvalues, + yminmax = this.yminmax, + i, val, isStr, isArray, sp; + for (i = 0; i < valcount; i++) { + val = values[i]; + isStr = typeof(values[i]) === 'string'; + isArray = typeof(values[i]) === 'object' && values[i] instanceof Array; + sp = isStr && values[i].split(':'); + if (isStr && sp.length === 2) { // x:y + xvalues.push(Number(sp[0])); + yvalues.push(Number(sp[1])); + yminmax.push(Number(sp[1])); + } else if (isArray) { + xvalues.push(val[0]); + yvalues.push(val[1]); + yminmax.push(val[1]); + } else { + xvalues.push(i); + if (values[i] === null || values[i] === 'null') { + yvalues.push(null); + } else { + yvalues.push(Number(val)); + yminmax.push(Number(val)); + } + } + } + if (this.options.get('xvalues')) { + xvalues = this.options.get('xvalues'); + } - var spotRadius = options.get('spotRadius'); - if (spotRadius && (canvas_width < (spotRadius*4) || canvas_height < (spotRadius*4))) { + this.maxy = this.maxyorg = Math.max.apply(Math, yminmax); + this.miny = this.minyorg = Math.min.apply(Math, yminmax); + + this.maxx = Math.max.apply(Math, xvalues); + this.minx = Math.min.apply(Math, xvalues); + + this.xvalues = xvalues; + this.yvalues = yvalues; + this.yminmax = yminmax; + + }, + + processRangeOptions: function () { + var options = this.options, + normalRangeMin = options.get('normalRangeMin'), + normalRangeMax = options.get('normalRangeMax'); + + if (normalRangeMin !== undefined) { + if (normalRangeMin < this.miny) { + this.miny = normalRangeMin; + } + if (normalRangeMax > this.maxy) { + this.maxy = normalRangeMax; + } + } + if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.miny)) { + this.miny = options.get('chartRangeMin'); + } + if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.maxy)) { + this.maxy = options.get('chartRangeMax'); + } + if (options.get('chartRangeMinX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMinX') < this.minx)) { + this.minx = options.get('chartRangeMinX'); + } + if (options.get('chartRangeMaxX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMaxX') > this.maxx)) { + this.maxx = options.get('chartRangeMaxX'); + } + + }, + + drawNormalRange: function (canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey) { + var normalRangeMin = this.options.get('normalRangeMin'), + normalRangeMax = this.options.get('normalRangeMax'), + ytop = canvasTop + Math.round(canvasHeight - (canvasHeight * ((normalRangeMax - this.miny) / rangey))), + height = Math.round((canvasHeight * (normalRangeMax - normalRangeMin)) / rangey); + this.target.drawRect(canvasLeft, ytop, canvasWidth, height, undefined, this.options.get('normalRangeColor')).append(); + }, + + render: function () { + var options = this.options, + target = this.target, + canvasWidth = this.canvasWidth, + canvasHeight = this.canvasHeight, + vertices = this.vertices, + spotRadius = options.get('spotRadius'), + regionMap = this.regionMap, + rangex, rangey, yvallast, + canvasTop, canvasLeft, + vertex, path, paths, x, y, xnext, xpos, xposnext, + last, next, yvalcount, lineShapes, fillShapes, plen, + valueSpots, hlSpotsEnabled, color, xvalues, yvalues, i; + + if (!line._super.render.call(this)) { + return; + } + + this.scanValues(); + this.processRangeOptions(); + + xvalues = this.xvalues; + yvalues = this.yvalues; + + if (!this.yminmax.length || this.yvalues.length < 2) { + // empty or all null valuess + return; + } + + canvasTop = canvasLeft = 0; + + rangex = this.maxx - this.minx === 0 ? 1 : this.maxx - this.minx; + rangey = this.maxy - this.miny === 0 ? 1 : this.maxy - this.miny; + yvallast = this.yvalues.length - 1; + + if (spotRadius && (canvasWidth < (spotRadius * 4) || canvasHeight < (spotRadius * 4))) { spotRadius = 0; } if (spotRadius) { // adjust the canvas size as required so that spots will fit - if (options.get('minSpotColor') || (options.get('spotColor') && yvalues[vl]==miny)) { - canvas_height -= Math.ceil(spotRadius); + hlSpotsEnabled = options.get('highlightSpotColor') && !options.get('disableInteraction'); + if (hlSpotsEnabled || options.get('minSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.miny)) { + canvasHeight -= Math.ceil(spotRadius); } - if (options.get('maxSpotColor') || (options.get('spotColor') && yvalues[vl]==maxy)) { - canvas_height -= Math.ceil(spotRadius); - canvas_top += Math.ceil(spotRadius); + if (hlSpotsEnabled || options.get('maxSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.maxy)) { + canvasHeight -= Math.ceil(spotRadius); + canvasTop += Math.ceil(spotRadius); } - if (options.get('minSpotColor') || options.get('maxSpotColor') && (yvalues[0]==miny || yvalues[0]==maxy)) { - canvas_left += Math.ceil(spotRadius); - canvas_width -= Math.ceil(spotRadius); + if (hlSpotsEnabled || + ((options.get('minSpotColor') || options.get('maxSpotColor')) && (yvalues[0] === this.miny || yvalues[0] === this.maxy))) { + canvasLeft += Math.ceil(spotRadius); + canvasWidth -= Math.ceil(spotRadius); } - if (options.get('spotColor') || (options.get('minSpotColor') || options.get('maxSpotColor') && (yvalues[vl]==miny||yvalues[vl]==maxy))) { - canvas_width -= Math.ceil(spotRadius); + if (hlSpotsEnabled || options.get('spotColor') || + (options.get('minSpotColor') || options.get('maxSpotColor') && + (yvalues[yvallast] === this.miny || yvalues[yvallast] === this.maxy))) { + canvasWidth -= Math.ceil(spotRadius); } } - canvas_height--; + canvasHeight--; - var drawNormalRange = function() { - if (normalRangeMin!==undefined) { - var ytop = canvas_top+Math.round(canvas_height-(canvas_height*((normalRangeMax-miny)/rangey))); - var height = Math.round((canvas_height*(normalRangeMax-normalRangeMin))/rangey); - target.drawRect(canvas_left, ytop, canvas_width, height, undefined, options.get('normalRangeColor')); - } - }; - - if (!options.get('drawNormalOnTop')) { - drawNormalRange(); + if (options.get('normalRangeMin') !== undefined && !options.get('drawNormalOnTop')) { + this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey); } - var path = []; - var paths = [path]; - var x, y, vlen=yvalues.length; - for(i=0; i maxy) { - y=maxy; + if (y > this.maxy) { + y = this.maxy; } if (!path.length) { // previous value was null - path.push([canvas_left+Math.round((x-minx)*(canvas_width/rangex)), canvas_top+canvas_height]); + path.push([xpos, canvasTop + canvasHeight]); } - path.push([canvas_left+Math.round((x-minx)*(canvas_width/rangex)), canvas_top+Math.round(canvas_height-(canvas_height*((y-miny)/rangey)))]); + vertex = [xpos, canvasTop + Math.round(canvasHeight - (canvasHeight * ((y - this.miny) / rangey)))]; + path.push(vertex); + vertices.push(vertex); } } - var lineshapes = []; - var fillshapes = []; - var plen=paths.length; - for(i=0; i 2) { + // else we want the first value + path[0] = [path[0][0], path[1][1]]; + } + lineShapes.push(path); } - if (options.get('fillColor')) { - path.push([path[path.length-1][0], canvas_top+canvas_height-1]); - fillshapes.push(path.slice(0)); - path.pop(); - } - // if there's only a single point in this path, then we want to display it as a vertical line - // which means we keep path[0] as is - if (path.length>2) { - // else we want the first value - path[0] = [ path[0][0], path[1][1] ]; - } - lineshapes.push(path); } // draw the fill first, then optionally the normal range, then the line on top of that - plen = fillshapes.length; - for(i=0; imax)) { - max = options.get('chartRangeMax'); - } - var zeroAxis = options.get('zeroAxis'); - if (zeroAxis === undefined) { - zeroAxis = min<0; - } - var range = max-min === 0 ? 1 : max-min; + $.fn.sparkline.bar = bar = createClass($.fn.sparkline._base, barHighlightMixin, { + type: 'bar', - var colorMapByIndex, colorMapByValue; - if ($.isArray(options.get('colorMap'))) { - colorMapByIndex = options.get('colorMap'); - colorMapByValue = null; - } else { - colorMapByIndex = null; - colorMapByValue = options.get('colorMap'); - } + init: function (el, values, options, width, height) { + var barWidth = parseInt(options.get('barWidth'), 10), + barSpacing = parseInt(options.get('barSpacing'), 10), + chartRangeMin = options.get('chartRangeMin'), + chartRangeMax = options.get('chartRangeMax'), + chartRangeClip = options.get('chartRangeClip'), + stackMin = Infinity, + stackMax = -Infinity, + isStackString, groupMin, groupMax, stackRanges, + numValues, i, vlen, range, zeroAxis, xaxisOffset, min, max, clipMin, clipMax, + stacked, vlist, j, slen, svals, val, yoffset, yMaxCalc, canvasHeightEf; + bar._super.init.call(this, el, values, options, width, height); - var target = $(this).simpledraw(width, height, options.get('composite')); - if (target) { - var color, - canvas_height = target.pixel_height, - yzero = min<0 && zeroAxis ? canvas_height-Math.round(canvas_height * (Math.abs(min)/range))-1 : canvas_height-1; - - for(i=values.length; i--;) { - var x = i*(options.get('barWidth')+options.get('barSpacing')), - y, - val = values[i]; - if (val===null) { - if (options.get('nullColor')) { - color = options.get('nullColor'); - val = (zeroAxis && min<0) ? 0 : min; - height = 1; - y = (zeroAxis && min<0) ? yzero : canvas_height - height; - } else { - continue; + // scan values to determine whether to stack bars + for (i = 0, vlen = values.length; i < vlen; i++) { + val = values[i]; + isStackString = typeof(val) === 'string' && val.indexOf(':') > -1; + if (isStackString || $.isArray(val)) { + stacked = true; + if (isStackString) { + val = values[i] = normalizeValues(val.split(':')); } - } else { - if (val < min) { - val=min; + val = remove(val, null); // min/max will treat null as zero + groupMin = Math.min.apply(Math, val); + groupMax = Math.max.apply(Math, val); + if (groupMin < stackMin) { + stackMin = groupMin; } - if (val > max) { - val=max; - } - color = (val < 0) ? options.get('negBarColor') : options.get('barColor'); - if (zeroAxis && min<0) { - height = Math.round(canvas_height*((Math.abs(val)/range)))+1; - y = (val < 0) ? yzero : yzero-height; - } else { - height = Math.round(canvas_height*((val-min)/range))+1; - y = canvas_height-height; - } - if (val===0 && options.get('zeroColor')!==undefined) { - color = options.get('zeroColor'); - } - if (colorMapByValue && colorMapByValue[val]) { - color = colorMapByValue[val]; - } else if (colorMapByIndex && colorMapByIndex.length>i) { - color = colorMapByIndex[i]; - } - if (color===null) { - continue; + if (groupMax > stackMax) { + stackMax = groupMax; } } - target.drawRect(x, y, options.get('barWidth')-1, height-1, color, color); } - } else { - // Remove the tag contents if sparklines aren't supported - this.innerHTML = ''; - } - }; + this.stacked = stacked; + this.regionShapes = {}; + this.barWidth = barWidth; + this.barSpacing = barSpacing; + this.totalBarWidth = barWidth + barSpacing; + this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing); + + this.initTarget(); + + if (chartRangeClip) { + clipMin = chartRangeMin === undefined ? -Infinity : chartRangeMin; + clipMax = chartRangeMax === undefined ? Infinity : chartRangeMax; + } + + numValues = []; + stackRanges = stacked ? [] : numValues; + var stackTotals = []; + var stackRangesNeg = []; + for (i = 0, vlen = values.length; i < vlen; i++) { + if (stacked) { + vlist = values[i]; + values[i] = svals = []; + stackTotals[i] = 0; + stackRanges[i] = stackRangesNeg[i] = 0; + for (j = 0, slen = vlist.length; j < slen; j++) { + val = svals[j] = chartRangeClip ? clipval(vlist[j], clipMin, clipMax) : vlist[j]; + if (val !== null) { + if (val > 0) { + stackTotals[i] += val; + } + if (stackMin < 0 && stackMax > 0) { + if (val < 0) { + stackRangesNeg[i] += Math.abs(val); + } else { + stackRanges[i] += val; + } + } else { + stackRanges[i] += Math.abs(val - (val < 0 ? stackMax : stackMin)); + } + numValues.push(val); + } + } + } else { + val = chartRangeClip ? clipval(values[i], clipMin, clipMax) : values[i]; + val = values[i] = normalizeValue(val); + if (val !== null) { + numValues.push(val); + } + } + } + this.max = max = Math.max.apply(Math, numValues); + this.min = min = Math.min.apply(Math, numValues); + this.stackMax = stackMax = stacked ? Math.max.apply(Math, stackTotals) : max; + this.stackMin = stackMin = stacked ? Math.min.apply(Math, numValues) : min; + + if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < min)) { + min = options.get('chartRangeMin'); + } + if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > max)) { + max = options.get('chartRangeMax'); + } + + this.zeroAxis = zeroAxis = options.get('zeroAxis', true); + if (min <= 0 && max >= 0 && zeroAxis) { + xaxisOffset = 0; + } else if (zeroAxis == false) { + xaxisOffset = min; + } else if (min > 0) { + xaxisOffset = min; + } else { + xaxisOffset = max; + } + this.xaxisOffset = xaxisOffset; + + range = stacked ? (Math.max.apply(Math, stackRanges) + Math.max.apply(Math, stackRangesNeg)) : max - min; + + // as we plot zero/min values a single pixel line, we add a pixel to all other + // values - Reduce the effective canvas size to suit + this.canvasHeightEf = (zeroAxis && min < 0) ? this.canvasHeight - 2 : this.canvasHeight - 1; + + if (min < xaxisOffset) { + yMaxCalc = (stacked && max >= 0) ? stackMax : max; + yoffset = (yMaxCalc - xaxisOffset) / range * this.canvasHeight; + if (yoffset !== Math.ceil(yoffset)) { + this.canvasHeightEf -= 2; + yoffset = Math.ceil(yoffset); + } + } else { + yoffset = this.canvasHeight; + } + this.yoffset = yoffset; + + if ($.isArray(options.get('colorMap'))) { + this.colorMapByIndex = options.get('colorMap'); + this.colorMapByValue = null; + } else { + this.colorMapByIndex = null; + this.colorMapByValue = options.get('colorMap'); + if (this.colorMapByValue && this.colorMapByValue.get === undefined) { + this.colorMapByValue = new RangeMap(this.colorMapByValue); + } + } + + this.range = range; + }, + + getRegion: function (el, x, y) { + var result = Math.floor(x / this.totalBarWidth); + return (result < 0 || result >= this.values.length) ? undefined : result; + }, + + getCurrentRegionFields: function () { + var currentRegion = this.currentRegion, + values = ensureArray(this.values[currentRegion]), + result = [], + value, i; + for (i = values.length; i--;) { + value = values[i]; + result.push({ + isNull: value === null, + value: value, + color: this.calcColor(i, value, currentRegion), + offset: currentRegion + }); + } + return result; + }, + + calcColor: function (stacknum, value, valuenum) { + var colorMapByIndex = this.colorMapByIndex, + colorMapByValue = this.colorMapByValue, + options = this.options, + color, newColor; + if (this.stacked) { + color = options.get('stackedBarColor'); + } else { + color = (value < 0) ? options.get('negBarColor') : options.get('barColor'); + } + if (value === 0 && options.get('zeroColor') !== undefined) { + color = options.get('zeroColor'); + } + if (colorMapByValue && (newColor = colorMapByValue.get(value))) { + color = newColor; + } else if (colorMapByIndex && colorMapByIndex.length > valuenum) { + color = colorMapByIndex[valuenum]; + } + return $.isArray(color) ? color[stacknum % color.length] : color; + }, + + /** + * Render bar(s) for a region + */ + renderRegion: function (valuenum, highlight) { + var vals = this.values[valuenum], + options = this.options, + xaxisOffset = this.xaxisOffset, + result = [], + range = this.range, + stacked = this.stacked, + target = this.target, + x = valuenum * this.totalBarWidth, + canvasHeightEf = this.canvasHeightEf, + yoffset = this.yoffset, + y, height, color, isNull, yoffsetNeg, i, valcount, val, minPlotted, allMin; + + vals = $.isArray(vals) ? vals : [vals]; + valcount = vals.length; + val = vals[0]; + isNull = all(null, vals); + allMin = all(xaxisOffset, vals, true); + + if (isNull) { + if (options.get('nullColor')) { + color = highlight ? options.get('nullColor') : this.calcHighlightColor(options.get('nullColor'), options); + y = (yoffset > 0) ? yoffset - 1 : yoffset; + return target.drawRect(x, y, this.barWidth - 1, 0, color, color); + } else { + return undefined; + } + } + yoffsetNeg = yoffset; + for (i = 0; i < valcount; i++) { + val = vals[i]; + + if (stacked && val === xaxisOffset) { + if (!allMin || minPlotted) { + continue; + } + minPlotted = true; + } + + if (range > 0) { + height = Math.floor(canvasHeightEf * ((Math.abs(val - xaxisOffset) / range))) + 1; + } else { + height = 1; + } + if (val < xaxisOffset || (val === xaxisOffset && yoffset === 0)) { + y = yoffsetNeg; + yoffsetNeg += height; + } else { + y = yoffset - height; + yoffset -= height; + } + color = this.calcColor(i, val, valuenum); + if (highlight) { + color = this.calcHighlightColor(color, options); + } + result.push(target.drawRect(x, y, this.barWidth - 1, height - 1, color, color)); + } + if (result.length === 1) { + return result[0]; + } + return result; + } + }); /** * Tristate charts */ - $.fn.sparkline.tristate = function(values, options, width, height) { - values = $.map(values, Number); - width = (values.length * options.get('barWidth')) + ((values.length-1) * options.get('barSpacing')); + $.fn.sparkline.tristate = tristate = createClass($.fn.sparkline._base, barHighlightMixin, { + type: 'tristate', - var colorMapByIndex, colorMapByValue; - if ($.isArray(options.get('colorMap'))) { - colorMapByIndex = options.get('colorMap'); - colorMapByValue = null; - } else { - colorMapByIndex = null; - colorMapByValue = options.get('colorMap'); - } + init: function (el, values, options, width, height) { + var barWidth = parseInt(options.get('barWidth'), 10), + barSpacing = parseInt(options.get('barSpacing'), 10); + tristate._super.init.call(this, el, values, options, width, height); - var target = $(this).simpledraw(width, height, options.get('composite')); - if (target) { - var canvas_height = target.pixel_height, - half_height = Math.round(canvas_height/2); + this.regionShapes = {}; + this.barWidth = barWidth; + this.barSpacing = barSpacing; + this.totalBarWidth = barWidth + barSpacing; + this.values = $.map(values, Number); + this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing); - for(var i=values.length; i--;) { - var x = i*(options.get('barWidth')+options.get('barSpacing')), - y, color; - if (values[i] < 0) { - y = half_height; - height = half_height-1; - color = options.get('negBarColor'); - } else if (values[i] > 0) { - y = 0; - height = half_height-1; - color = options.get('posBarColor'); - } else { - y = half_height-1; - height = 2; - color = options.get('zeroBarColor'); + if ($.isArray(options.get('colorMap'))) { + this.colorMapByIndex = options.get('colorMap'); + this.colorMapByValue = null; + } else { + this.colorMapByIndex = null; + this.colorMapByValue = options.get('colorMap'); + if (this.colorMapByValue && this.colorMapByValue.get === undefined) { + this.colorMapByValue = new RangeMap(this.colorMapByValue); } - if (colorMapByValue && colorMapByValue[values[i]]) { - color = colorMapByValue[values[i]]; - } else if (colorMapByIndex && colorMapByIndex.length>i) { - color = colorMapByIndex[i]; - } - if (color===null) { - continue; - } - target.drawRect(x, y, options.get('barWidth')-1, height-1, color, color); } - } else { - // Remove the tag contents if sparklines aren't supported - this.innerHTML = ''; + this.initTarget(); + }, + + getRegion: function (el, x, y) { + return Math.floor(x / this.totalBarWidth); + }, + + getCurrentRegionFields: function () { + var currentRegion = this.currentRegion; + return { + isNull: this.values[currentRegion] === undefined, + value: this.values[currentRegion], + color: this.calcColor(this.values[currentRegion], currentRegion), + offset: currentRegion + }; + }, + + calcColor: function (value, valuenum) { + var values = this.values, + options = this.options, + colorMapByIndex = this.colorMapByIndex, + colorMapByValue = this.colorMapByValue, + color, newColor; + + if (colorMapByValue && (newColor = colorMapByValue.get(value))) { + color = newColor; + } else if (colorMapByIndex && colorMapByIndex.length > valuenum) { + color = colorMapByIndex[valuenum]; + } else if (values[valuenum] < 0) { + color = options.get('negBarColor'); + } else if (values[valuenum] > 0) { + color = options.get('posBarColor'); + } else { + color = options.get('zeroBarColor'); + } + return color; + }, + + renderRegion: function (valuenum, highlight) { + var values = this.values, + options = this.options, + target = this.target, + canvasHeight, height, halfHeight, + x, y, color; + + canvasHeight = target.pixelHeight; + halfHeight = Math.round(canvasHeight / 2); + + x = valuenum * this.totalBarWidth; + if (values[valuenum] < 0) { + y = halfHeight; + height = halfHeight - 1; + } else if (values[valuenum] > 0) { + y = 0; + height = halfHeight - 1; + } else { + y = halfHeight - 1; + height = 2; + } + color = this.calcColor(values[valuenum], valuenum); + if (color === null) { + return; + } + if (highlight) { + color = this.calcHighlightColor(color, options); + } + return target.drawRect(x, y, this.barWidth - 1, height - 1, color, color); } - }; + }); - - /** + /** * Discrete charts */ - $.fn.sparkline.discrete = function(values, options, width, height) { - values = $.map(values, Number); - width = options.get('width')=='auto' ? values.length*2 : width; - var interval = Math.floor(width / values.length); + $.fn.sparkline.discrete = discrete = createClass($.fn.sparkline._base, barHighlightMixin, { + type: 'discrete', - var target = $(this).simpledraw(width, height, options.get('composite')); - if (target) { - var canvas_height = target.pixel_height, - line_height = options.get('lineHeight') == 'auto' ? Math.round(canvas_height * 0.3) : options.get('lineHeight'), - pheight = canvas_height - line_height, - min = Math.min.apply(Math, values), - max = Math.max.apply(Math, values); - if (options.get('chartRangeMin')!==undefined && (options.get('chartRangeClip') || options.get('chartRangeMin')max)) { - max = options.get('chartRangeMax'); - } - var range = max-min; + init: function (el, values, options, width, height) { + discrete._super.init.call(this, el, values, options, width, height); - for(var i=values.length; i--;) { - var val = values[i]; - if (val < min) { - val=min; - } - if (val > max) { - val=max; - } - var x = (i*interval), - ytop = Math.round(pheight-pheight*((val-min)/range)); - target.drawLine(x, ytop, x, ytop+line_height, (options.get('thresholdColor') && val < options.get('thresholdValue')) ? options.get('thresholdColor') : options.get('lineColor')); + this.regionShapes = {}; + this.values = values = $.map(values, Number); + this.min = Math.min.apply(Math, values); + this.max = Math.max.apply(Math, values); + this.range = this.max - this.min; + this.width = width = options.get('width') === 'auto' ? values.length * 2 : this.width; + this.interval = Math.floor(width / values.length); + this.itemWidth = width / values.length; + if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.min)) { + this.min = options.get('chartRangeMin'); } - } else { - // Remove the tag contents if sparklines aren't supported - this.innerHTML = ''; + if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.max)) { + this.max = options.get('chartRangeMax'); + } + this.initTarget(); + if (this.target) { + this.lineHeight = options.get('lineHeight') === 'auto' ? Math.round(this.canvasHeight * 0.3) : options.get('lineHeight'); + } + }, + + getRegion: function (el, x, y) { + return Math.floor(x / this.itemWidth); + }, + + getCurrentRegionFields: function () { + var currentRegion = this.currentRegion; + return { + isNull: this.values[currentRegion] === undefined, + value: this.values[currentRegion], + offset: currentRegion + }; + }, + + renderRegion: function (valuenum, highlight) { + var values = this.values, + options = this.options, + min = this.min, + max = this.max, + range = this.range, + interval = this.interval, + target = this.target, + canvasHeight = this.canvasHeight, + lineHeight = this.lineHeight, + pheight = canvasHeight - lineHeight, + ytop, val, color, x; + + val = clipval(values[valuenum], min, max); + x = valuenum * interval; + ytop = Math.round(pheight - pheight * ((val - min) / range)); + color = (options.get('thresholdColor') && val < options.get('thresholdValue')) ? options.get('thresholdColor') : options.get('lineColor'); + if (highlight) { + color = this.calcHighlightColor(color, options); + } + return target.drawLine(x, ytop, x, ytop + lineHeight, color); } - - }; - + }); /** * Bullet charts */ - $.fn.sparkline.bullet = function(values, options, width, height) { - values = $.map(values, Number); - // target, performance, range1, range2, range3 - - width = options.get('width')=='auto' ? '4.0em' : width; + $.fn.sparkline.bullet = bullet = createClass($.fn.sparkline._base, { + type: 'bullet', - var target = $(this).simpledraw(width, height, options.get('composite')); - if (target && values.length>1) { - var canvas_width = target.pixel_width-Math.ceil(options.get('targetWidth')/2), - canvas_height = target.pixel_height, - min = Math.min.apply(Math, values), - max = Math.max.apply(Math, values); + init: function (el, values, options, width, height) { + var min, max, vals; + bullet._super.init.call(this, el, values, options, width, height); + // values: target, performance, range1, range2, range3 + this.values = values = normalizeValues(values); + // target or performance could be null + vals = values.slice(); + vals[0] = vals[0] === null ? vals[2] : vals[0]; + vals[1] = values[1] === null ? vals[2] : vals[1]; + min = Math.min.apply(Math, values); + max = Math.max.apply(Math, values); if (options.get('base') === undefined) { min = min < 0 ? min : 0; } else { min = options.get('base'); } - var range = max-min; - - // draw range values - for(var i=2, vlen=values.length; i1) { - var canvas_width = target.pixel_width, - canvas_height = target.pixel_height, - radius = Math.floor(Math.min(canvas_width, canvas_height)/2), - total = 0, - next = 0, - circle = 2*Math.PI; + init: function (el, values, options, width, height) { + var total = 0, i; - for(var i=values.length; i--;) { - total += values[i]; + pie._super.init.call(this, el, values, options, width, height); + + this.shapes = {}; // map shape ids to value offsets + this.valueShapes = {}; // maps value offsets to shape ids + this.values = values = $.map(values, Number); + + if (options.get('width') === 'auto') { + this.width = this.height; } - if (options.get('offset')) { - next += (2*Math.PI)*(options.get('offset')/360); - } - var vlen = values.length; - for(i=0; i 0) { // avoid divide by zero - end = next + (circle*(values[i]/total)); + if (values.length > 0) { + for (i = values.length; i--;) { + total += values[i]; + } + } + this.total = total; + this.initTarget(); + this.radius = Math.floor(Math.min(this.canvasWidth, this.canvasHeight) / 2); + }, + + getRegion: function (el, x, y) { + var shapeid = this.target.getShapeAt(el, x, y); + return (shapeid !== undefined && this.shapes[shapeid] !== undefined) ? this.shapes[shapeid] : undefined; + }, + + getCurrentRegionFields: function () { + var currentRegion = this.currentRegion; + return { + isNull: this.values[currentRegion] === undefined, + value: this.values[currentRegion], + percent: this.values[currentRegion] / this.total * 100, + color: this.options.get('sliceColors')[currentRegion % this.options.get('sliceColors').length], + offset: currentRegion + }; + }, + + changeHighlight: function (highlight) { + var currentRegion = this.currentRegion, + newslice = this.renderSlice(currentRegion, highlight), + shapeid = this.valueShapes[currentRegion]; + delete this.shapes[shapeid]; + this.target.replaceWithShape(shapeid, newslice); + this.valueShapes[currentRegion] = newslice.id; + this.shapes[newslice.id] = currentRegion; + }, + + renderSlice: function (valuenum, highlight) { + var target = this.target, + options = this.options, + radius = this.radius, + borderWidth = options.get('borderWidth'), + offset = options.get('offset'), + circle = 2 * Math.PI, + values = this.values, + total = this.total, + next = offset ? (2*Math.PI)*(offset/360) : 0, + start, end, i, vlen, color; + + vlen = values.length; + for (i = 0; i < vlen; i++) { + start = next; + end = next; + if (total > 0) { // avoid divide by zero + end = next + (circle * (values[i] / total)); + } + if (valuenum === i) { + color = options.get('sliceColors')[i % options.get('sliceColors').length]; + if (highlight) { + color = this.calcHighlightColor(color, options); + } + + return target.drawPieSlice(radius, radius, radius - borderWidth, start, end, undefined, color); } - target.drawPieSlice(radius, radius, radius, start, end, undefined, options.get('sliceColors')[i % options.get('sliceColors').length]); next = end; } - } - }; + }, + render: function () { + var target = this.target, + values = this.values, + options = this.options, + radius = this.radius, + borderWidth = options.get('borderWidth'), + shape, i; + + if (!pie._super.render.call(this)) { + return; + } + if (borderWidth) { + target.drawCircle(radius, radius, Math.floor(radius - (borderWidth / 2)), + options.get('borderColor'), undefined, borderWidth).append(); + } + for (i = values.length; i--;) { + if (values[i]) { // don't render zero values + shape = this.renderSlice(i).append(); + this.valueShapes[i] = shape.id; // store just the shapeid + this.shapes[shape.id] = i; + } + } + target.render(); + } + }); /** * Box plots */ - var quartile = function(values, q) { - if (q==2) { - var vl2 = Math.floor(values.length/2); - return values.length % 2 ? values[vl2] : (values[vl2]+values[vl2+1])/2; - } else { - var vl4 = Math.floor(values.length/4); - return values.length % 2 ? (values[vl4*q]+values[vl4*q+1])/2 : values[vl4*q]; - } - }; + $.fn.sparkline.box = box = createClass($.fn.sparkline._base, { + type: 'box', - $.fn.sparkline.box = function(values, options, width, height) { - values = $.map(values, Number); - width = options.get('width')=='auto' ? '4.0em' : width; + init: function (el, values, options, width, height) { + box._super.init.call(this, el, values, options, width, height); + this.values = $.map(values, Number); + this.width = options.get('width') === 'auto' ? '4.0em' : width; + this.initTarget(); + if (!this.values.length) { + this.disabled = 1; + } + }, - var minvalue = options.get('chartRangeMin')===undefined ? Math.min.apply(Math, values) : options.get('chartRangeMin'), - maxvalue = options.get('chartRangeMax')===undefined ? Math.max.apply(Math, values) : options.get('chartRangeMax'), - target = $(this).simpledraw(width, height, options.get('composite')), - vlen = values.length, - lwhisker, loutlier, q1, q2, q3, rwhisker, routlier; + /** + * Simulate a single region + */ + getRegion: function () { + return 1; + }, + + getCurrentRegionFields: function () { + var result = [ + { field: 'lq', value: this.quartiles[0] }, + { field: 'med', value: this.quartiles[1] }, + { field: 'uq', value: this.quartiles[2] } + ]; + if (this.loutlier !== undefined) { + result.push({ field: 'lo', value: this.loutlier}); + } + if (this.routlier !== undefined) { + result.push({ field: 'ro', value: this.routlier}); + } + if (this.lwhisker !== undefined) { + result.push({ field: 'lw', value: this.lwhisker}); + } + if (this.rwhisker !== undefined) { + result.push({ field: 'rw', value: this.rwhisker}); + } + return result; + }, + + render: function () { + var target = this.target, + values = this.values, + vlen = values.length, + options = this.options, + canvasWidth = this.canvasWidth, + canvasHeight = this.canvasHeight, + minValue = options.get('chartRangeMin') === undefined ? Math.min.apply(Math, values) : options.get('chartRangeMin'), + maxValue = options.get('chartRangeMax') === undefined ? Math.max.apply(Math, values) : options.get('chartRangeMax'), + canvasLeft = 0, + lwhisker, loutlier, iqr, q1, q2, q3, rwhisker, routlier, i, + size, unitSize; + + if (!box._super.render.call(this)) { + return; + } - if (target && values.length>1) { - var canvas_width = target.pixel_width, - canvas_height = target.pixel_height; if (options.get('raw')) { - if (options.get('showOutliers') && values.length>5) { - loutlier=values[0]; lwhisker=values[1]; q1=values[2]; q2=values[3]; q3=values[4]; rwhisker=values[5]; routlier=values[6]; + if (options.get('showOutliers') && values.length > 5) { + loutlier = values[0]; + lwhisker = values[1]; + q1 = values[2]; + q2 = values[3]; + q3 = values[4]; + rwhisker = values[5]; + routlier = values[6]; } else { - lwhisker=values[0]; q1=values[1]; q2=values[2]; q3=values[3]; rwhisker=values[4]; + lwhisker = values[0]; + q1 = values[1]; + q2 = values[2]; + q3 = values[3]; + rwhisker = values[4]; } } else { - values.sort(function(a, b) { return a-b; }); + values.sort(function (a, b) { return a - b; }); q1 = quartile(values, 1); q2 = quartile(values, 2); q3 = quartile(values, 3); - var iqr = q3-q1; + iqr = q3 - q1; if (options.get('showOutliers')) { - lwhisker=undefined; rwhisker=undefined; - for(var i=0; i q1-(iqr*options.get('outlierIQR'))) { + lwhisker = rwhisker = undefined; + for (i = 0; i < vlen; i++) { + if (lwhisker === undefined && values[i] > q1 - (iqr * options.get('outlierIQR'))) { lwhisker = values[i]; } - if (values[i] < q3+(iqr*options.get('outlierIQR'))) { + if (values[i] < q3 + (iqr * options.get('outlierIQR'))) { rwhisker = values[i]; } } loutlier = values[0]; - routlier = values[vlen-1]; + routlier = values[vlen - 1]; } else { lwhisker = values[0]; - rwhisker = values[vlen-1]; + rwhisker = values[vlen - 1]; } } + this.quartiles = [q1, q2, q3]; + this.lwhisker = lwhisker; + this.rwhisker = rwhisker; + this.loutlier = loutlier; + this.routlier = routlier; - var unitsize = canvas_width / (maxvalue-minvalue+1), - canvas_left = 0; + unitSize = canvasWidth / (maxValue - minValue + 1); if (options.get('showOutliers')) { - canvas_left = Math.ceil(options.get('spotRadius')); - canvas_width -= 2*Math.ceil(options.get('spotRadius')); - unitsize = canvas_width / (maxvalue-minvalue+1); + canvasLeft = Math.ceil(options.get('spotRadius')); + canvasWidth -= 2 * Math.ceil(options.get('spotRadius')); + unitSize = canvasWidth / (maxValue - minValue + 1); if (loutlier < lwhisker) { - target.drawCircle((loutlier-minvalue)*unitsize+canvas_left, canvas_height/2, options.get('spotRadius'), options.get('outlierLineColor'), options.get('outlierFillColor')); + target.drawCircle((loutlier - minValue) * unitSize + canvasLeft, + canvasHeight / 2, + options.get('spotRadius'), + options.get('outlierLineColor'), + options.get('outlierFillColor')).append(); } if (routlier > rwhisker) { - target.drawCircle((routlier-minvalue)*unitsize+canvas_left, canvas_height/2, options.get('spotRadius'), options.get('outlierLineColor'), options.get('outlierFillColor')); + target.drawCircle((routlier - minValue) * unitSize + canvasLeft, + canvasHeight / 2, + options.get('spotRadius'), + options.get('outlierLineColor'), + options.get('outlierFillColor')).append(); } } // box target.drawRect( - Math.round((q1-minvalue)*unitsize+canvas_left), - Math.round(canvas_height*0.1), - Math.round((q3-q1)*unitsize), - Math.round(canvas_height*0.8), - options.get('boxLineColor'), - options.get('boxFillColor')); + Math.round((q1 - minValue) * unitSize + canvasLeft), + Math.round(canvasHeight * 0.1), + Math.round((q3 - q1) * unitSize), + Math.round(canvasHeight * 0.8), + options.get('boxLineColor'), + options.get('boxFillColor')).append(); // left whisker target.drawLine( - Math.round((lwhisker-minvalue)*unitsize+canvas_left), - Math.round(canvas_height/2), - Math.round((q1-minvalue)*unitsize+canvas_left), - Math.round(canvas_height/2), - options.get('lineColor')); + Math.round((lwhisker - minValue) * unitSize + canvasLeft), + Math.round(canvasHeight / 2), + Math.round((q1 - minValue) * unitSize + canvasLeft), + Math.round(canvasHeight / 2), + options.get('lineColor')).append(); target.drawLine( - Math.round((lwhisker-minvalue)*unitsize+canvas_left), - Math.round(canvas_height/4), - Math.round((lwhisker-minvalue)*unitsize+canvas_left), - Math.round(canvas_height-canvas_height/4), - options.get('whiskerColor')); + Math.round((lwhisker - minValue) * unitSize + canvasLeft), + Math.round(canvasHeight / 4), + Math.round((lwhisker - minValue) * unitSize + canvasLeft), + Math.round(canvasHeight - canvasHeight / 4), + options.get('whiskerColor')).append(); // right whisker - target.drawLine(Math.round((rwhisker-minvalue)*unitsize+canvas_left), - Math.round(canvas_height/2), - Math.round((q3-minvalue)*unitsize+canvas_left), - Math.round(canvas_height/2), - options.get('lineColor')); + target.drawLine(Math.round((rwhisker - minValue) * unitSize + canvasLeft), + Math.round(canvasHeight / 2), + Math.round((q3 - minValue) * unitSize + canvasLeft), + Math.round(canvasHeight / 2), + options.get('lineColor')).append(); target.drawLine( - Math.round((rwhisker-minvalue)*unitsize+canvas_left), - Math.round(canvas_height/4), - Math.round((rwhisker-minvalue)*unitsize+canvas_left), - Math.round(canvas_height-canvas_height/4), - options.get('whiskerColor')); + Math.round((rwhisker - minValue) * unitSize + canvasLeft), + Math.round(canvasHeight / 4), + Math.round((rwhisker - minValue) * unitSize + canvasLeft), + Math.round(canvasHeight - canvasHeight / 4), + options.get('whiskerColor')).append(); // median line target.drawLine( - Math.round((q2-minvalue)*unitsize+canvas_left), - Math.round(canvas_height*0.1), - Math.round((q2-minvalue)*unitsize+canvas_left), - Math.round(canvas_height*0.9), - options.get('medianColor')); + Math.round((q2 - minValue) * unitSize + canvasLeft), + Math.round(canvasHeight * 0.1), + Math.round((q2 - minValue) * unitSize + canvasLeft), + Math.round(canvasHeight * 0.9), + options.get('medianColor')).append(); if (options.get('target')) { - var size = Math.ceil(options.get('spotRadius')); + size = Math.ceil(options.get('spotRadius')); target.drawLine( - Math.round((options.get('target')-minvalue)*unitsize+canvas_left), - Math.round((canvas_height/2)-size), - Math.round((options.get('target')-minvalue)*unitsize+canvas_left), - Math.round((canvas_height/2)+size), - options.get('targetColor')); + Math.round((options.get('target') - minValue) * unitSize + canvasLeft), + Math.round((canvasHeight / 2) - size), + Math.round((options.get('target') - minValue) * unitSize + canvasLeft), + Math.round((canvasHeight / 2) + size), + options.get('targetColor')).append(); target.drawLine( - Math.round((options.get('target')-minvalue)*unitsize+canvas_left-size), - Math.round(canvas_height/2), - Math.round((options.get('target')-minvalue)*unitsize+canvas_left+size), - Math.round(canvas_height/2), - options.get('targetColor')); + Math.round((options.get('target') - minValue) * unitSize + canvasLeft - size), + Math.round(canvasHeight / 2), + Math.round((options.get('target') - minValue) * unitSize + canvasLeft + size), + Math.round(canvasHeight / 2), + options.get('targetColor')).append(); } - } else { - // Remove the tag contents if sparklines aren't supported - this.innerHTML = ''; + target.render(); } - }; - + }); // Setup a very simple "virtual canvas" to make drawing the few shapes we need easier // This is accessible as $(foo).simpledraw() - if ($.browser.msie && !document.namespaces.v) { + if ($.browser.msie && document.namespaces && !document.namespaces.v) { document.namespaces.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML'); } if ($.browser.hasCanvas === undefined) { - var t = document.createElement('canvas'); - $.browser.hasCanvas = t.getContext!==undefined; + $.browser.hasCanvas = document.createElement('canvas').getContext !== undefined; } - VCanvas_base = function(width, height, target) { - }; + VShape = createClass({ + init: function (target, id, type, args) { + this.target = target; + this.id = id; + this.type = type; + this.args = args; + }, + append: function () { + this.target.appendShape(this); + return this; + } + }); - VCanvas_base.prototype = { - init : function(width, height, target) { + VCanvas_base = createClass({ + _pxregex: /(\d+)(px)?\s*$/i, + + init: function (width, height, target) { + if (!width) { + return; + } this.width = width; this.height = height; this.target = target; + this.lastShapeId = null; if (target[0]) { - target=target[0]; + target = target[0]; } - target.VCanvas = this; + $.data(target, '_jqs_vcanvas', this); }, - drawShape : function(path, lineColor, fillColor, lineWidth) { - alert('drawShape not implemented'); + drawLine: function (x1, y1, x2, y2, lineColor, lineWidth) { + return this.drawShape([[x1, y1], [x2, y2]], lineColor, lineWidth); }, - drawLine : function(x1, y1, x2, y2, lineColor, lineWidth) { - return this.drawShape([ [x1,y1], [x2,y2] ], lineColor, lineWidth); + drawShape: function (path, lineColor, fillColor, lineWidth) { + return this._genShape('Shape', [path, lineColor, fillColor, lineWidth]); }, - drawCircle : function(x, y, radius, lineColor, fillColor) { - alert('drawCircle not implemented'); + drawCircle: function (x, y, radius, lineColor, fillColor, lineWidth) { + return this._genShape('Circle', [x, y, radius, lineColor, fillColor, lineWidth]); }, - drawPieSlice : function(x, y, radius, startAngle, endAngle, lineColor, fillColor) { - alert('drawPieSlice not implemented'); + drawPieSlice: function (x, y, radius, startAngle, endAngle, lineColor, fillColor) { + return this._genShape('PieSlice', [x, y, radius, startAngle, endAngle, lineColor, fillColor]); }, - drawRect : function(x, y, width, height, lineColor, fillColor) { - alert('drawRect not implemented'); + drawRect: function (x, y, width, height, lineColor, fillColor) { + return this._genShape('Rect', [x, y, width, height, lineColor, fillColor]); }, - getElement : function() { + getElement: function () { return this.canvas; }, - _insert : function(el, target) { - $(target).html(el); - } - }; - - VCanvas_canvas = function(width, height, target) { - return this.init(width, height, target); - }; - - VCanvas_canvas.prototype = $.extend(new VCanvas_base(), { - _super : VCanvas_base.prototype, - - init : function(width, height, target) { - this._super.init(width, height, target); - this.canvas = document.createElement('canvas'); - if (target[0]) { - target=target[0]; - } - target.VCanvas = this; - $(this.canvas).css({ display:'inline-block', width:width, height:height, verticalAlign:'top' }); - this._insert(this.canvas, target); - this.pixel_height = $(this.canvas).height(); - this.pixel_width = $(this.canvas).width(); - this.canvas.width = this.pixel_width; - this.canvas.height = this.pixel_height; - $(this.canvas).css({width: this.pixel_width, height: this.pixel_height}); + /** + * Return the most recently inserted shape id + */ + getLastShapeId: function () { + return this.lastShapeId; }, - _getContext : function(lineColor, fillColor, lineWidth) { + /** + * Clear and reset the canvas + */ + reset: function () { + alert('reset not implemented'); + }, + + _insert: function (el, target) { + $(target).html(el); + }, + + /** + * Calculate the pixel dimensions of the canvas + */ + _calculatePixelDims: function (width, height, canvas) { + // XXX This should probably be a configurable option + var match; + match = this._pxregex.exec(height); + if (match) { + this.pixelHeight = match[1]; + } else { + this.pixelHeight = $(canvas).height(); + } + match = this._pxregex.exec(width); + if (match) { + this.pixelWidth = match[1]; + } else { + this.pixelWidth = $(canvas).width(); + } + }, + + /** + * Generate a shape object and id for later rendering + */ + _genShape: function (shapetype, shapeargs) { + var id = shapeCount++; + shapeargs.unshift(id); + return new VShape(this, id, shapetype, shapeargs); + }, + + /** + * Add a shape to the end of the render queue + */ + appendShape: function (shape) { + alert('appendShape not implemented'); + }, + + /** + * Replace one shape with another + */ + replaceWithShape: function (shapeid, shape) { + alert('replaceWithShape not implemented'); + }, + + /** + * Insert one shape after another in the render queue + */ + insertAfterShape: function (shapeid, shape) { + alert('insertAfterShape not implemented'); + }, + + /** + * Remove a shape from the queue + */ + removeShapeId: function (shapeid) { + alert('removeShapeId not implemented'); + }, + + /** + * Find a shape at the specified x/y co-ordinates + */ + getShapeAt: function (el, x, y) { + alert('getShapeAt not implemented'); + }, + + /** + * Render all queued shapes onto the canvas + */ + render: function () { + alert('render not implemented'); + } + }); + + VCanvas_canvas = createClass(VCanvas_base, { + init: function (width, height, target, interact) { + VCanvas_canvas._super.init.call(this, width, height, target); + this.canvas = document.createElement('canvas'); + if (target[0]) { + target = target[0]; + } + $.data(target, '_jqs_vcanvas', this); + $(this.canvas).css({ display: 'inline-block', width: width, height: height, verticalAlign: 'top' }); + this._insert(this.canvas, target); + this._calculatePixelDims(width, height, this.canvas); + this.canvas.width = this.pixelWidth; + this.canvas.height = this.pixelHeight; + this.interact = interact; + this.shapes = {}; + this.shapeseq = []; + this.currentTargetShapeId = undefined; + $(this.canvas).css({width: this.pixelWidth, height: this.pixelHeight}); + }, + + _getContext: function (lineColor, fillColor, lineWidth) { var context = this.canvas.getContext('2d'); if (lineColor !== undefined) { context.strokeStyle = lineColor; } - context.lineWidth = lineWidth===undefined ? 1 : lineWidth; + context.lineWidth = lineWidth === undefined ? 1 : lineWidth; if (fillColor !== undefined) { context.fillStyle = fillColor; } return context; }, - drawShape : function(path, lineColor, fillColor, lineWidth) { + reset: function () { + var context = this._getContext(); + context.clearRect(0, 0, this.pixelWidth, this.pixelHeight); + this.shapes = {}; + this.shapeseq = []; + this.currentTargetShapeId = undefined; + }, + + _drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) { + var context = this._getContext(lineColor, fillColor, lineWidth), + i, plen; + context.beginPath(); + context.moveTo(path[0][0] + 0.5, path[0][1] + 0.5); + for (i = 1, plen = path.length; i < plen; i++) { + context.lineTo(path[i][0] + 0.5, path[i][1] + 0.5); // the 0.5 offset gives us crisp pixel-width lines + } + if (lineColor !== undefined) { + context.stroke(); + } + if (fillColor !== undefined) { + context.fill(); + } + if (this.targetX !== undefined && this.targetY !== undefined && + context.isPointInPath(this.targetX, this.targetY)) { + this.currentTargetShapeId = shapeid; + } + }, + + _drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) { var context = this._getContext(lineColor, fillColor, lineWidth); context.beginPath(); - context.moveTo(path[0][0]+0.5, path[0][1]+0.5); - for(var i=1, plen=path.length; i'; + this._calculatePixelDims(width, height, this.canvas); + this.canvas.width = this.pixelWidth; + this.canvas.height = this.pixelHeight; + groupel = ''; this.canvas.insertAdjacentHTML('beforeEnd', groupel); this.group = $(this.canvas).children()[0]; + this.rendered = false; + this.prerender = ''; }, - drawShape : function(path, lineColor, fillColor, lineWidth) { - var vpath = []; - for(var i=0, plen=path.length; i' + + ' style="position:absolute;left:0px;top:0px;height:' + this.pixelHeight + 'px;width:' + this.pixelWidth + 'px;padding:0px;margin:0px;" ' + + ' path="m ' + initial + ' l ' + vpath.join(', ') + ' ' + closed + 'e">' + ' '; - this.group.insertAdjacentHTML('beforeEnd', vel); + return vel; }, - drawCircle : function(x, y, radius, lineColor, fillColor) { - x -= radius+1; - y -= radius+1; - var stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="1" strokeColor="'+lineColor+'" '; - var fill = fillColor === undefined ? ' filled="false"' : ' fillColor="'+fillColor+'" filled="true" '; - var vel = ''; - this.group.insertAdjacentHTML('beforeEnd', vel); - + ' style="position:absolute;top:' + y + 'px; left:' + x + 'px; width:' + (radius * 2) + 'px; height:' + (radius * 2) + 'px">'; + return vel; + }, - - drawPieSlice : function(x, y, radius, startAngle, endAngle, lineColor, fillColor) { - if (startAngle == endAngle) { + + _drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) { + var vpath, startx, starty, endx, endy, stroke, fill, vel; + if (startAngle === endAngle) { return; // VML seems to have problem when start angle equals end angle. } - if ((endAngle - startAngle) == (2*Math.PI)) { + if ((endAngle - startAngle) === (2 * Math.PI)) { startAngle = 0.0; // VML seems to have a problem when drawing a full circle that doesn't start 0 - endAngle = (2*Math.PI); + endAngle = (2 * Math.PI); } - var startx = x + Math.round(Math.cos(startAngle) * radius); - var starty = y + Math.round(Math.sin(startAngle) * radius); - var endx = x + Math.round(Math.cos(endAngle) * radius); - var endy = y + Math.round(Math.sin(endAngle) * radius); + startx = x + Math.round(Math.cos(startAngle) * radius); + starty = y + Math.round(Math.sin(startAngle) * radius); + endx = x + Math.round(Math.cos(endAngle) * radius); + endy = y + Math.round(Math.sin(endAngle) * radius); // Prevent very small slices from being mistaken as a whole pie - if (startx==endx && starty==endy && (endAngle-startAngle) < Math.PI) { + if (startx === endx && starty === endy && (endAngle - startAngle) < Math.PI) { return; } - var vpath = [ x-radius, y-radius, x+radius, y+radius, startx, starty, endx, endy ]; - var stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="1" strokeColor="'+lineColor+'" '; - var fill = fillColor === undefined ? ' filled="false"' : ' fillColor="'+fillColor+'" filled="true" '; - var vel = '' + + ' style="position:absolute;left:0px;top:0px;height:' + this.pixelHeight + 'px;width:' + this.pixelWidth + 'px;padding:0px;margin:0px;" ' + + ' path="m ' + x + ',' + y + ' wa ' + vpath.join(', ') + ' x e">' + ' '; - this.group.insertAdjacentHTML('beforeEnd', vel); + return vel; }, - drawRect : function(x, y, width, height, lineColor, fillColor) { - return this.drawShape( [ [x, y], [x, y+height], [x+width, y+height], [x+width, y], [x, y] ], lineColor, fillColor); + _drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) { + return this._drawShape(shapeid, [[x, y], [x, y + height], [x + width, y + height], [x + width, y], [x, y]], lineColor, fillColor); + }, + + reset: function () { + this.group.innerHTML = ''; + }, + + appendShape: function (shape) { + var vel = this['_draw' + shape.type].apply(this, shape.args); + if (this.rendered) { + this.group.insertAdjacentHTML('beforeEnd', vel); + } else { + this.prerender += vel; + } + this.lastShapeId = shape.id; + return shape.id; + }, + + replaceWithShape: function (shapeid, shape) { + var existing = $('#jqsshape' + shapeid), + vel = this['_draw' + shape.type].apply(this, shape.args); + existing[0].outerHTML = vel; + }, + + replaceWithShapes: function (shapeids, shapes) { + // replace the first shapeid with all the new shapes then toast the remaining old shapes + var existing = $('#jqsshape' + shapeids[0]), + replace = '', + slen = shapes.length, + i; + for (i = 0; i < slen; i++) { + replace += this['_draw' + shapes[i].type].apply(this, shapes[i].args); + } + existing[0].outerHTML = replace; + for (i = 1; i < shapeids.length; i++) { + $('#jqsshape' + shapeids[i]).remove(); + } + }, + + insertAfterShape: function (shapeid, shape) { + var existing = $('#jqsshape' + shapeid), + vel = this['_draw' + shape.type].apply(this, shape.args); + existing[0].insertAdjacentHTML('afterEnd', vel); + }, + + removeShapeId: function (shapeid) { + var existing = $('#jqsshape' + shapeid); + this.group.removeChild(existing[0]); + }, + + getShapeAt: function (el, x, y) { + var shapeid = el.id.substr(8); + return shapeid; + }, + + render: function () { + if (!this.rendered) { + // batch the intial render into a single repaint + this.group.innerHTML = this.prerender; + this.rendered = true; + } } }); -})(jQuery); +})); From 7a66fc108f51fdbf075e4d4eded0cae9a95bb854 Mon Sep 17 00:00:00 2001 From: Stefan Matheis Date: Thu, 24 Jan 2013 19:34:45 +0000 Subject: [PATCH 5/6] SOLR-4349 : Admin UI - Query Interface does not work in IE git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1438139 13f79535-47bb-0310-9956-ffa450edef68 --- solr/CHANGES.txt | 3 +++ solr/webapp/web/js/scripts/query.js | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index 934edac82fb..03f39709944 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -77,6 +77,9 @@ Bug Fixes * SOLR-4345 : Solr Admin UI dosent work in IE 10 (steffkes) +* SOLR-4349 : Admin UI - Query Interface does not work in IE + (steffkes) + Optimizations ---------------------- diff --git a/solr/webapp/web/js/scripts/query.js b/solr/webapp/web/js/scripts/query.js index aefb4ba76ca..3d7bcba3e41 100644 --- a/solr/webapp/web/js/scripts/query.js +++ b/solr/webapp/web/js/scripts/query.js @@ -130,7 +130,7 @@ sammy.get var fieldsets = $( '> fieldset', query_form ); - var fields = fieldsets.first().formToArray(); + var fields = fieldsets.first().formToArray( true ); add_to_form_values( fields ); fieldsets.not( '.common' ) @@ -140,7 +140,7 @@ sammy.get { if( $( 'legend input', set ).is( ':checked' ) ) { - var fields = $( set ).formToArray(); + var fields = $( set ).formToArray( true ); add_to_form_values( fields ); } } From e9e37211528b591e26d83801f05bfffe4a852c68 Mon Sep 17 00:00:00 2001 From: Robert Muir Date: Thu, 24 Jan 2013 20:30:59 +0000 Subject: [PATCH 6/6] add basic bounds checks for terms/payloads/etc git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1438164 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/lucene/index/CheckIndex.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java b/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java index b0a68dce15b..24aca3fa663 100644 --- a/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java +++ b/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java @@ -769,6 +769,8 @@ public class CheckIndex { break; } + checkBounds(term); + // make sure terms arrive in order according to // the comp if (lastTerm == null) { @@ -856,6 +858,9 @@ public class CheckIndex { } lastPos = pos; BytesRef payload = postings.getPayload(); + if (payload != null) { + checkBounds(payload); + } if (payload != null && payload.length < 1) { throw new RuntimeException("term " + term + ": doc " + doc + ": pos " + pos + " payload length is out of bounds " + payload.length); } @@ -1366,6 +1371,31 @@ public class CheckIndex { } return status; } + + // basic value checks + private static void checkBounds(BytesRef b) { + if (b.bytes == null) { + throw new RuntimeException("bytes is null"); + } + if (b.length < 0) { + throw new RuntimeException("length is negative: " + b.length); + } + if (b.length > b.bytes.length) { + throw new RuntimeException("length is out of bounds: " + b.length + ", bytes.length=" + b.bytes.length); + } + if (b.offset < 0) { + throw new RuntimeException("offset is negative: " + b.offset); + } + if (b.offset > b.bytes.length) { + throw new RuntimeException("offset out of bounds: " + b.offset + ", length=" + b.length); + } + if (b.offset + b.length < 0) { + throw new RuntimeException("offset+length is negative: offset=" + b.offset + ",length=" + b.length); + } + if (b.offset + b.length > b.bytes.length) { + throw new RuntimeException("offset+length out of bounds: offset=" + b.offset + ",length=" + b.length + ",bytes.length=" + b.bytes.length); + } + } /** * Test term vectors for a segment.