diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py index 1b4ba191b82..ae9da7ade08 100644 --- a/addons/crm/__openerp__.py +++ b/addons/crm/__openerp__.py @@ -121,7 +121,6 @@ Dashboard for CRM will include: 'static/src/css/crm.css' ], 'js': [ - 'static/lib/sparkline/jquery.sparkline.js', 'static/src/js/crm_case_section.js', ], 'installable': True, diff --git a/addons/crm/static/lib/sparkline/jquery.sparkline.js b/addons/crm/static/lib/sparkline/jquery.sparkline.js deleted file mode 100644 index c003923e03b..00000000000 --- a/addons/crm/static/lib/sparkline/jquery.sparkline.js +++ /dev/null @@ -1,3047 +0,0 @@ -/** -* -* jquery.sparkline.js -* -* v2.1.1 -* (c) Splunk, Inc -* Contact: Gareth Watts (gareth@splunk.com) -* http://omnipotent.net/jquery.sparkline/ -* -* Generates inline sparkline charts from data supplied either to the method -* or inline in HTML -* -* Compatible with Internet Explorer 6.0+ and modern browsers equipped with the canvas tag -* (Firefox 2.0+, Safari, Opera, etc) -* -* License: New BSD License -* -* Copyright (c) 2012, Splunk Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without modification, -* are permitted provided that the following conditions are met: -* -* * Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* * Redistributions in binary form must reproduce the above copyright notice, -* this list of conditions and the following disclaimer in the documentation -* and/or other materials provided with the distribution. -* * Neither the name of Splunk Inc nor the names of its contributors may -* be used to endorse or promote products derived from this software without -* specific prior written permission. -* -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT -* SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT -* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -* -* -* Usage: -* $(selector).sparkline(values, options) -* -* If values is undefined or set to 'html' then the data values are read from the specified tag: -*

Sparkline: 1,4,6,6,8,5,3,5

-* $('.sparkline').sparkline(); -* There must be no spaces in the enclosed data set -* -* Otherwise values must be an array of numbers or null values -*

Sparkline: This text replaced if the browser is compatible

-* $('#sparkline1').sparkline([1,4,6,6,8,5,3,5]) -* $('#sparkline2').sparkline([1,4,6,null,null,5,3,5]) -* -* Values can also be specified in an HTML comment, or as a values attribute: -*

Sparkline:

-*

Sparkline:

-* $('.sparkline').sparkline(); -* -* For line charts, x values can also be specified: -*

Sparkline: 1:1,2.7:4,3.4:6,5:6,6:8,8.7:5,9:3,10:5

-* $('#sparkline1').sparkline([ [1,1], [2.7,4], [3.4,6], [5,6], [6,8], [8.7,5], [9,3], [10,5] ]) -* -* By default, options should be passed in as teh second argument to the sparkline function: -* $('.sparkline').sparkline([1,2,3,4], {type: 'bar'}) -* -* Options can also be set by passing them on the tag itself. This feature is disabled by default though -* as there's a slight performance overhead: -* $('.sparkline').sparkline([1,2,3,4], {enableTagOptions: true}) -*

Sparkline: loading

-* Prefix all options supplied as tag attribute with "spark" (configurable by setting tagOptionPrefix) -* -* Supported options: -* lineColor - Color of the line used for the chart -* fillColor - Color used to fill in the chart - Set to '' or false for a transparent chart -* width - Width of the chart - Defaults to 3 times the number of values in pixels -* height - Height of the chart - Defaults to the height of the containing element -* chartRangeMin - Specify the minimum value to use for the Y range of the chart - Defaults to the minimum value supplied -* chartRangeMax - Specify the maximum value to use for the Y range of the chart - Defaults to the maximum value supplied -* chartRangeClip - Clip out of range values to the max/min specified by chartRangeMin and chartRangeMax -* chartRangeMinX - Specify the minimum value to use for the X range of the chart - Defaults to the minimum value supplied -* chartRangeMaxX - Specify the maximum value to use for the X range of the chart - Defaults to the maximum value supplied -* composite - If true then don't erase any existing chart attached to the tag, but draw -* another chart over the top - Note that width and height are ignored if an -* existing chart is detected. -* tagValuesAttribute - Name of tag attribute to check for data values - Defaults to 'values' -* enableTagOptions - Whether to check tags for sparkline options -* tagOptionPrefix - Prefix used for options supplied as tag attributes - Defaults to 'spark' -* disableHiddenCheck - If set to true, then the plugin will assume that charts will never be drawn into a -* hidden dom element, avoding a browser reflow -* disableInteraction - If set to true then all mouseover/click interaction behaviour will be disabled, -* making the plugin perform much like it did in 1.x -* disableTooltips - If set to true then tooltips will be disabled - Defaults to false (tooltips enabled) -* disableHighlight - If set to true then highlighting of selected chart elements on mouseover will be disabled -* defaults to false (highlights enabled) -* highlightLighten - Factor to lighten/darken highlighted chart values by - Defaults to 1.4 for a 40% increase -* tooltipContainer - Specify which DOM element the tooltip should be rendered into - defaults to document.body -* tooltipClassname - Optional CSS classname to apply to tooltips - If not specified then a default style will be applied -* tooltipOffsetX - How many pixels away from the mouse pointer to render the tooltip on the X axis -* tooltipOffsetY - How many pixels away from the mouse pointer to render the tooltip on the r axis -* tooltipFormatter - Optional callback that allows you to override the HTML displayed in the tooltip -* callback is given arguments of (sparkline, options, fields) -* tooltipChartTitle - If specified then the tooltip uses the string specified by this setting as a title -* tooltipFormat - A format string or SPFormat object (or an array thereof for multiple entries) -* to control the format of the tooltip -* tooltipPrefix - A string to prepend to each field displayed in a tooltip -* tooltipSuffix - A string to append to each field displayed in a tooltip -* tooltipSkipNull - If true then null values will not have a tooltip displayed (defaults to true) -* tooltipValueLookups - An object or range map to map field values to tooltip strings -* (eg. to map -1 to "Lost", 0 to "Draw", and 1 to "Win") -* numberFormatter - Optional callback for formatting numbers in tooltips -* numberDigitGroupSep - Character to use for group separator in numbers "1,234" - Defaults to "," -* numberDecimalMark - Character to use for the decimal point when formatting numbers - Defaults to "." -* numberDigitGroupCount - Number of digits between group separator - Defaults to 3 -* -* There are 7 types of sparkline, selected by supplying a "type" option of 'line' (default), -* 'bar', 'tristate', 'bullet', 'discrete', 'pie' or 'box' -* line - Line chart. Options: -* spotColor - Set to '' to not end each line in a circular spot -* minSpotColor - If set, color of spot at minimum value -* maxSpotColor - If set, color of spot at maximum value -* spotRadius - Radius in pixels -* lineWidth - Width of line in pixels -* normalRangeMin -* normalRangeMax - If set draws a filled horizontal bar between these two values marking the "normal" -* or expected range of values -* normalRangeColor - Color to use for the above bar -* drawNormalOnTop - Draw the normal range above the chart fill color if true -* defaultPixelsPerValue - Defaults to 3 pixels of width for each value in the chart -* highlightSpotColor - The color to use for drawing a highlight spot on mouseover - Set to null to disable -* highlightLineColor - The color to use for drawing a highlight line on mouseover - Set to null to disable -* valueSpots - Specify which points to draw spots on, and in which color. Accepts a range map -* -* bar - Bar chart. Options: -* barColor - Color of bars for postive values -* negBarColor - Color of bars for negative values -* zeroColor - Color of bars with zero values -* nullColor - Color of bars with null values - Defaults to omitting the bar entirely -* barWidth - Width of bars in pixels -* colorMap - Optional mappnig of values to colors to override the *BarColor values above -* can be an Array of values to control the color of individual bars or a range map -* to specify colors for individual ranges of values -* barSpacing - Gap between bars in pixels -* zeroAxis - Centers the y-axis around zero if true -* -* tristate - Charts values of win (>0), lose (<0) or draw (=0) -* posBarColor - Color of win values -* negBarColor - Color of lose values -* zeroBarColor - Color of draw values -* barWidth - Width of bars in pixels -* barSpacing - Gap between bars in pixels -* colorMap - Optional mappnig of values to colors to override the *BarColor values above -* can be an Array of values to control the color of individual bars or a range map -* to specify colors for individual ranges of values -* -* discrete - Options: -* lineHeight - Height of each line in pixels - Defaults to 30% of the graph height -* thesholdValue - Values less than this value will be drawn using thresholdColor instead of lineColor -* thresholdColor -* -* bullet - Values for bullet graphs msut be in the order: target, performance, range1, range2, range3, ... -* options: -* targetColor - The color of the vertical target marker -* targetWidth - The width of the target marker in pixels -* performanceColor - The color of the performance measure horizontal bar -* rangeColors - Colors to use for each qualitative range background color -* -* pie - Pie chart. Options: -* sliceColors - An array of colors to use for pie slices -* offset - Angle in degrees to offset the first slice - Try -90 or +90 -* borderWidth - Width of border to draw around the pie chart, in pixels - Defaults to 0 (no border) -* borderColor - Color to use for the pie chart border - Defaults to #000 -* -* box - Box plot. Options: -* raw - Set to true to supply pre-computed plot points as values -* values should be: low_outlier, low_whisker, q1, median, q3, high_whisker, high_outlier -* When set to false you can supply any number of values and the box plot will -* be computed for you. Default is false. -* showOutliers - Set to true (default) to display outliers as circles -* outlierIQR - Interquartile range used to determine outliers. Default 1.5 -* boxLineColor - Outline color of the box -* boxFillColor - Fill color for the box -* whiskerColor - Line color used for whiskers -* outlierLineColor - Outline color of outlier circles -* outlierFillColor - Fill color of the outlier circles -* spotRadius - Radius of outlier circles -* medianColor - Line color of the median line -* target - Draw a target cross hair at the supplied value (default undefined) -* -* -* -* Examples: -* $('#sparkline1').sparkline(myvalues, { lineColor: '#f00', fillColor: false }); -* $('.barsparks').sparkline('html', { type:'bar', height:'40px', barWidth:5 }); -* $('#tristate').sparkline([1,1,-1,1,0,0,-1], { type:'tristate' }): -* $('#discrete').sparkline([1,3,4,5,5,3,4,5], { type:'discrete' }); -* $('#bullet').sparkline([10,12,12,9,7], { type:'bullet' }); -* $('#pie').sparkline([1,1,2], { type:'pie' }); -*/ - -/*jslint regexp: true, browser: true, jquery: true, white: true, nomen: false, plusplus: false, maxerr: 500, indent: 4 */ - -(function(factory) { - if(typeof define === 'function' && define.amd) { - define(['jquery'], factory); - } - else { - factory(jQuery); - } -} -(function($) { - 'use strict'; - - 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; - - /** - * Default configuration settings - */ - 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; - case 'null': - val = null; - break; - case 'true': - val = true; - break; - case 'false': - val = false; - break; - default: - nf = parseFloat(val); - if (val == nf) { - val = nf; - } - } - return val; - }; - - normalizeValues = function (vals) { - var i, result = []; - for (i = vals.length; i--;) { - result[i] = normalizeValue(vals[i]); - } - return result; - }; - - 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]); - } - } - return result; - }; - - 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 ($.fn.sparkline.hasCanvas) { - target = new VCanvas_canvas(width, height, this, interact); - } else if ($.fn.sparkline.hasVML) { - 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 = line = createClass($.fn.sparkline._base, { - type: 'line', - - init: function (el, values, options, width, height) { - line._super.init.call(this, el, values, options, width, height); - this.vertices = []; - this.regionMap = []; - this.xvalues = []; - this.yvalues = []; - this.yminmax = []; - this.hightlightSpotId = null; - this.lastShapeId = null; - this.initTarget(); - }, - - getRegion: function (el, x, y) { - var i, - regionMap = this.regionMap; // maps regions to value positions - for (i = regionMap.length; i--;) { - if (regionMap[i] !== null && x >= regionMap[i][0] && x <= regionMap[i][1]) { - return regionMap[i][2]; - } - } - return undefined; - }, - - 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 - }; - }, - - 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; - - if (!vertex) { - return; - } - if (spotRadius && highlightSpotColor) { - highlightSpot = target.drawCircle(vertex[0], vertex[1], - spotRadius, undefined, highlightSpotColor); - this.highlightSpotId = highlightSpot.id; - target.insertAfterShape(this.lastShapeId, highlightSpot); - } - 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); - } - }, - - 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; - } - }, - - 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'); - } - - 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 - hlSpotsEnabled = options.get('highlightSpotColor') && !options.get('disableInteraction'); - if (hlSpotsEnabled || options.get('minSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.miny)) { - canvasHeight -= Math.ceil(spotRadius); - } - if (hlSpotsEnabled || options.get('maxSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.maxy)) { - canvasHeight -= Math.ceil(spotRadius); - canvasTop += 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 (hlSpotsEnabled || options.get('spotColor') || - (options.get('minSpotColor') || options.get('maxSpotColor') && - (yvalues[yvallast] === this.miny || yvalues[yvallast] === this.maxy))) { - canvasWidth -= Math.ceil(spotRadius); - } - } - - - canvasHeight--; - - if (options.get('normalRangeMin') !== undefined && !options.get('drawNormalOnTop')) { - this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey); - } - - path = []; - paths = [path]; - last = next = null; - yvalcount = yvalues.length; - for (i = 0; i < yvalcount; i++) { - x = xvalues[i]; - xnext = xvalues[i + 1]; - y = yvalues[i]; - xpos = canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)); - xposnext = i < yvalcount - 1 ? canvasLeft + Math.round((xnext - this.minx) * (canvasWidth / rangex)) : canvasWidth; - next = xpos + ((xposnext - xpos) / 2); - regionMap[i] = [last || 0, next, i]; - last = next; - if (y === null) { - if (i) { - if (yvalues[i - 1] !== null) { - path = []; - paths.push(path); - } - vertices.push(null); - } - } else { - if (y < this.miny) { - y = this.miny; - } - if (y > this.maxy) { - y = this.maxy; - } - if (!path.length) { - // previous value was null - path.push([xpos, canvasTop + canvasHeight]); - } - vertex = [xpos, canvasTop + Math.round(canvasHeight - (canvasHeight * ((y - this.miny) / rangey)))]; - path.push(vertex); - vertices.push(vertex); - } - } - - lineShapes = []; - fillShapes = []; - plen = paths.length; - for (i = 0; i < plen; i++) { - path = paths[i]; - if (path.length) { - if (options.get('fillColor')) { - path.push([path[path.length - 1][0], (canvasTop + canvasHeight)]); - 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; i < plen; i++) { - target.drawShape(fillShapes[i], - options.get('fillColor'), options.get('fillColor')).append(); - } - - if (options.get('normalRangeMin') !== undefined && options.get('drawNormalOnTop')) { - this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey); - } - - plen = lineShapes.length; - for (i = 0; i < plen; i++) { - target.drawShape(lineShapes[i], options.get('lineColor'), undefined, - options.get('lineWidth')).append(); - } - - if (spotRadius && options.get('valueSpots')) { - valueSpots = options.get('valueSpots'); - if (valueSpots.get === undefined) { - valueSpots = new RangeMap(valueSpots); - } - for (i = 0; i < yvalcount; i++) { - color = valueSpots.get(yvalues[i]); - if (color) { - target.drawCircle(canvasLeft + Math.round((xvalues[i] - this.minx) * (canvasWidth / rangex)), - canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[i] - this.miny) / rangey))), - spotRadius, undefined, - color).append(); - } - } - - } - if (spotRadius && options.get('spotColor') && yvalues[yvallast] !== null) { - target.drawCircle(canvasLeft + Math.round((xvalues[xvalues.length - 1] - this.minx) * (canvasWidth / rangex)), - canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[yvallast] - this.miny) / rangey))), - spotRadius, undefined, - options.get('spotColor')).append(); - } - if (this.maxy !== this.minyorg) { - if (spotRadius && options.get('minSpotColor')) { - x = xvalues[$.inArray(this.minyorg, yvalues)]; - target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)), - canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.minyorg - this.miny) / rangey))), - spotRadius, undefined, - options.get('minSpotColor')).append(); - } - if (spotRadius && options.get('maxSpotColor')) { - x = xvalues[$.inArray(this.maxyorg, yvalues)]; - target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)), - canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.maxyorg - this.miny) / rangey))), - spotRadius, undefined, - options.get('maxSpotColor')).append(); - } - } - - this.lastShapeId = target.getLastShapeId(); - this.canvasTop = canvasTop; - target.render(); - } - }); - - /** - * Bar charts - */ - $.fn.sparkline.bar = bar = createClass($.fn.sparkline._base, barHighlightMixin, { - type: 'bar', - - 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); - - // 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(':')); - } - 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 (groupMax > stackMax) { - stackMax = groupMax; - } - } - } - - 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 = tristate = createClass($.fn.sparkline._base, barHighlightMixin, { - type: 'tristate', - - 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); - - 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); - - 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.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 = discrete = createClass($.fn.sparkline._base, barHighlightMixin, { - type: 'discrete', - - init: function (el, values, options, width, height) { - discrete._super.init.call(this, el, values, options, width, height); - - 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'); - } - 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 = bullet = createClass($.fn.sparkline._base, { - type: 'bullet', - - 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'); - } - this.min = min; - this.max = max; - this.range = max - min; - this.shapes = {}; - this.valueShapes = {}; - this.regiondata = {}; - this.width = width = options.get('width') === 'auto' ? '4.0em' : width; - this.target = this.$el.simpledraw(width, height, options.get('composite')); - if (!values.length) { - this.disabled = true; - } - this.initTarget(); - }, - - 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 { - fieldkey: currentRegion.substr(0, 1), - value: this.values[currentRegion.substr(1)], - region: currentRegion - }; - }, - - changeHighlight: function (highlight) { - var currentRegion = this.currentRegion, - shapeid = this.valueShapes[currentRegion], - shape; - delete this.shapes[shapeid]; - switch (currentRegion.substr(0, 1)) { - case 'r': - shape = this.renderRange(currentRegion.substr(1), highlight); - break; - case 'p': - shape = this.renderPerformance(highlight); - break; - case 't': - shape = this.renderTarget(highlight); - break; - } - this.valueShapes[currentRegion] = shape.id; - this.shapes[shape.id] = currentRegion; - this.target.replaceWithShape(shapeid, shape); - }, - - renderRange: function (rn, highlight) { - var rangeval = this.values[rn], - rangewidth = Math.round(this.canvasWidth * ((rangeval - this.min) / this.range)), - color = this.options.get('rangeColors')[rn - 2]; - if (highlight) { - color = this.calcHighlightColor(color, this.options); - } - return this.target.drawRect(0, 0, rangewidth - 1, this.canvasHeight - 1, color, color); - }, - - renderPerformance: function (highlight) { - var perfval = this.values[1], - perfwidth = Math.round(this.canvasWidth * ((perfval - this.min) / this.range)), - color = this.options.get('performanceColor'); - if (highlight) { - color = this.calcHighlightColor(color, this.options); - } - return this.target.drawRect(0, Math.round(this.canvasHeight * 0.3), perfwidth - 1, - Math.round(this.canvasHeight * 0.4) - 1, color, color); - }, - - renderTarget: function (highlight) { - var targetval = this.values[0], - x = Math.round(this.canvasWidth * ((targetval - this.min) / this.range) - (this.options.get('targetWidth') / 2)), - targettop = Math.round(this.canvasHeight * 0.10), - targetheight = this.canvasHeight - (targettop * 2), - color = this.options.get('targetColor'); - if (highlight) { - color = this.calcHighlightColor(color, this.options); - } - return this.target.drawRect(x, targettop, this.options.get('targetWidth') - 1, targetheight - 1, color, color); - }, - - render: function () { - var vlen = this.values.length, - target = this.target, - i, shape; - if (!bullet._super.render.call(this)) { - return; - } - for (i = 2; i < vlen; i++) { - shape = this.renderRange(i).append(); - this.shapes[shape.id] = 'r' + i; - this.valueShapes['r' + i] = shape.id; - } - if (this.values[1] !== null) { - shape = this.renderPerformance().append(); - this.shapes[shape.id] = 'p1'; - this.valueShapes.p1 = shape.id; - } - if (this.values[0] !== null) { - shape = this.renderTarget().append(); - this.shapes[shape.id] = 't0'; - this.valueShapes.t0 = shape.id; - } - target.render(); - } - }); - - /** - * Pie charts - */ - $.fn.sparkline.pie = pie = createClass($.fn.sparkline._base, { - type: 'pie', - - init: function (el, values, options, width, height) { - var total = 0, 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 (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); - } - 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 - */ - $.fn.sparkline.box = box = createClass($.fn.sparkline._base, { - type: 'box', - - 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; - } - }, - - /** - * 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 (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]; - } else { - lwhisker = values[0]; - q1 = values[1]; - q2 = values[2]; - q3 = values[3]; - rwhisker = values[4]; - } - } else { - values.sort(function (a, b) { return a - b; }); - q1 = quartile(values, 1); - q2 = quartile(values, 2); - q3 = quartile(values, 3); - iqr = q3 - q1; - if (options.get('showOutliers')) { - 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'))) { - rwhisker = values[i]; - } - } - loutlier = values[0]; - routlier = values[vlen - 1]; - } else { - lwhisker = values[0]; - rwhisker = values[vlen - 1]; - } - } - this.quartiles = [q1, q2, q3]; - this.lwhisker = lwhisker; - this.rwhisker = rwhisker; - this.loutlier = loutlier; - this.routlier = routlier; - - unitSize = canvasWidth / (maxValue - minValue + 1); - if (options.get('showOutliers')) { - 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 + canvasLeft, - canvasHeight / 2, - options.get('spotRadius'), - options.get('outlierLineColor'), - options.get('outlierFillColor')).append(); - } - if (routlier > rwhisker) { - 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 + 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 + 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 + 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 + 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 + 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 + 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')) { - size = Math.ceil(options.get('spotRadius')); - target.drawLine( - 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 + canvasLeft - size), - Math.round(canvasHeight / 2), - Math.round((options.get('target') - minValue) * unitSize + canvasLeft + size), - Math.round(canvasHeight / 2), - options.get('targetColor')).append(); - } - target.render(); - } - }); - - // Setup a very simple "virtual canvas" to make drawing the few shapes we need easier - // This is accessible as $(foo).simpledraw() - - // Detect browser renderer support - (function() { - if (document.namespaces && !document.namespaces.v) { - $.fn.sparkline.hasVML = true; - document.namespaces.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML'); - } else { - $.fn.sparkline.hasVML = false; - } - - var el = document.createElement('canvas'); - $.fn.sparkline.hasCanvas = !!(el.getContext && el.getContext('2d')); - - })() - - 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 = 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]; - } - $.data(target, '_jqs_vcanvas', this); - }, - - 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, lineWidth) { - return this._genShape('Circle', [x, y, radius, lineColor, fillColor, lineWidth]); - }, - - 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) { - return this._genShape('Rect', [x, y, width, height, lineColor, fillColor]); - }, - - getElement: function () { - return this.canvas; - }, - - /** - * Return the most recently inserted shape id - */ - getLastShapeId: function () { - return this.lastShapeId; - }, - - /** - * 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; - if (fillColor !== undefined) { - context.fillStyle = fillColor; - } - return context; - }, - - 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.arc(x, y, radius, 0, 2 * Math.PI, false); - if (this.targetX !== undefined && this.targetY !== undefined && - context.isPointInPath(this.targetX, this.targetY)) { - this.currentTargetShapeId = shapeid; - } - if (lineColor !== undefined) { - context.stroke(); - } - if (fillColor !== undefined) { - context.fill(); - } - }, - - _drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) { - var context = this._getContext(lineColor, fillColor); - context.beginPath(); - context.moveTo(x, y); - context.arc(x, y, radius, startAngle, endAngle, false); - context.lineTo(x, y); - context.closePath(); - if (lineColor !== undefined) { - context.stroke(); - } - if (fillColor) { - context.fill(); - } - if (this.targetX !== undefined && this.targetY !== undefined && - context.isPointInPath(this.targetX, this.targetY)) { - this.currentTargetShapeId = shapeid; - } - }, - - _drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) { - return this._drawShape(shapeid, [[x, y], [x + width, y], [x + width, y + height], [x, y + height], [x, y]], lineColor, fillColor); - }, - - appendShape: function (shape) { - this.shapes[shape.id] = shape; - this.shapeseq.push(shape.id); - this.lastShapeId = shape.id; - return shape.id; - }, - - replaceWithShape: function (shapeid, shape) { - var shapeseq = this.shapeseq, - i; - this.shapes[shape.id] = shape; - for (i = shapeseq.length; i--;) { - if (shapeseq[i] == shapeid) { - shapeseq[i] = shape.id; - } - } - delete this.shapes[shapeid]; - }, - - replaceWithShapes: function (shapeids, shapes) { - var shapeseq = this.shapeseq, - shapemap = {}, - sid, i, first; - - for (i = shapeids.length; i--;) { - shapemap[shapeids[i]] = true; - } - for (i = shapeseq.length; i--;) { - sid = shapeseq[i]; - if (shapemap[sid]) { - shapeseq.splice(i, 1); - delete this.shapes[sid]; - first = i; - } - } - for (i = shapes.length; i--;) { - shapeseq.splice(first, 0, shapes[i].id); - this.shapes[shapes[i].id] = shapes[i]; - } - - }, - - insertAfterShape: function (shapeid, shape) { - var shapeseq = this.shapeseq, - i; - for (i = shapeseq.length; i--;) { - if (shapeseq[i] === shapeid) { - shapeseq.splice(i + 1, 0, shape.id); - this.shapes[shape.id] = shape; - return; - } - } - }, - - removeShapeId: function (shapeid) { - var shapeseq = this.shapeseq, - i; - for (i = shapeseq.length; i--;) { - if (shapeseq[i] === shapeid) { - shapeseq.splice(i, 1); - break; - } - } - delete this.shapes[shapeid]; - }, - - getShapeAt: function (el, x, y) { - this.targetX = x; - this.targetY = y; - this.render(); - return this.currentTargetShapeId; - }, - - render: function () { - var shapeseq = this.shapeseq, - shapes = this.shapes, - shapeCount = shapeseq.length, - context = this._getContext(), - shapeid, shape, i; - context.clearRect(0, 0, this.pixelWidth, this.pixelHeight); - for (i = 0; i < shapeCount; i++) { - shapeid = shapeseq[i]; - shape = shapes[shapeid]; - this['_draw' + shape.type].apply(this, shape.args); - } - if (!this.interact) { - // not interactive so no need to keep the shapes array - this.shapes = {}; - this.shapeseq = []; - } - } - - }); - - VCanvas_vml = createClass(VCanvas_base, { - init: function (width, height, target) { - var groupel; - VCanvas_vml._super.init.call(this, width, height, target); - if (target[0]) { - target = target[0]; - } - $.data(target, '_jqs_vcanvas', this); - this.canvas = document.createElement('span'); - $(this.canvas).css({ display: 'inline-block', position: 'relative', overflow: 'hidden', width: width, height: height, margin: '0px', padding: '0px', verticalAlign: 'top'}); - this._insert(this.canvas, target); - 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 (shapeid, path, lineColor, fillColor, lineWidth) { - var vpath = [], - initial, stroke, fill, closed, vel, plen, i; - for (i = 0, plen = path.length; i < plen; i++) { - vpath[i] = '' + (path[i][0]) + ',' + (path[i][1]); - } - initial = vpath.splice(0, 1); - lineWidth = lineWidth === undefined ? 1 : lineWidth; - stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth + 'px" strokeColor="' + lineColor + '" '; - fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" '; - closed = vpath[0] === vpath[vpath.length - 1] ? 'x ' : ''; - vel = '' + - ' '; - return vel; - }, - - _drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) { - var stroke, fill, vel; - x -= radius; - y -= radius; - stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth + 'px" strokeColor="' + lineColor + '" '; - fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" '; - vel = ''; - return vel; - - }, - - _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)) { - startAngle = 0.0; // VML seems to have a problem when drawing a full circle that doesn't start 0 - endAngle = (2 * Math.PI); - } - - 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); - - if (startx === endx && starty === endy) { - if ((endAngle - startAngle) < Math.PI) { - // Prevent very small slices from being mistaken as a whole pie - return ''; - } - // essentially going to be the entire circle, so ignore startAngle - startx = endx = x + radius; - starty = endy = y; - } - - if (startx === endx && starty === endy && (endAngle - startAngle) < Math.PI) { - return ''; - } - - vpath = [x - radius, y - radius, x + radius, y + radius, startx, starty, endx, endy]; - stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="1px" strokeColor="' + lineColor + '" '; - fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" '; - vel = '' + - ' '; - return vel; - }, - - _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; - } - } - }); - -})); diff --git a/addons/crm/static/src/js/crm_case_section.js b/addons/crm/static/src/js/crm_case_section.js index 2b4ca741d75..e027313d6dc 100644 --- a/addons/crm/static/src/js/crm_case_section.js +++ b/addons/crm/static/src/js/crm_case_section.js @@ -8,27 +8,4 @@ openerp.crm = function(openerp) { } }, }); - - openerp.crm.SparklineBarWidget = openerp.web_kanban.AbstractField.extend({ - className: "oe_sparkline_bar", - start: function() { - var self = this; - var title = this.$node.html(); - setTimeout(function () { - var value = _.pluck(self.field.value, 'value'); - var tooltips = _.pluck(self.field.value, 'tooltip'); - self.$el.sparkline(value, { - type: 'bar', - barWidth: 5, - tooltipFormat: '{{offset:offset}} {{value}}', - tooltipValueLookups: { - 'offset': tooltips - }, - }); - self.$el.tipsy({'delayIn': 0, 'html': true, 'title': function(){return title}, 'gravity': 'n'}); - }, 0); - }, - }); - openerp.web_kanban.fields_registry.add("sparkline_bar", "openerp.crm.SparklineBarWidget"); - }; diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index e5cb2b56eb3..73fb148c0ed 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -63,12 +63,16 @@ class mail_mail(osv.Model): 'notification': fields.boolean('Is Notification', help='Mail has been created to notify people of an existing mail.message'), # Bounce and tracking - 'opened': fields.integer( + 'opened': fields.datetime( 'Opened', - help='Number of times this email has been seen, using the OpenERP tracking.'), - 'replied': fields.integer( - 'Reply Received', - help='If checked, a reply to this email has been received.'), + help='Date when this email has been opened for the first time.'), + 'replied': fields.datetime( + 'Replied', + help='Date when this email has been replied for the first time.'), + 'bounced': fields.datetime( + 'Bounced', + help='Date when this email has bounced.' + ), } _defaults = { @@ -103,15 +107,24 @@ class mail_mail(osv.Model): return self.write(cr, uid, ids, {'state': 'cancel'}, context=context) def set_opened(self, cr, uid, ids, context=None): - """ Increment opened counter """ + """ Set as opened """ for mail in self.browse(cr, uid, ids, context=context): - self.write(cr, uid, [mail.id], {'opened': (mail.opened + 1)}, context=context) + if not mail.opened: + self.write(cr, uid, [mail.id], {'opened': fields.datetime.now()}, context=context) return True def set_replied(self, cr, uid, ids, context=None): - """ Increment replied counter """ + """ Set as replied """ for mail in self.browse(cr, uid, ids, context=context): - self.write(cr, uid, [mail.id], {'replied': (mail.replied + 1)}, context=context) + if not mail.replied: + self.write(cr, uid, [mail.id], {'replied': fields.datetime.now()}, context=context) + return True + + def set_bounced(self, cr, uid, ids, context=None): + """ Set as bounced """ + for mail in self.browse(cr, uid, ids, context=context): + if not mail.bounced: + self.write(cr, uid, [mail.id], {'bounced': fields.datetime.now()}, context=context) return True def process_email_queue(self, cr, uid, ids=None, context=None): diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 9943114a07a..9a5d3e3580d 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -1426,8 +1426,7 @@ class mail_thread(osv.AbstractModel): # update original mail_mail if exists if type == 'email': mail_mail_ids = self.pool['mail.mail'].search(cr, SUPERUSER_ID, [('mail_message_id', '=', parent_id)], context=context) - for mail in self.pool['mail.mail'].browse(cr, SUPERUSER_ID, mail_mail_ids, context=context): - self.pool['mail.mail'].write(cr, SUPERUSER_ID, [mail.id], {'replied': mail.replied + 1}, context=context) + self.pool['mail.mail'].set_replied(cr, SUPERUSER_ID, mail_mail_ids, context=context) message_ids = mail_message.search(cr, SUPERUSER_ID, [('id', '=', parent_id), ('parent_id', '!=', False)], context=context) # avoid loops when finding ancestors diff --git a/addons/mail/tests/__init__.py b/addons/mail/tests/__init__.py index 242beb60bf1..ff075d73d73 100644 --- a/addons/mail/tests/__init__.py +++ b/addons/mail/tests/__init__.py @@ -19,9 +19,10 @@ # ############################################################################## -from . import test_mail_group, test_mail_message, test_mail_features, test_mail_gateway, test_message_read, test_invite +from . import test_mail_mail, test_mail_group, test_mail_message, test_mail_features, test_mail_gateway, test_message_read, test_invite checks = [ + # test_mail_mail, test_mail_group, test_mail_message, test_mail_features, diff --git a/addons/mass_mailing/__openerp__.py b/addons/mass_mailing/__openerp__.py index e6fbb42e886..9bb5b746f63 100644 --- a/addons/mass_mailing/__openerp__.py +++ b/addons/mass_mailing/__openerp__.py @@ -29,10 +29,16 @@ 'description': """TODO""", 'data': [ 'mass_mailing_view.xml', + 'mass_mailing_demo.xml', 'mail_mail_view.xml', 'wizard/mail_compose_message_view.xml', 'security/ir.model.access.csv', ], + 'js': [], + 'qweb': [], + 'css': [ + 'static/src/css/mass_mailing.css' + ], 'demo': [], 'installable': True, 'auto_install': False, diff --git a/addons/mass_mailing/mail_mail.py b/addons/mass_mailing/mail_mail.py index d7f46446b56..529c6c73e7b 100644 --- a/addons/mass_mailing/mail_mail.py +++ b/addons/mass_mailing/mail_mail.py @@ -28,8 +28,14 @@ class MailMail(osv.Model): _inherit = ['mail.mail'] _columns = { - 'mass_mailing_campaign_id': fields.many2one( - 'mail.mass_mailing.campaign', 'Mass Mailing Campaign', + 'mass_mailing_segment_id': fields.many2one( + 'mail.mass_mailing.segment', 'Mass Mailing Segment', ondelete='set null', ), + 'mass_mailing_campaign_id': fields.related( + 'mass_mailing_segment_id', 'mass_mailing_campaign_id', + type='many2one', ondelete='set null', + relation='mail.mass_mailing.campaign', + store=True, readonly=True, + ), } diff --git a/addons/mass_mailing/mail_mail_view.xml b/addons/mass_mailing/mail_mail_view.xml index dd66aede66f..957ee0f6f93 100644 --- a/addons/mass_mailing/mail_mail_view.xml +++ b/addons/mass_mailing/mail_mail_view.xml @@ -10,6 +10,7 @@ + diff --git a/addons/mass_mailing/mass_mailing.py b/addons/mass_mailing/mass_mailing.py index 91392759b0e..f37f86e385a 100644 --- a/addons/mass_mailing/mass_mailing.py +++ b/addons/mass_mailing/mass_mailing.py @@ -19,6 +19,10 @@ # ############################################################################## +from datetime import date, datetime +from dateutil import relativedelta + +from openerp import tools from openerp.osv import osv, fields @@ -32,33 +36,42 @@ class MassMailingCampaign(osv.Model): """ Compute statistics of the mass mailing campaign """ results = dict.fromkeys(ids, False) for campaign in self.browse(cr, uid, ids, context=context): - if not campaign.mail_ids: - results[campaign.id] = { - 'sent': 0, - 'opened_ratio': 0.0, - 'replied_ratio': 0.0, - 'bounce_ratio': 0.0, - } - continue results[campaign.id] = { 'sent': len(campaign.mail_ids), - 'opened_ratio': len([mail for mail in campaign.mail_ids if mail.opened]) * 1.0 / len(campaign.mail_ids), - 'replied_ratio': len([mail for mail in campaign.mail_ids if mail.replied]) * 1.0 / len(campaign.mail_ids), - 'bounce_ratio': 0.0, + 'opened': len([mail for mail in campaign.mail_ids if mail.opened]), + 'replied': len([mail for mail in campaign.mail_ids if mail.replied]), + 'bounced': len([mail for mail in campaign.mail_ids if mail.bounced]), } return results + def _get_segment_kanban_ids(self, cr, uid, ids, name, arg, context=None): + results = dict.fromkeys(ids, '') + for campaign in self.browse(cr, uid, ids, context=context): + segment_results = [] + for segment in campaign.segment_ids: + segment_object = {} + for attr in ['name', 'sent', 'opened', 'replied', 'bounced']: + segment_object[attr] = getattr(segment, attr) + segment_results.append(segment_object) + results[campaign.id] = segment_results + return results + _columns = { 'name': fields.char( 'Campaign Name', required=True, ), - 'template_id': fields.many2one( - 'email.template', 'Email Template', - ondelete='set null', + 'segment_ids': fields.one2many( + 'mail.mass_mailing.segment', 'mass_mailing_campaign_id', + 'Segments', + ), + 'segment_kanban_ids': fields.function( + _get_segment_kanban_ids, + type='text', string='Segments (kanban data)', + help='This field has for purpose to gather data about segment to display them in kanban view as nested kanban views is not possible currently', ), 'mail_ids': fields.one2many( 'mail.mail', 'mass_mailing_campaign_id', - 'Send Emails', + 'Sent Emails', ), # stat fields 'sent': fields.function( @@ -66,19 +79,132 @@ class MassMailingCampaign(osv.Model): string='Sent Emails', type='integer', multi='_get_statistics' ), - 'opened_ratio': fields.function( + 'opened': fields.function( _get_statistics, - string='Opened Ratio', - type='float', multi='_get_statistics', + string='Opened', + type='integer', multi='_get_statistics', ), - 'replied_ratio': fields.function( + 'replied': fields.function( _get_statistics, - string='Replied Ratio', - type='float', multi='_get_statistics' + string='Replied', + type='integer', multi='_get_statistics' ), - 'bounce_ratio': fields.function( + 'bounced': fields.function( _get_statistics, - string='Bounce Ratio', - type='float', multi='_get_statistics' + string='Bounced', + type='integer', multi='_get_statistics' + ), + } + + +class MassMailingSegment(osv.Model): + """ TODO """ + _name = 'mail.mass_mailing.segment' + _description = 'Segment of a mass mailing campaign' + # number of periods for tracking mail_mail statistics + _period_number = 6 + + def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, context=None): + """ Generic method to generate data for bar chart values using SparklineBarWidget. + This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field). + + :param obj: the target model (i.e. crm_lead) + :param domain: the domain applied to the read_group + :param list read_fields: the list of fields to read in the read_group + :param str value_field: the field used to compute the value of the bar slice + :param str groupby_field: the fields used to group + + :return list section_result: a list of dicts: [ + { 'value': (int) bar_column_value, + 'tootip': (str) bar_column_tooltip, + } + ] + """ + month_begin = date.today().replace(day=1) + section_result = [{'value': 0, + 'tooltip': (month_begin + relativedelta.relativedelta(months=-i)).strftime('%B'), + } for i in range(self._period_number - 1, -1, -1)] + group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context) + print group_obj + for group in group_obj: + group_begin_date = datetime.strptime(group['__domain'][0][2], tools.DEFAULT_SERVER_DATE_FORMAT) + month_delta = relativedelta.relativedelta(month_begin, group_begin_date) + section_result[self._period_number - (month_delta.months + 1)] = {'value': group.get(value_field, 0), 'tooltip': group_begin_date.strftime('%B')} + return section_result + + def _get_monthly_statistics(self, cr, uid, ids, field_name, arg, context=None): + """ TODO + """ + obj = self.pool.get('mail.mail') + res = dict.fromkeys(ids, False) + month_begin = date.today().replace(day=1) + groupby_begin = (month_begin + relativedelta.relativedelta(months=-4)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) + for id in ids: + res[id] = dict() + domain = [('mass_mailing_segment_id', '=', id), ('opened', '>=', groupby_begin)] + res[id]['opened_monthly'] = self.__get_bar_values(cr, uid, obj, domain, ['opened'], 'opened_count', 'opened', context=context) + domain = [('mass_mailing_segment_id', '=', id), ('replied', '>=', groupby_begin)] + res[id]['replied_monthly'] = self.__get_bar_values(cr, uid, obj, domain, ['replied'], 'replied_count', 'replied', context=context) + return res + + def _get_statistics(self, cr, uid, ids, name, arg, context=None): + """ Compute statistics of the mass mailing campaign """ + results = dict.fromkeys(ids, False) + for segment in self.browse(cr, uid, ids, context=context): + results[segment.id] = { + 'sent': len(segment.mail_ids), + 'opened': len([mail for mail in segment.mail_ids if mail.opened]), + 'replied': len([mail for mail in segment.mail_ids if mail.replied]), + 'bounced': len([mail for mail in segment.mail_ids if mail.bounced]), + } + return results + + _columns = { + 'name': fields.char('Name', required=True), + 'mass_mailing_campaign_id': fields.many2one( + 'mail.mass_mailing.campaign', 'Mass Mailing Campaign', + ondelete='cascade', + ), + 'template_id': fields.many2one( + 'email.template', 'Email Template', + ondelete='set null', + ), + 'domain': fields.char('Domain'), + 'date': fields.datetime('Date'), + # mail_mail data + 'mail_ids': fields.one2many( + 'mail.mail', 'mass_mailing_segment_id', + 'Send Emails', + ), + 'sent': fields.function( + _get_statistics, + string='Sent Emails', + type='integer', multi='_get_statistics' + ), + 'opened': fields.function( + _get_statistics, + string='Opened', + type='integer', multi='_get_statistics', + ), + 'replied': fields.function( + _get_statistics, + string='Replied', + type='integer', multi='_get_statistics' + ), + 'bounced': fields.function( + _get_statistics, + string='Bounce', + type='integer', multi='_get_statistics' + ), + # monthly ratio + 'opened_monthly': fields.function( + _get_monthly_statistics, + string='Sent Emails', + type='char', multi='_get_monthly_statistics', + ), + 'replied_monthly': fields.function( + _get_monthly_statistics, + string='Replied', + type='char', multi='_get_monthly_statistics', ), } diff --git a/addons/mass_mailing/mass_mailing_demo.xml b/addons/mass_mailing/mass_mailing_demo.xml new file mode 100644 index 00000000000..3e04baf7cef --- /dev/null +++ b/addons/mass_mailing/mass_mailing_demo.xml @@ -0,0 +1,81 @@ + + + + + + + Partner Newsletter 1 + + + ${object.id} + Hello

]]>
+
+ + Partner Newsletter 2 + + + ${object.id} + Hello

]]>
+
+ + + Partners Newsletter + + + + First Newsletter + + + + + + Second Newsletter + + + + + + + + + + sent + + + + + + sent + + + + + sent + + + + sent + + + + + sent + + + + + + sent + + + + + sent + + + + sent + + +
+
diff --git a/addons/mass_mailing/mass_mailing_view.xml b/addons/mass_mailing/mass_mailing_view.xml index bc28827343d..b847a89db53 100644 --- a/addons/mass_mailing/mass_mailing_view.xml +++ b/addons/mass_mailing/mass_mailing_view.xml @@ -3,7 +3,7 @@ - + mail.mass_mailing.campaign.tree mail.mass_mailing.campaign 10 @@ -14,7 +14,7 @@ - + mail.mass_mailing.campaign.form mail.mass_mailing.campaign @@ -22,27 +22,188 @@ - - - - + + + + + + + mail.mass_mailing.campaign.kanban + mail.mass_mailing.campaign + + + + + + +
+

+
+
+

+
+ Sent +

+

+
+ Opened +

+

+
+ Replied +

+

+
+ Bounced +

+
+
+ + +
+
+

+ +

+
+ + +
+ + + +
+
+
+
+
+
+
+
+ Mass Mailing Campaigns mail.mass_mailing.campaign form - tree,form + kanban,tree,form + + + + + mail.mass_mailing.segment.tree + mail.mass_mailing.segment + 10 + + + + + + + + + mail.mass_mailing.segment.form + mail.mass_mailing.segment + +
+ + + + + + + + +
+
+
+ + + mail.mass_mailing.segment.kanban + mail.mass_mailing.segment + + + + +
+
+
+

+ +

+

+ Sent:
+ Campaign: +

+
+
+

+
+ Sent +

+

+
+ Opened +

+

+
+ Replied +

+

+
+ Bounced +

+
+
+
+

Opened


+ +
+
+

Replied


+ +
+
+
+
+
+
+
+
+
+
+ + + Mass Mailing Segments + mail.mass_mailing.segment + form + kanban,tree,form + + + + + + + +
diff --git a/addons/mass_mailing/static/src/css/mass_mailing.css b/addons/mass_mailing/static/src/css/mass_mailing.css new file mode 100644 index 00000000000..8b217d8d588 --- /dev/null +++ b/addons/mass_mailing/static/src/css/mass_mailing.css @@ -0,0 +1,54 @@ +.openerp .oe_kanban_view .oe_kanban_mass_mailing.oe_kanban_mass_mailing_campaign { + width: 270px; +} + +.openerp .oe_kanban_view .oe_kanban_mass_mailing.oe_kanban_mass_mailing_segment { + width: 270px; +} + +.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_mail_stats { + width: 120px; + display: inline-block; + margin: 2px 5px 0px 5px; + text-align: center; + border: 1px solid rgba(0, 0, 0, 0.16); + -webkit-border-radius: 2px; + border-radius: 2px; +} + +.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_mail_result { + font-weight: bold; +} + +.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_gauge { + width: 120px; + height: 120px; + display: inline-block; +} + +.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_kanban_content div.oe_sparkline_container { + height: 60px; + width: 120px; + display: inline-block; + margin: 8px 5px 0px 5px; +} + +.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_sparkline_bar_title { + text-align: center; + display: inline; +} + +.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_sparkline_bar { + width: 100px; + height: 60px; + display: inline-block; +} + +/* + * Campaign related CSS + */ + + +/* + * Segment related CSS + */ diff --git a/addons/sale_crm/__openerp__.py b/addons/sale_crm/__openerp__.py index 7db1aa1eff0..0cc4ad3fad2 100644 --- a/addons/sale_crm/__openerp__.py +++ b/addons/sale_crm/__openerp__.py @@ -48,7 +48,6 @@ modules. 'report/sale_crm_account_invoice_report_view.xml', ], 'js': [ - 'static/lib/justgage.js', 'static/src/js/sale_crm.js', ], 'demo': ['sale_crm_demo.xml'], diff --git a/addons/sale_crm/sale_crm_view.xml b/addons/sale_crm/sale_crm_view.xml index 63ad9fb980e..b0bfa85163c 100644 --- a/addons/sale_crm/sale_crm_view.xml +++ b/addons/sale_crm/sale_crm_view.xml @@ -268,8 +268,8 @@
- Invoiced - Forecast + Invoiced + Forecast

Define an invoicing target in the sales team settings to see the period's achievement and forecast at a glance. diff --git a/addons/sale_crm/static/lib/justgage.js b/addons/sale_crm/static/lib/justgage.js deleted file mode 100644 index 52b5c6ea0c3..00000000000 --- a/addons/sale_crm/static/lib/justgage.js +++ /dev/null @@ -1,883 +0,0 @@ -/** - * JustGage - this is work-in-progress, unreleased, unofficial code, so it might not work top-notch :) - * Check http://www.justgage.com for official releases - * Licensed under MIT. - * @author Bojan Djuricic (@Toorshia) - * - * LATEST UPDATES - - * ----------------------------- - * April 01, 2013. - * ----------------------------- - * fix - https://github.com/toorshia/justgage/issues/46 - - * ----------------------------- - * March 26, 2013. - * ----------------------------- - * customSectors - define specific color for value range (0-10 : red, 10-30 : blue etc.) - - * ----------------------------- - * March 23, 2013. - * ----------------------------- - * counter - option to animate value in counting fashion - * fix - https://github.com/toorshia/justgage/issues/45 - - * ----------------------------- - * March 13, 2013. - * ----------------------------- - * refresh method - added optional 'max' parameter to use when you need to update max value - - * ----------------------------- - * February 26, 2013. - * ----------------------------- - * decimals - option to define/limit number of decimals when not using humanFriendly or customRenderer to display value - * fixed a missing parameters bug when calling generateShadow() for IE < 9 - - * ----------------------------- - * December 31, 2012. - * ----------------------------- - * fixed text y-position for hidden divs - workaround for Raphael 'dy' bug - https://github.com/DmitryBaranovskiy/raphael/issues/491 - * 'show' parameters, like showMinMax are now 'hide' because I am lame developer - please update these in your setups - * Min and Max labels are now auto-off when in donut mode - * Start angle in donut mode is now 90 - * donutStartAngle - option to define start angle for donut - - * ----------------------------- - * November 25, 2012. - * ----------------------------- - * Option to define custom rendering function for displayed value - - * ----------------------------- - * November 19, 2012. - * ----------------------------- - * Config.value is now updated after gauge refresh - - * ----------------------------- - * November 13, 2012. - * ----------------------------- - * Donut display mode added - * Option to hide value label - * Option to enable responsive gauge size - * Removed default title attribute - * Option to accept min and max defined as string values - * Option to configure value symbol - * Fixed bad aspect ratio calculations - * Option to configure minimum font size for all texts - * Option to show shorthand big numbers (human friendly) - */ - - JustGage = function(config) { - - if (!config.id) {alert("Missing id parameter for gauge!"); return false;} - if (!config.node) { - if (!document.getElementById(config.id)) {alert("No element with id: \""+config.id+"\" found!"); return false;} - config.node = document.getElementById(config.id); - } - - var obj = this; - - // configurable parameters - obj.config = - { - // id : string - // this is container element id - id : config.id, - - // node : string - // the node to use instead of DOM - node : config.node, - - // title : string - // gauge title - title : (config.title) ? config.title : "", - - // titleFontColor : string - // color of gauge title - titleFontColor : (config.titleFontColor) ? config.titleFontColor : "#999999", - - // value : int - // value gauge is showing - value : (config.value) ? config.value : 0, - - - // valueFontColor : string - // color of label showing current value - valueFontColor : (config.valueFontColor) ? config.valueFontColor : "#010101", - - // symbol : string - // special symbol to show next to value - symbol : (config.symbol) ? config.symbol : "", - - // min : int - // min value - min : (config.min) ? parseFloat(config.min) : 0, - - // max : int - // max value - max : (config.max) ? parseFloat(config.max) : 100, - - // humanFriendlyDecimal : int - // number of decimal places for our human friendly number to contain - humanFriendlyDecimal : (config.humanFriendlyDecimal) ? config.humanFriendlyDecimal : 0, - - // textRenderer: func - // function applied before rendering text - textRenderer : (config.textRenderer) ? config.textRenderer : null, - - // gaugeWidthScale : float - // width of the gauge element - gaugeWidthScale : (config.gaugeWidthScale) ? config.gaugeWidthScale : 1.0, - - // gaugeColor : string - // background color of gauge element - gaugeColor : (config.gaugeColor) ? config.gaugeColor : "#edebeb", - - // label : string - // text to show below value - label : (config.label) ? config.label : "", - - // labelFontColor : string - // color of label showing label under value - labelFontColor : (config.labelFontColor) ? config.labelFontColor : "#b3b3b3", - - // shadowOpacity : int - // 0 ~ 1 - shadowOpacity : (config.shadowOpacity) ? config.shadowOpacity : 0.2, - - // shadowSize: int - // inner shadow size - shadowSize : (config.shadowSize) ? config.shadowSize : 5, - - // shadowVerticalOffset : int - // how much shadow is offset from top - shadowVerticalOffset : (config.shadowVerticalOffset) ? config.shadowVerticalOffset : 3, - - // levelColors : string[] - // colors of indicator, from lower to upper, in RGB format - levelColors : (config.levelColors) ? config.levelColors : [ - "#a9d70b", - "#f9c802", - "#ff0000" - ], - - // startAnimationTime : int - // length of initial animation - startAnimationTime : (config.startAnimationTime) ? config.startAnimationTime : 700, - - // startAnimationType : string - // type of initial animation (linear, >, <, <>, bounce) - startAnimationType : (config.startAnimationType) ? config.startAnimationType : ">", - - // refreshAnimationTime : int - // length of refresh animation - refreshAnimationTime : (config.refreshAnimationTime) ? config.refreshAnimationTime : 700, - - // refreshAnimationType : string - // type of refresh animation (linear, >, <, <>, bounce) - refreshAnimationType : (config.refreshAnimationType) ? config.refreshAnimationType : ">", - - // donutStartAngle : int - // angle to start from when in donut mode - donutStartAngle : (config.donutStartAngle) ? config.donutStartAngle : 90, - - // valueMinFontSize : int - // absolute minimum font size for the value - valueMinFontSize : config.valueMinFontSize || 16, - - // titleMinFontSize - // absolute minimum font size for the title - titleMinFontSize : config.titleMinFontSize || 10, - - // labelMinFontSize - // absolute minimum font size for the label - labelMinFontSize : config.labelMinFontSize || 10, - - // minLabelMinFontSize - // absolute minimum font size for the minimum label - minLabelMinFontSize : config.minLabelMinFontSize || 10, - - // maxLabelMinFontSize - // absolute minimum font size for the maximum label - maxLabelMinFontSize : config.maxLabelMinFontSize || 10, - - // hideValue : bool - // hide value text - hideValue : (config.hideValue) ? config.hideValue : false, - - // hideMinMax : bool - // hide min and max values - hideMinMax : (config.hideMinMax) ? config.hideMinMax : false, - - // hideInnerShadow : bool - // hide inner shadow - hideInnerShadow : (config.hideInnerShadow) ? config.hideInnerShadow : false, - - // humanFriendly : bool - // convert large numbers for min, max, value to human friendly (e.g. 1234567 -> 1.23M) - humanFriendly : (config.humanFriendly) ? config.humanFriendly : false, - - // noGradient : bool - // whether to use gradual color change for value, or sector-based - noGradient : (config.noGradient) ? config.noGradient : false, - - // donut : bool - // show full donut gauge - donut : (config.donut) ? config.donut : false, - - // relativeGaugeSize : bool - // whether gauge size should follow changes in container element size - relativeGaugeSize : (config.relativeGaugeSize) ? config.relativeGaugeSize : false, - - // counter : bool - // animate level number change - counter : (config.counter) ? config.counter : false, - - // decimals : int - // number of digits after floating point - decimals : (config.decimals) ? config.decimals : 0, - - // customSectors : [] of objects - // number of digits after floating point - customSectors : (config.customSectors) ? config.customSectors : [] - }; - - // variables - var - canvasW, - canvasH, - widgetW, - widgetH, - aspect, - dx, - dy, - titleFontSize, - titleX, - titleY, - valueFontSize, - valueX, - valueY, - labelFontSize, - labelX, - labelY, - minFontSize, - minX, - minY, - maxFontSize, - maxX, - maxY; - - // overflow values - if (this.config.value > this.config.max) this.config.value = this.config.max; - if (this.config.value < this.config.min) this.config.value = this.config.min; - this.originalValue = config.value; - - // canvas - this.canvas = Raphael(this.config.node, "100%", "100%"); - if (this.config.relativeGaugeSize === true) { - this.canvas.setViewBox(0, 0, 200, 150, true); - } - - // canvas dimensions - if (this.config.relativeGaugeSize === true) { - canvasW = 200; - canvasH = 150; - } else { - canvasW = getStyle(this.config.node, "width").slice(0, -2) * 1; - canvasH = getStyle(this.config.node, "height").slice(0, -2) * 1; - } - - // widget dimensions - if (this.config.donut === true) { - - // DONUT ******************************* - - // width more than height - if(canvasW > canvasH) { - widgetH = canvasH; - widgetW = widgetH; - // width less than height - } else if (canvasW < canvasH) { - widgetW = canvasW; - widgetH = widgetW; - // if height don't fit, rescale both - if(widgetH > canvasH) { - aspect = widgetH / canvasH; - widgetH = widgetH / aspect; - widgetW = widgetH / aspect; - } - // equal - } else { - widgetW = canvasW; - widgetH = widgetW; - } - - // delta - dx = (canvasW - widgetW)/2; - dy = (canvasH - widgetH)/2; - - // title - titleFontSize = ((widgetH / 8) > 10) ? (widgetH / 10) : 10; - titleX = dx + widgetW / 2; - titleY = dy + widgetH / 11; - - // value - valueFontSize = ((widgetH / 6.4) > 16) ? (widgetH / 5.4) : 18; - valueX = dx + widgetW / 2; - if(this.config.label !== '') { - valueY = dy + widgetH / 1.85; - } else { - valueY = dy + widgetH / 1.7; - } - - // label - labelFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10; - labelX = dx + widgetW / 2; - labelY = valueY + labelFontSize; - - // min - minFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10; - minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2 ; - minY = labelY; - - // max - maxFontSize = ((widgetH / 16) > 10) ? (widgetH / 16) : 10; - maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2 ; - maxY = labelY; - - } else { - // HALF ******************************* - - // width more than height - if(canvasW > canvasH) { - widgetH = canvasH; - widgetW = widgetH * 1.25; - //if width doesn't fit, rescale both - if(widgetW > canvasW) { - aspect = widgetW / canvasW; - widgetW = widgetW / aspect; - widgetH = widgetH / aspect; - } - // width less than height - } else if (canvasW < canvasH) { - widgetW = canvasW; - widgetH = widgetW / 1.25; - // if height don't fit, rescale both - if(widgetH > canvasH) { - aspect = widgetH / canvasH; - widgetH = widgetH / aspect; - widgetW = widgetH / aspect; - } - // equal - } else { - widgetW = canvasW; - widgetH = widgetW * 0.75; - } - - // delta - dx = (canvasW - widgetW)/2; - dy = (canvasH - widgetH)/2; - - // title - titleFontSize = ((widgetH / 8) > this.config.titleMinFontSize) ? (widgetH / 10) : this.config.titleMinFontSize; - titleX = dx + widgetW / 2; - titleY = dy + widgetH / 6.4; - - // value - valueFontSize = ((widgetH / 6.5) > this.config.valueMinFontSize) ? (widgetH / 6.5) : this.config.valueMinFontSize; - valueX = dx + widgetW / 2; - valueY = dy + widgetH / 1.275; - - // label - labelFontSize = ((widgetH / 16) > this.config.labelMinFontSize) ? (widgetH / 16) : this.config.labelMinFontSize; - labelX = dx + widgetW / 2; - labelY = valueY + valueFontSize / 2 + 5; - - // min - minFontSize = ((widgetH / 16) > this.config.minLabelMinFontSize) ? (widgetH / 16) : this.config.minLabelMinFontSize; - minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2 ; - minY = labelY; - - // max - maxFontSize = ((widgetH / 16) > this.config.maxLabelMinFontSize) ? (widgetH / 16) : this.config.maxLabelMinFontSize; - maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * this.config.gaugeWidthScale) / 2 ; - maxY = labelY; - } - - // parameters - this.params = { - canvasW : canvasW, - canvasH : canvasH, - widgetW : widgetW, - widgetH : widgetH, - dx : dx, - dy : dy, - titleFontSize : titleFontSize, - titleX : titleX, - titleY : titleY, - valueFontSize : valueFontSize, - valueX : valueX, - valueY : valueY, - labelFontSize : labelFontSize, - labelX : labelX, - labelY : labelY, - minFontSize : minFontSize, - minX : minX, - minY : minY, - maxFontSize : maxFontSize, - maxX : maxX, - maxY : maxY - }; - - // var clear - canvasW, canvasH, widgetW, widgetH, aspect, dx, dy, titleFontSize, titleX, titleY, valueFontSize, valueX, valueY, labelFontSize, labelX, labelY, minFontSize, minX, minY, maxFontSize, maxX, maxY = null - - // pki - custom attribute for generating gauge paths - this.canvas.customAttributes.pki = function (value, min, max, w, h, dx, dy, gws, donut) { - - var alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, path; - - if (donut) { - alpha = (1 - 2 * (value - min) / (max - min)) * Math.PI; - Ro = w / 2 - w / 7; - Ri = Ro - w / 6.666666666666667 * gws; - - Cx = w / 2 + dx; - Cy = h / 1.95 + dy; - - Xo = w / 2 + dx + Ro * Math.cos(alpha); - Yo = h - (h - Cy) + 0 - Ro * Math.sin(alpha); - Xi = w / 2 + dx + Ri * Math.cos(alpha); - Yi = h - (h - Cy) + 0 - Ri * Math.sin(alpha); - - path += "M" + (Cx - Ri) + "," + Cy + " "; - path += "L" + (Cx - Ro) + "," + Cy + " "; - if (value > ((max - min) / 2)) { - path += "A" + Ro + "," + Ro + " 0 0 1 " + (Cx + Ro) + "," + Cy + " "; - } - path += "A" + Ro + "," + Ro + " 0 0 1 " + Xo + "," + Yo + " "; - path += "L" + Xi + "," + Yi + " "; - if (value > ((max - min) / 2)) { - path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx + Ri) + "," + Cy + " "; - } - path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx - Ri) + "," + Cy + " "; - path += "Z "; - - return { path: path }; - - } else { - alpha = (1 - (value - min) / (max - min)) * Math.PI; - Ro = w / 2 - w / 10; - Ri = Ro - w / 6.666666666666667 * gws; - - Cx = w / 2 + dx; - Cy = h / 1.25 + dy; - - Xo = w / 2 + dx + Ro * Math.cos(alpha); - Yo = h - (h - Cy) + 0 - Ro * Math.sin(alpha); - Xi = w / 2 + dx + Ri * Math.cos(alpha); - Yi = h - (h - Cy) + 0 - Ri * Math.sin(alpha); - - path += "M" + (Cx - Ri) + "," + Cy + " "; - path += "L" + (Cx - Ro) + "," + Cy + " "; - path += "A" + Ro + "," + Ro + " 0 0 1 " + Xo + "," + Yo + " "; - path += "L" + Xi + "," + Yi + " "; - path += "A" + Ri + "," + Ri + " 0 0 0 " + (Cx - Ri) + "," + Cy + " "; - path += "Z "; - - return { path: path }; - } - - // var clear - alpha, Ro, Ri, Cx, Cy, Xo, Yo, Xi, Yi, path = null; - }; - - // gauge - this.gauge = this.canvas.path().attr({ - "stroke": "none", - "fill": this.config.gaugeColor, - pki: [this.config.max, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH, this.params.dx, this.params.dy, this.config.gaugeWidthScale, this.config.donut] - }); - this.gauge.id = this.config.id+"-gauge"; - - // level - this.level = this.canvas.path().attr({ - "stroke": "none", - "fill": getColor(this.config.value, (this.config.value - this.config.min) / (this.config.max - this.config.min), this.config.levelColors, this.config.noGradient, this.config.customSectors), - pki: [this.config.min, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH, this.params.dx, this.params.dy, this.config.gaugeWidthScale, this.config.donut] - }); - if(this.config.donut) { - this.level.transform("r" + this.config.donutStartAngle + ", " + (this.params.widgetW/2 + this.params.dx) + ", " + (this.params.widgetH/1.95 + this.params.dy)); - } - this.level.id = this.config.id+"-level"; - - // title - this.txtTitle = this.canvas.text(this.params.titleX, this.params.titleY, this.config.title); - this.txtTitle.attr({ - "font-size":this.params.titleFontSize, - "font-weight":"bold", - "font-family":"Arial", - "fill":this.config.titleFontColor, - "fill-opacity":"1" - }); - setDy(this.txtTitle, this.params.titleFontSize, this.params.titleY); - this.txtTitle.id = this.config.id+"-txttitle"; - - // value - this.txtValue = this.canvas.text(this.params.valueX, this.params.valueY, 0); - this.txtValue.attr({ - "font-size":this.params.valueFontSize, - "font-weight":"bold", - "font-family":"Arial", - "fill":this.config.valueFontColor, - "fill-opacity":"0" - }); - setDy(this.txtValue, this.params.valueFontSize, this.params.valueY); - this.txtValue.id = this.config.id+"-txtvalue"; - - // label - this.txtLabel = this.canvas.text(this.params.labelX, this.params.labelY, this.config.label); - this.txtLabel.attr({ - "font-size":this.params.labelFontSize, - "font-weight":"normal", - "font-family":"Arial", - "fill":this.config.labelFontColor, - "fill-opacity":"0" - }); - setDy(this.txtLabel, this.params.labelFontSize, this.params.labelY); - this.txtLabel.id = this.config.id+"-txtlabel"; - - // min - this.txtMinimum = this.config.min; - if( this.config.humanFriendly ) this.txtMinimum = humanFriendlyNumber( this.config.min, this.config.humanFriendlyDecimal ); - this.txtMin = this.canvas.text(this.params.minX, this.params.minY, this.txtMinimum); - this.txtMin.attr({ - "font-size":this.params.minFontSize, - "font-weight":"normal", - "font-family":"Arial", - "fill":this.config.labelFontColor, - "fill-opacity": (this.config.hideMinMax || this.config.donut)? "0" : "1" - }); - setDy(this.txtMin, this.params.minFontSize, this.params.minY); - this.txtMin.id = this.config.id+"-txtmin"; - - // max - this.txtMaximum = this.config.max; - if( this.config.humanFriendly ) this.txtMaximum = humanFriendlyNumber( this.config.max, this.config.humanFriendlyDecimal ); - this.txtMax = this.canvas.text(this.params.maxX, this.params.maxY, this.txtMaximum); - this.txtMax.attr({ - "font-size":this.params.maxFontSize, - "font-weight":"normal", - "font-family":"Arial", - "fill":this.config.labelFontColor, - "fill-opacity": (this.config.hideMinMax || this.config.donut)? "0" : "1" - }); - setDy(this.txtMax, this.params.maxFontSize, this.params.maxY); - this.txtMax.id = this.config.id+"-txtmax"; - - var defs = this.canvas.canvas.childNodes[1]; - var svg = "http://www.w3.org/2000/svg"; - - if (ie < 9) { - onCreateElementNsReady(function() { - this.generateShadow(svg, defs); - }); - } else { - this.generateShadow(svg, defs); - } - - // var clear - defs, svg = null; - - // execute on each animation frame - function onAnimate() { - if (obj.config.counter) { - var currentValue = obj.level.attr("pki"); - - if(obj.config.textRenderer) { - // this.originalValue = this.config.textRenderer(this.originalValue); - obj.txtValue.attr("text", obj.config.textRenderer(Math.floor(currentValue[0]))); - } else if(obj.config.humanFriendly) { - // this.originalValue = humanFriendlyNumber( this.originalValue, this.config.humanFriendlyDecimal ) + this.config.symbol; - obj.txtValue.attr("text", humanFriendlyNumber( Math.floor(currentValue[0]), obj.config.humanFriendlyDecimal ) + obj.config.symbol); - } else { - // this.originalValue += this.config.symbol; - obj.txtValue.attr("text", (currentValue[0] * 1).toFixed(obj.config.decimals) + obj.config.symbol); - } - - setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY); - currentValue = null; - } - } - - if(!obj.config.counter) { - if(obj.config.textRenderer) { - obj.originalValue = obj.config.textRenderer(obj.originalValue); - } else if(obj.config.humanFriendly) { - obj.originalValue = humanFriendlyNumber( obj.originalValue, obj.config.humanFriendlyDecimal ) + obj.config.symbol; - } else { - obj.originalValue = (obj.originalValue * 1).toFixed(obj.config.decimals) + obj.config.symbol; - } - - obj.txtValue.attr("text", obj.originalValue); - setDy(obj.txtValue, obj.params.valueFontSize, obj.params.valueY); - } - - //event fired on each animation frame - eve.on("raphael.anim.frame.*", onAnimate); - - // animate gauge level - this.level.animate({pki: [this.config.value, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH, this.params.dx, this.params.dy, this.config.gaugeWidthScale, this.config.donut]}, this.config.startAnimationTime, this.config.startAnimationType); - - // animate value - this.txtValue.animate({"fill-opacity":"1"}, this.config.startAnimationTime, this.config.startAnimationType); - - // animate label - this.txtLabel.animate({"fill-opacity":"1"}, this.config.startAnimationTime, this.config.startAnimationType); -}; - -/** Refresh gauge level */ -JustGage.prototype.refresh = function(val, max) { - - var originalVal, displayVal, color, max = max || null; - - // set new max - if(max !== null) { - this.config.max = max; - - this.txtMaximum = this.config.max; - if( this.config.humanFriendly ) this.txtMaximum = humanFriendlyNumber( this.config.max, this.config.humanFriendlyDecimal ); - this.txtMax.attr({"text" : this.config.max}); - setDy(this.txtMax, this.params.maxFontSize, this.params.maxY); - } - - // overflow values - originalVal = val; - displayVal = val; - if ((val * 1) > (this.config.max * 1)) {val = (this.config.max * 1);} - if ((val * 1) < (this.config.min * 1)) {val = (this.config.min * 1);} - - color = getColor(val, (val - this.config.min) / (this.config.max - this.config.min), this.config.levelColors, this.config.noGradient, this.config.customSectors); - - if(this.config.textRenderer) { - displayVal = this.config.textRenderer(displayVal); - } else if( this.config.humanFriendly ) { - displayVal = humanFriendlyNumber( displayVal, this.config.humanFriendlyDecimal ) + this.config.symbol; - } else { - displayVal = (displayVal * 1).toFixed(this.config.decimals) + this.config.symbol; - } - - if(!this.config.counter) { - this.canvas.getById(this.config.id+"-txtvalue").attr({"text":displayVal}); - setDy(this.canvas.getById(this.config.id+"-txtvalue"), this.params.valueFontSize, this.params.valueY); - } - - this.canvas.getById(this.config.id+"-level").animate({pki: [val, this.config.min, this.config.max, this.params.widgetW, this.params.widgetH, this.params.dx, this.params.dy, this.config.gaugeWidthScale, this.config.donut], "fill":color}, this.config.refreshAnimationTime, this.config.refreshAnimationType); - this.config.value = val * 1; - - // var clear - originalVal, displayVal, color, max = null; -}; - -/** Generate shadow */ -JustGage.prototype.generateShadow = function(svg, defs) { - - var gaussFilter, feOffset, feGaussianBlur, feComposite1, feFlood, feComposite2, feComposite3; - - // FILTER - gaussFilter=document.createElementNS(svg,"filter"); - gaussFilter.setAttribute("id", this.config.id + "-inner-shadow"); - defs.appendChild(gaussFilter); - - // offset - feOffset = document.createElementNS(svg,"feOffset"); - feOffset.setAttribute("dx", 0); - feOffset.setAttribute("dy", this.config.shadowVerticalOffset); - gaussFilter.appendChild(feOffset); - - // blur - feGaussianBlur = document.createElementNS(svg,"feGaussianBlur"); - feGaussianBlur.setAttribute("result","offset-blur"); - feGaussianBlur.setAttribute("stdDeviation", this.config.shadowSize); - gaussFilter.appendChild(feGaussianBlur); - - // composite 1 - feComposite1 = document.createElementNS(svg,"feComposite"); - feComposite1.setAttribute("operator","out"); - feComposite1.setAttribute("in", "SourceGraphic"); - feComposite1.setAttribute("in2","offset-blur"); - feComposite1.setAttribute("result","inverse"); - gaussFilter.appendChild(feComposite1); - - // flood - feFlood = document.createElementNS(svg,"feFlood"); - feFlood.setAttribute("flood-color","black"); - feFlood.setAttribute("flood-opacity", this.config.shadowOpacity); - feFlood.setAttribute("result","color"); - gaussFilter.appendChild(feFlood); - - // composite 2 - feComposite2 = document.createElementNS(svg,"feComposite"); - feComposite2.setAttribute("operator","in"); - feComposite2.setAttribute("in", "color"); - feComposite2.setAttribute("in2","inverse"); - feComposite2.setAttribute("result","shadow"); - gaussFilter.appendChild(feComposite2); - - // composite 3 - feComposite3 = document.createElementNS(svg,"feComposite"); - feComposite3.setAttribute("operator","over"); - feComposite3.setAttribute("in", "shadow"); - feComposite3.setAttribute("in2","SourceGraphic"); - gaussFilter.appendChild(feComposite3); - - // set shadow - if (!this.config.hideInnerShadow) { - this.canvas.canvas.childNodes[2].setAttribute("filter", "url(#" + this.config.id + "-inner-shadow)"); - this.canvas.canvas.childNodes[3].setAttribute("filter", "url(#" + this.config.id + "-inner-shadow)"); - } - - // var clear - gaussFilter, feOffset, feGaussianBlur, feComposite1, feFlood, feComposite2, feComposite3 = null; - -}; - -/** Get color for value */ -function getColor(val, pct, col, noGradient, custSec) { - - var no, inc, colors, percentage, rval, gval, bval, lower, upper, range, rangePct, pctLower, pctUpper, color; - var noGradient = noGradient || custSec.length > 0; - - if(custSec.length > 0) { - for(var i = 0; i < custSec.length; i++) { - if(val > custSec[i].lo && val <= custSec[i].hi) { - return custSec[i].color; - } - } - } - - no = col.length; - if (no === 1) return col[0]; - inc = (noGradient) ? (1 / no) : (1 / (no - 1)); - colors = []; - for (var i = 0; i < col.length; i++) { - percentage = (noGradient) ? (inc * (i + 1)) : (inc * i); - rval = parseInt((cutHex(col[i])).substring(0,2),16); - gval = parseInt((cutHex(col[i])).substring(2,4),16); - bval = parseInt((cutHex(col[i])).substring(4,6),16); - colors[i] = { pct: percentage, color: { r: rval, g: gval, b: bval } }; - } - - if(pct === 0) { - return 'rgb(' + [colors[0].color.r, colors[0].color.g, colors[0].color.b].join(',') + ')'; - } - - for (var j = 0; j < colors.length; j++) { - if (pct <= colors[j].pct) { - if (noGradient) { - return 'rgb(' + [colors[j].color.r, colors[j].color.g, colors[j].color.b].join(',') + ')'; - } else { - lower = colors[j - 1]; - upper = colors[j]; - range = upper.pct - lower.pct; - rangePct = (pct - lower.pct) / range; - pctLower = 1 - rangePct; - pctUpper = rangePct; - color = { - r: Math.floor(lower.color.r * pctLower + upper.color.r * pctUpper), - g: Math.floor(lower.color.g * pctLower + upper.color.g * pctUpper), - b: Math.floor(lower.color.b * pctLower + upper.color.b * pctUpper) - }; - return 'rgb(' + [color.r, color.g, color.b].join(',') + ')'; - } - } - } - -} - -/** Fix Raphael display:none tspan dy attribute bug */ -function setDy(elem, fontSize, txtYpos) { - if ((!ie || ie > 9) && (elem.node.firstChild.attributes.dy)) { - elem.node.firstChild.attributes.dy.value = 0; - } -} - -/** Random integer */ -function getRandomInt (min, max) { - return Math.floor(Math.random() * (max - min + 1)) + min; -} - -/** Cut hex */ -function cutHex(str) { - return (str.charAt(0)=="#") ? str.substring(1,7):str; -} - -/** Human friendly number suffix - From: http://stackoverflow.com/questions/2692323/code-golf-friendly-number-abbreviator */ -function humanFriendlyNumber( n, d ) { - var p, d2, i, s; - - p = Math.pow; - d2 = p(10, d); - i = 7; - while( i ) { - s = p(10,i--*3); - if( s <= n ) { - n = Math.round(n*d2/s)/d2+"KMGTPE"[i]; - } - } - return n; -} - -/** Get style */ -function getStyle(oElm, strCssRule){ - var strValue = ""; - if(document.defaultView && document.defaultView.getComputedStyle){ - strValue = document.defaultView.getComputedStyle(oElm).getPropertyValue(strCssRule); - } - else if(oElm.currentStyle){ - strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){ - return p1.toUpperCase(); - }); - strValue = oElm.currentStyle[strCssRule]; - } - return strValue; -} - -/** Create Element NS Ready */ -function onCreateElementNsReady(func) { - if (document.createElementNS !== undefined) { - func(); - } else { - setTimeout(function() { onCreateElementNsReady(func); }, 100); - } -} - -/** Get IE version */ -// ---------------------------------------------------------- -// A short snippet for detecting versions of IE in JavaScript -// without resorting to user-agent sniffing -// ---------------------------------------------------------- -// If you're not in IE (or IE version is less than 5) then: -// ie === undefined -// If you're in IE (>=5) then you can determine which version: -// ie === 7; // IE7 -// Thus, to detect IE: -// if (ie) {} -// And to detect the version: -// ie === 6 // IE6 -// ie > 7 // IE8, IE9 ... -// ie < 9 // Anything less than IE9 -// ---------------------------------------------------------- -// UPDATE: Now using Live NodeList idea from @jdalton -var ie = (function(){ - - var undef, - v = 3, - div = document.createElement('div'), - all = div.getElementsByTagName('i'); - - while ( - div.innerHTML = '', - all[0] - ); - return v > 4 ? v : undef; -}()); \ No newline at end of file diff --git a/addons/sale_crm/static/src/js/sale_crm.js b/addons/sale_crm/static/src/js/sale_crm.js deleted file mode 100644 index 207ac959af3..00000000000 --- a/addons/sale_crm/static/src/js/sale_crm.js +++ /dev/null @@ -1,108 +0,0 @@ -openerp.sale_crm = function(openerp) { -var _t = openerp.web._t; - -openerp.sale_crm.GaugeWidget = openerp.web_kanban.AbstractField.extend({ - className: "oe_gage", - start: function() { - var self = this; - - var parent = this.getParent(); - var max = this.options.max_field ? parent.record[this.options.max_field].raw_value : 100; - var label = this.options.label_field ? parent.record[this.options.label_field].raw_value : ""; - var title = this.$node.html(); - var val = this.field.value; - var value = _.isArray(val) && val.length ? val[val.length-1]['value'] : val; - var unique_id = _.uniqueId("JustGage"); - - this.$el.empty() - .attr('style', this.$node.attr('style') + ';position:relative; display:inline-block;') - .attr('id', unique_id); - this.gage = new JustGage({ - id: unique_id, - node: this.$el[0], - title: title, - value: value, - min: 0, - max: max, - relativeGaugeSize: true, - humanFriendly: true, - titleFontColor: '#333333', - valueFontColor: '#333333', - labelFontColor: '#000', - label: label, - levelColors: [ - "#ff0000", - "#f9c802", - "#a9d70b" - ], - }); - - var flag_open = false; - if (self.options.action_change) { - var $svg = self.$el.find('svg'); - var css = { - 'text-align': 'center', - 'position': 'absolute', - 'width': self.$el.outerWidth() + 'px', - 'top': (self.$el.outerHeight()/2-5) + 'px' - }; - - self.$el.click(function (event) { - event.stopPropagation(); - flag_open = false; - if (!parent.view.is_action_enabled('edit')) { - return; - } - if (!self.$el.find(".oe_justgage_edit").size()) { - $div = $('
'); - $div.css(css); - $input = $('').val(value); - $input.css({ - 'text-align': 'center', - 'margin': 'auto', - 'width': ($svg.outerWidth()-40) + 'px' - }); - $div.append($input); - self.$el.prepend($div) - $input.focus() - .keydown(function (event) { - event.stopPropagation(); - if (event.keyCode == 13 || event.keyCode == 9) { - if ($input.val() != value) { - parent.view.dataset.call(self.options.action_change, [parent.id, $input.val()]).then(function () { - parent.do_reload(); - }); - } else { - $div.remove(); - } - } - }) - .click(function (event) { - event.stopPropagation(); - flag_open = false; - }) - .blur(function (event) { - if(!flag_open) { - self.$el.find(".oe_justgage_edit").remove(); - } else { - flag_open = false; - setTimeout(function () {$input.focus();}, 0); - } - }); - } - }).mousedown(function () { - flag_open = true; - }); - - if (!+value) { - $svg.fadeTo(0, 0.3); - $div = $('
').text(_t("Click to change value")); - $div.css(css); - self.$el.append($div); - } - } - }, -}); -openerp.web_kanban.fields_registry.add("gage", "openerp.sale_crm.GaugeWidget"); - -};