Форк Rambox
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

550 lines
16 KiB

/**
* @class Ext.sparkline.Base
*
* The base class for ExtJS SparkLines. SparkLines are small, inline graphs used to visually
* display small amounts of data. For large datasets, use the {@link Ext.chart.AbstractChart chart package}.
*
* The SparkLine subclasses accept an {@link #values array of values}, and present the data in different visualizations.
*
* @example
* new Ext.Panel({
* height: 300,
* width: 600,
* frame: true,
* title: 'Test Sparklines',
* renderTo:document.body,
* bodyPadding: 10,
*
* // Named listeners will resolve to methods in this Panel
* defaultListenerScope: true,
*
* // Named references will be collected, and can be access from this Panel
* referenceHolder: true,
*
* items: [{
* reference: 'values',
* xtype: 'textfield',
* fieldLabel: 'Values',
* validator: function(v) {
* var result = [];
*
* v = v.replace(/\s/g, '');
* v = v.replace(/,$/, '');
* v = v.split(',');
* for (var i = 0; i < v.length; i++) {
* if (!Ext.isNumeric(v[i])) {
* return 'Value must be a comma separated array of numbers';
* }
* result.push(parseInt(v[i], 10));
* }
* this.values = result;
* return true;
* },
* listeners: {
* change: 'onTypeChange',
* buffer: 500,
* afterrender: {
* fn: 'afterTypeRender',
* single: true
* }
* }
* }, {
* reference: 'type',
* xtype: 'combobox',
* fieldLabel: 'Type',
* store: [
* ['sparklineline', 'Line'],
* ['sparklinebox', 'Box'],
* ['sparklinebullet', 'Bullet'],
* ['sparklinediscrete', 'Discrete'],
* ['sparklinepie', 'Pie'],
* ['sparklinetristate', 'TriState']
* ],
* value: 'sparklineline',
* listeners: {
* change: 'onTypeChange',
* buffer: 500
* }
* }],
*
* // Start with a line plot.
* afterTypeRender: function(typeField) {
* typeField.setValue('6,10,4,-3,7,2');
* },
*
* onTypeChange: function() {
* var me = this,
* refs = me.getReferences(),
* config;
*
* if (me.sparkLine) {
* me.remove(me.sparkLine, true);
* }
* config = {
* xtype: refs.type.getValue(),
* values: refs.values.values,
* height: 25,
* width: 100
* };
* me.sparkLine = Ext.create(config);
* me.add(me.sparkLine);
*
* // Put under fields
* me.sparkLine.el.dom.style.marginLeft = refs.type.labelEl.getWidth() + 'px';
* }
* });
*
*/
Ext.define('Ext.sparkline.Base', {
extend: 'Ext.Widget',
requires: [
'Ext.XTemplate',
'Ext.sparkline.CanvasCanvas',
'Ext.sparkline.VmlCanvas'
],
cachedConfig: {
baseCls: Ext.baseCSSPrefix + 'sparkline',
/**
* @cfg {String} [lineColor=#157fcc] The hex value for line colors in graphs which display lines ({@link Ext.sparkline.Box Box}, {@link Ext.sparkline.Discrete Discrete and {@link Ext.sparkline.Line Line}).
*/
lineColor: '#157fcc',
/**
* @cfg {String} [fillColor=#def] The hex value for fill color in graphs which fill areas ({@link Ext.sparkline.Line Line}).
*/
fillColor: '#def',
defaultPixelsPerValue: 3,
tagValuesAttribute: 'values',
enableTagOptions: false,
enableHighlight: true,
/**
* @cfg {String} [highlightColor=null] The hex value for the highlight color to use when mouseing over a graph segment.
*/
highlightColor: null,
/**
* @cfg {Number} [highlightLighten=1.4] How much to lighten the highlight color by when mouseing over a graph segment.
*/
highlightLighten: 1.4,
/**
* @cfg {Boolean} [tooltipSkipNull=true] Null values will not have a tooltip displayed.
*/
tooltipSkipNull: true,
/*
* @cfg {String} [tooltipPrefix] A string to prepend to each field displayed in a tooltip.
*/
tooltipPrefix: '',
/*
* @cfg {String} [tooltipSuffix] A string to append to each field displayed in a tooltip.
*/
tooltipSuffix: '',
/*
* @cfg {Boolean} [disableTooltips=false] Set to `true` to disable mouseover tooltips.
*/
disableTooltips: false,
disableInteraction: false,
/**
* @cfg {String/Ext.XTemplate} [tipTpl] An XTemplate used to display the value or values in a tooltip when hovering over a Sparkline.
*
* The implemented subclases all define their own `tipTpl`, but it can be overridden.
*/
tipTpl: null
},
config: {
/**
* @cfg {Number[]} values An array of numbers which define the chart.
*/
values: null
},
element: {
tag: 'canvas',
reference: 'element',
style: {
display: 'inline-block',
verticalAlign: 'top'
},
listeners: {
mouseenter: 'onMouseEnter',
mouseleave: 'onMouseLeave',
mousemove: 'onMouseMove'
},
// Create canvas zero sized so that it does not affect the containing element's initial layout
// https://sencha.jira.com/browse/EXTJSIV-10145
width: 0,
height: 0
},
defaultBindProperty: 'values',
// When any config is changed, the canvas needs to be redrawn.
// This is done at the next animation frame when this queue is traversed.
redrawQueue: {},
inheritableStatics: {
sparkLineTipClass: Ext.baseCSSPrefix + 'sparkline-tip-target',
onClassCreated: function(cls) {
var proto = cls.prototype,
configs = cls.getConfigurator().configs,
config;
// Set up an applier for all local configs which kicks off a request to redraw on the next animation frame
for (config in configs) {
// tipTpl not included in this scheme
if (config !== 'tipTpl') {
proto[Ext.Config.get(config).names.apply] = proto.applyConfigChange;
}
}
}
},
constructor: function(config) {
var me = this;
// The canvas sets our element config
me.canvas = Ext.supports.Canvas ? new Ext.sparkline.CanvasCanvas(me)
: new Ext.sparkline.VmlCanvas(me);
if (!me.getDisableTooltips()) {
me.element.cls = Ext.sparkline.Base.sparkLineTipClass;
}
Ext.apply(me, config);
me.callParent([config]);
// For compatibility of all the code.
me.el = me.element;
},
// 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;
},
// generic config value applier.
// Adds this to the queue to do a redraw on the next animation frame
applyConfigChange: function(newValue) {
var me = this;
me.redrawQueue[me.getId()] = me;
// Ensure that there is a single timer to handle all queued redraws.
if (!me.redrawTimer) {
Ext.sparkline.Base.prototype.redrawTimer =
Ext.Function.requestAnimationFrame(me.processRedrawQueue);
}
return newValue;
},
// Appliers convert an incoming config value.
// Ensure the tipTpl is an XTemplate
applyTipTpl: function(tipTpl) {
if (!tipTpl.isTemplate) {
tipTpl = new Ext.XTemplate(tipTpl);
}
return tipTpl;
},
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] = this.normalizeValue(vals[i]);
}
return result;
},
doSetWidth: function(width) {
var me = this,
dom = me.element.dom;
me.callParent(arguments);
me.canvas.setWidth(width);
me.width = width;
if (me.height == null) {
me.setHeight(parseInt(me.measurer.getCachedStyle(dom.parentNode, 'line-height'), 10));
}
else {
me.redrawQueue[me.getId()] = me;
}
},
doSetHeight: function(height) {
var me = this;
me.callParent(arguments);
me.canvas.setHeight(height);
me.height = height;
me.redrawQueue[me.getId()] = me;
},
updateValues: function(values) {
this.values = values;
},
redraw: function() {
var me = this,
tooltip;
if (me.getValues()) {
tooltip = me.tooltip;
// Avoid the visible tooltup thinking a subsequent mousemove is a mouseout by updating its triggerElement
if (tooltip && tooltip.isVisible() && me.currentPageXY && me.el.getRegion().contains(me.currentPageXY)) {
me.tooltip.triggerElement = me.el.dom;
}
me.onUpdate();
me.canvas.onOwnerUpdate();
me.renderGraph();
}
},
onUpdate: Ext.emptyFn,
/*
* Render the chart to the canvas
*/
renderGraph: function () {
var ret = true;
if (this.disabled) {
this.canvas.reset();
ret = false;
}
return ret;
},
onMouseEnter: function(e) {
this.onMouseMove(e);
},
onMouseMove: function (e) {
this.currentPageXY = e.getPoint();
this.redraw();
},
onMouseLeave: function () {
var me = this;
me.currentPageXY = me.targetX = me.targetY = null;
me.redraw();
},
updateDisplay: function () {
var me = this,
offset, tooltip, tooltipHTML, region;
if (me.currentPageXY && me.el.getRegion().contains(me.currentPageXY)) {
offset = me.canvas.el.getXY();
region = me.getRegion(me.currentPageXY[0] - offset[0], me.currentPageXY[1] - offset[1]);
if (region != null && !me.disableHighlight) {
me.renderHighlight(region);
}
me.fireEvent('sparklineregionchange', me);
tooltip = me.tooltip;
if (region != null && tooltip) {
tooltipHTML = me.getRegionTooltip(region);
if (tooltipHTML) {
if (!me.lastTooltipHTML || tooltipHTML[0] !== me.lastTooltipHTML[0] || tooltipHTML[1] !== me.lastTooltipHTML[1]) {
tooltip.setTitle(tooltipHTML[0]);
tooltip.update(tooltipHTML[1]);
me.lastTooltipHTML = tooltipHTML;
}
} else {
tooltip.hide();
}
}
}
},
/*
* Return a region id for a given x/y co-ordinate
*/
getRegion: Ext.emptyFn,
/*
* Fetch the HTML to display as a tooltip
*/
getRegionTooltip: function(region) {
var me = this,
header = me.tooltipChartTitle,
entries = [],
tipTpl = me.getTipTpl(),
fields, showFields, showFieldsKey, newFields, fv,
formatter, fieldlen, i, j;
fields = me.getRegionFields(region);
formatter = me.tooltipFormatter;
if (formatter) {
return formatter(me, me, fields);
}
if (!tipTpl) {
return '';
}
if (!Ext.isArray(fields)) {
fields = [fields];
}
showFields = me.tooltipFormatFieldlist;
showFieldsKey = me.tooltipFormatFieldlistKey;
if (showFields && showFieldsKey) {
// user-selected ordering of fields
newFields = [];
for (i = fields.length; i--;) {
fv = fields[i][showFieldsKey];
if ((j = Ext.Array.indexOf(fv, showFields)) !== -1) {
newFields[j] = fields[i];
}
}
fields = newFields;
}
fieldlen = fields.length;
for (j = 0; j < fieldlen; j++) {
if (!fields[j].isNull || !me.getTooltipSkipNull()) {
Ext.apply(fields[j], {
prefix: me.getTooltipPrefix(),
suffix: me.getTooltipSuffix()
});
entries.push(tipTpl.apply(fields[j]));
}
}
if (header || entries.length) {
return [header, entries.join('<br>')];
}
return '';
},
getRegionFields: Ext.emptyFn,
calcHighlightColor: function(color) {
var me = this,
highlightColor = me.getHighlightColor(),
lighten = me.getHighlightLighten(),
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] = Ext.Number.constrain(Math.round(parseInt(parse[i + 1], 16) * mult * lighten), 0, 255);
}
return 'rgb(' + rgbnew.join(',') + ')';
}
}
return color;
},
destroy: function() {
delete this.redrawQueue[this.getId()];
this.callParent();
}
}, function(SparklineBase) {
var proto = SparklineBase.prototype;
Ext.onInternalReady(function() {
proto.tooltip = new Ext.tip.ToolTip({
id: 'sparklines-tooltip',
target: document.body,
delegate: '.' + SparklineBase.sparkLineTipClass,
showDelay: 0,
dismissDelay: 0,
hideDelay: 400
});
});
SparklineBase.onClassCreated(SparklineBase);
proto.processRedrawQueue = function () {
var redrawQueue = proto.redrawQueue,
id;
for (id in redrawQueue) {
redrawQueue[id].redraw();
}
proto.redrawQueue = {};
proto.redrawTimer = 0;
};
// If we are on a VML platform (IE8 - TODO: remove this when that retires)...
if (!Ext.supports.Canvas) {
SparklineBase.prototype.element = {
tag: 'span',
reference: 'element',
listeners: {
mouseenter: 'onMouseEnter',
mouseleave: 'onMouseLeave',
mousemove: 'onMouseMove'
},
style: {
display: 'inline-block',
position: 'relative',
overflow: 'hidden',
margin: '0px',
padding: '0px',
verticalAlign: 'top',
cursor: 'default'
},
children: [{
tag: 'svml:group',
reference: 'groupEl',
coordorigin: '0 0',
coordsize: '0 0',
style: 'position:absolute;width:0;height:0;pointer-events:none'
}]
};
}
});