]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/sparkline/Base.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / sparkline / Base.js
CommitLineData
6527f429
DM
1/**\r
2 * @class Ext.sparkline.Base\r
3 *\r
4 * The base class for ExtJS SparkLines. SparkLines are small, inline graphs used to visually\r
5 * display small amounts of data. For large datasets, use the {@link Ext.chart.AbstractChart chart package}.\r
6 *\r
7 * The SparkLine subclasses accept an {@link #values array of values}, and present the data in different visualizations.\r
8 *\r
9 * @example\r
10 * new Ext.Panel({\r
11 * height: 300,\r
12 * width: 600,\r
13 * frame: true,\r
14 * title: 'Test Sparklines',\r
15 * renderTo:document.body,\r
16 * bodyPadding: 10,\r
17 *\r
18 * // Named listeners will resolve to methods in this Panel\r
19 * defaultListenerScope: true,\r
20 *\r
21 * // Named references will be collected, and can be access from this Panel\r
22 * referenceHolder: true,\r
23 *\r
24 * items: [{\r
25 * reference: 'values',\r
26 * xtype: 'textfield',\r
27 * fieldLabel: 'Values',\r
28 * validator: function(v) {\r
29 * var result = [];\r
30 *\r
31 * v = v.replace(/\s/g, '');\r
32 * v = v.replace(/,$/, '');\r
33 * v = v.split(',');\r
34 * for (var i = 0; i < v.length; i++) {\r
35 * if (!Ext.isNumeric(v[i])) {\r
36 * return 'Value must be a comma separated array of numbers';\r
37 * }\r
38 * result.push(parseInt(v[i], 10));\r
39 * }\r
40 * this.values = result;\r
41 * return true;\r
42 * },\r
43 * listeners: {\r
44 * change: 'onTypeChange',\r
45 * buffer: 500,\r
46 * afterrender: {\r
47 * fn: 'afterTypeRender',\r
48 * single: true\r
49 * }\r
50 * }\r
51 * }, {\r
52 * reference: 'type',\r
53 * xtype: 'combobox',\r
54 * fieldLabel: 'Type',\r
55 * store: [\r
56 * ['sparklineline', 'Line'],\r
57 * ['sparklinebox', 'Box'],\r
58 * ['sparklinebullet', 'Bullet'],\r
59 * ['sparklinediscrete', 'Discrete'],\r
60 * ['sparklinepie', 'Pie'],\r
61 * ['sparklinetristate', 'TriState']\r
62 * ],\r
63 * value: 'sparklineline',\r
64 * listeners: {\r
65 * change: 'onTypeChange',\r
66 * buffer: 500\r
67 * }\r
68 * }],\r
69 *\r
70 * // Start with a line plot. \r
71 * afterTypeRender: function(typeField) {\r
72 * typeField.setValue('6,10,4,-3,7,2');\r
73 * },\r
74 *\r
75 * onTypeChange: function() {\r
76 * var me = this,\r
77 * refs = me.getReferences(),\r
78 * config;\r
79 *\r
80 * if (me.sparkLine) {\r
81 * me.remove(me.sparkLine, true);\r
82 * }\r
83 * config = {\r
84 * xtype: refs.type.getValue(),\r
85 * values: refs.values.values,\r
86 * height: 25,\r
87 * width: 100 \r
88 * };\r
89 * me.sparkLine = Ext.create(config);\r
90 * me.add(me.sparkLine);\r
91 *\r
92 * // Put under fields\r
93 * me.sparkLine.el.dom.style.marginLeft = refs.type.labelEl.getWidth() + 'px';\r
94 * }\r
95 * });\r
96 *\r
97 */\r
98Ext.define('Ext.sparkline.Base', {\r
99 extend: 'Ext.Widget',\r
100 requires: [\r
101 'Ext.XTemplate',\r
102 'Ext.sparkline.CanvasCanvas',\r
103 'Ext.sparkline.VmlCanvas'\r
104 ],\r
105\r
106 cachedConfig: {\r
107 baseCls: Ext.baseCSSPrefix + 'sparkline',\r
108\r
109 /**\r
110 * @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}).\r
111 */\r
112 lineColor: '#157fcc',\r
113\r
114 /**\r
115 * @cfg {String} [fillColor=#def] The hex value for fill color in graphs which fill areas ({@link Ext.sparkline.Line Line}).\r
116 */\r
117 fillColor: '#def',\r
118\r
119 defaultPixelsPerValue: 3,\r
120\r
121 tagValuesAttribute: 'values',\r
122\r
123 enableTagOptions: false,\r
124 \r
125 enableHighlight: true,\r
126 \r
127 /**\r
128 * @cfg {String} [highlightColor=null] The hex value for the highlight color to use when mouseing over a graph segment.\r
129 */\r
130 highlightColor: null,\r
131 \r
132 /**\r
133 * @cfg {Number} [highlightLighten=1.4] How much to lighten the highlight color by when mouseing over a graph segment.\r
134 */\r
135 highlightLighten: 1.4,\r
136 \r
137 /**\r
138 * @cfg {Boolean} [tooltipSkipNull=true] Null values will not have a tooltip displayed.\r
139 */\r
140 tooltipSkipNull: true,\r
141 \r
142 /**\r
143 * @cfg {String} [tooltipPrefix] A string to prepend to each field displayed in a tooltip.\r
144 */\r
145 tooltipPrefix: '',\r
146 \r
147 /**\r
148 * @cfg {String} [tooltipSuffix] A string to append to each field displayed in a tooltip.\r
149 */\r
150 tooltipSuffix: '',\r
151 \r
152 /**\r
153 * @cfg {Boolean} [disableTooltips=false] Set to `true` to disable mouseover tooltips.\r
154 */\r
155 disableTooltips: false,\r
156 \r
157 disableInteraction: false,\r
158 \r
159 /**\r
160 * @cfg {String/Ext.XTemplate} [tipTpl] An XTemplate used to display the value or values in a tooltip when hovering over a Sparkline.\r
161 *\r
162 * The implemented subclases all define their own `tipTpl`, but it can be overridden.\r
163 */\r
164 tipTpl: null\r
165 },\r
166\r
167 config: {\r
168 /**\r
169 * @cfg {Number[]} values An array of numbers which define the chart.\r
170 */\r
171 values: null\r
172 },\r
173\r
174 element: {\r
175 tag: 'canvas',\r
176 reference: 'element',\r
177 style: {\r
178 display: 'inline-block',\r
179 verticalAlign: 'top'\r
180 },\r
181 listeners: {\r
182 mouseenter: 'onMouseEnter',\r
183 mouseleave: 'onMouseLeave',\r
184 mousemove: 'onMouseMove'\r
185 },\r
186 // Create canvas zero sized so that it does not affect the containing element's initial layout\r
187 // https://sencha.jira.com/browse/EXTJSIV-10145\r
188 width: 0,\r
189 height: 0\r
190 },\r
191 \r
192 defaultBindProperty: 'values',\r
193\r
194 // When any config is changed, the canvas needs to be redrawn.\r
195 // This is done at the next animation frame when this queue is traversed.\r
196 redrawQueue: {},\r
197\r
198 inheritableStatics: {\r
199 /**\r
200 * @private\r
201 * @static\r
202 * @inheritable\r
203 */\r
204 sparkLineTipClass: Ext.baseCSSPrefix + 'sparkline-tip-target',\r
205\r
206 /**\r
207 * @private\r
208 * @static\r
209 * @inheritable\r
210 */\r
211 onClassCreated: function(cls) {\r
212 var proto = cls.prototype,\r
213 configs = cls.getConfigurator().configs,\r
214 config;\r
215\r
216 // Set up an applier for all local configs which kicks off a request to redraw on the next animation frame\r
217 for (config in configs) {\r
218 // tipTpl not included in this scheme\r
219 if (config !== 'tipTpl') {\r
220 proto[Ext.Config.get(config).names.apply] = proto.applyConfigChange;\r
221 }\r
222 } \r
223 }\r
224 },\r
225\r
226 constructor: function(config) {\r
227 var me = this;\r
228\r
229 // The canvas sets our element config\r
230 me.canvas = Ext.supports.Canvas ? new Ext.sparkline.CanvasCanvas(me)\r
231 : new Ext.sparkline.VmlCanvas(me);\r
232 if (!me.getDisableTooltips()) {\r
233 me.element.cls = Ext.sparkline.Base.sparkLineTipClass;\r
234 }\r
235\r
236 Ext.apply(me, config);\r
237 me.callParent([config]);\r
238\r
239 // For compatibility of all the code.\r
240 me.el = me.element;\r
241 },\r
242\r
243 // determine if all values of an array match a value\r
244 // returns true if the array is empty\r
245 all: function (val, arr, ignoreNull) {\r
246 var i;\r
247 for (i = arr.length; i--; ) {\r
248 if (ignoreNull && arr[i] === null) {\r
249 continue;\r
250 }\r
251 if (arr[i] !== val) {\r
252 return false;\r
253 }\r
254 }\r
255 return true;\r
256 },\r
257\r
258 // generic config value applier.\r
259 // Adds this to the queue to do a redraw on the next animation frame\r
260 applyConfigChange: function(newValue) {\r
261 var me = this;\r
262 me.redrawQueue[me.getId()] = me;\r
263\r
264 // Ensure that there is a single timer to handle all queued redraws.\r
265 if (!me.redrawTimer) {\r
266 Ext.sparkline.Base.prototype.redrawTimer =\r
267 Ext.Function.requestAnimationFrame(me.processRedrawQueue);\r
268 }\r
269 return newValue;\r
270 },\r
271\r
272 // Appliers convert an incoming config value.\r
273 // Ensure the tipTpl is an XTemplate\r
274 applyTipTpl: function(tipTpl) {\r
275 if (!tipTpl.isTemplate) {\r
276 tipTpl = new Ext.XTemplate(tipTpl);\r
277 }\r
278 return tipTpl;\r
279 },\r
280\r
281 normalizeValue: function (val) {\r
282 var nf;\r
283 switch (val) {\r
284 case 'undefined':\r
285 val = undefined;\r
286 break;\r
287 case 'null':\r
288 val = null;\r
289 break;\r
290 case 'true':\r
291 val = true;\r
292 break;\r
293 case 'false':\r
294 val = false;\r
295 break;\r
296 default:\r
297 nf = parseFloat(val);\r
298 if (val == nf) {\r
299 val = nf;\r
300 }\r
301 }\r
302 return val;\r
303 },\r
304\r
305 normalizeValues: function (vals) {\r
306 var i, result = [];\r
307 for (i = vals.length; i--;) {\r
308 result[i] = this.normalizeValue(vals[i]);\r
309 }\r
310 return result;\r
311 },\r
312\r
313 updateWidth: function(width, oldWidth) {\r
314 var me = this,\r
315 dom = me.element.dom;\r
316\r
317 me.callParent([width, oldWidth]);\r
318 me.canvas.setWidth(width);\r
319 me.width = width;\r
320 if (me.height == null) {\r
321 me.setHeight(parseInt(me.measurer.getCachedStyle(dom.parentNode, 'line-height'), 10));\r
322 } else {\r
323 me.redrawQueue[me.getId()] = me;\r
324 }\r
325 },\r
326\r
327 updateHeight: function(height, oldHeight) {\r
328 var me = this;\r
329\r
330 me.callParent([height, oldHeight]);\r
331 me.canvas.setHeight(height);\r
332 me.height = height;\r
333 me.redrawQueue[me.getId()] = me;\r
334 },\r
335\r
336 updateValues: function(values) {\r
337 this.values = values;\r
338 },\r
339\r
340 redraw: function() {\r
341 var me = this;\r
342\r
343 if (me.getValues()) {\r
344 me.onUpdate();\r
345 me.canvas.onOwnerUpdate();\r
346 me.renderGraph();\r
347 }\r
348 },\r
349\r
350 onUpdate: Ext.emptyFn,\r
351\r
352 /*\r
353 * Render the chart to the canvas\r
354 */\r
355 renderGraph: function () {\r
356 var ret = true;\r
357 if (this.disabled) {\r
358 this.canvas.reset();\r
359 ret = false;\r
360 }\r
361 return ret;\r
362 },\r
363\r
364 onMouseEnter: function(e) {\r
365 this.onMouseMove(e);\r
366 },\r
367\r
368 onMouseMove: function (e) {\r
369 this.tooltip.triggerEvent = e;\r
370 this.currentPageXY = e.getPoint();\r
371 this.redraw();\r
372 },\r
373\r
374 onMouseLeave: function () {\r
375 var me = this;\r
376 me.currentPageXY = me.targetX = me.targetY = null;\r
377 me.redraw();\r
378 me.tooltip.target = null; \r
379 me.tooltip.hide();\r
380 },\r
381\r
382 updateDisplay: function () {\r
383 var me = this,\r
384 values = me.getValues(),\r
385 offset, tooltip = me.tooltip, tooltipHTML, region;\r
386\r
387 if (values && values.length && me.currentPageXY && me.el.getRegion().contains(me.currentPageXY)) {\r
388 offset = me.canvas.el.getXY();\r
389 region = me.getRegion(me.currentPageXY[0] - offset[0], me.currentPageXY[1] - offset[1]);\r
390\r
391 if (region != null && region < values.length) {\r
392 if (!me.disableHighlight) {\r
393 me.renderHighlight(region);\r
394 }\r
395 tooltipHTML = me.getRegionTooltip(region);\r
396 }\r
397 me.fireEvent('sparklineregionchange', me);\r
398\r
399 if (tooltipHTML) {\r
400 if (!me.lastTooltipHTML || tooltipHTML[0] !== me.lastTooltipHTML[0] || tooltipHTML[1] !== me.lastTooltipHTML[1]) {\r
401 tooltip.setTitle(tooltipHTML[0]);\r
402 tooltip.update(tooltipHTML[1]);\r
403 me.lastTooltipHTML = tooltipHTML;\r
404 }\r
405 tooltip.target = me.el;\r
406 tooltip.onTargetOver(tooltip.triggerEvent);\r
407 }\r
408 }\r
409\r
410 // No tip content; ensure it's hidden\r
411 if (!tooltipHTML) {\r
412 tooltip.target = null; \r
413 tooltip.hide();\r
414 }\r
415 },\r
416\r
417 /*\r
418 * Return a region id for a given x/y co-ordinate\r
419 */\r
420 getRegion: Ext.emptyFn,\r
421\r
422 /*\r
423 * Fetch the HTML to display as a tooltip\r
424 */\r
425 getRegionTooltip: function(region) {\r
426 var me = this,\r
427 header = me.tooltipChartTitle,\r
428 entries = [],\r
429 tipTpl = me.getTipTpl(),\r
430 fields, showFields, showFieldsKey, newFields, fv,\r
431 formatter, fieldlen, i, j;\r
432\r
433 fields = me.getRegionFields(region);\r
434 formatter = me.tooltipFormatter;\r
435 if (formatter) {\r
436 return formatter(me, me, fields);\r
437 }\r
438\r
439 if (!tipTpl) {\r
440 return '';\r
441 }\r
442 if (!Ext.isArray(fields)) {\r
443 fields = [fields];\r
444 }\r
445 showFields = me.tooltipFormatFieldlist;\r
446 showFieldsKey = me.tooltipFormatFieldlistKey;\r
447 if (showFields && showFieldsKey) {\r
448 // user-selected ordering of fields\r
449 newFields = [];\r
450 for (i = fields.length; i--;) {\r
451 fv = fields[i][showFieldsKey];\r
452 if ((j = Ext.Array.indexOf(fv, showFields)) !== -1) {\r
453 newFields[j] = fields[i];\r
454 }\r
455 }\r
456 fields = newFields;\r
457 }\r
458 fieldlen = fields.length;\r
459\r
460 for (j = 0; j < fieldlen; j++) {\r
461 if (!fields[j].isNull || !me.getTooltipSkipNull()) {\r
462 Ext.apply(fields[j], {\r
463 prefix: me.getTooltipPrefix(),\r
464 suffix: me.getTooltipSuffix()\r
465 });\r
466 entries.push(tipTpl.apply(fields[j]));\r
467 }\r
468 }\r
469\r
470 if (header || entries.length) {\r
471 return [header, entries.join('<br>')];\r
472 }\r
473 return '';\r
474 },\r
475\r
476 getRegionFields: Ext.emptyFn,\r
477\r
478 calcHighlightColor: function(color) {\r
479 var me = this,\r
480 highlightColor = me.getHighlightColor(),\r
481 lighten = me.getHighlightLighten(),\r
482 parse, mult, rgbnew, i;\r
483\r
484 if (highlightColor) {\r
485 return highlightColor;\r
486 }\r
487 if (lighten) {\r
488 // extract RGB values\r
489 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);\r
490 if (parse) {\r
491 rgbnew = [];\r
492 mult = color.length === 4 ? 16 : 1;\r
493 for (i = 0; i < 3; i++) {\r
494 rgbnew[i] = Ext.Number.constrain(Math.round(parseInt(parse[i + 1], 16) * mult * lighten), 0, 255);\r
495 }\r
496 return 'rgb(' + rgbnew.join(',') + ')';\r
497 }\r
498 }\r
499 return color;\r
500 },\r
501\r
502 destroy: function() {\r
503 delete this.redrawQueue[this.getId()];\r
504 this.callParent();\r
505 }\r
506}, function(SparklineBase) {\r
507 var proto = SparklineBase.prototype;\r
508\r
509 Ext.onInternalReady(function() {\r
510 proto.tooltip = new Ext.tip.ToolTip({\r
511 id: 'sparklines-tooltip',\r
512 showDelay: 0,\r
513 dismissDelay: 0,\r
514 hideDelay: 400\r
515 });\r
516 });\r
517\r
518 SparklineBase.onClassCreated(SparklineBase);\r
519 \r
520 proto.processRedrawQueue = function () {\r
521 var redrawQueue = proto.redrawQueue,\r
522 id;\r
523\r
524 for (id in redrawQueue) {\r
525 redrawQueue[id].redraw();\r
526 }\r
527 proto.redrawQueue = {};\r
528 proto.redrawTimer = 0;\r
529 };\r
530\r
531 // If we are on a VML platform (IE8 - TODO: remove this when that retires)...\r
532 if (!Ext.supports.Canvas) {\r
533 SparklineBase.prototype.element = {\r
534 tag: 'span',\r
535 reference: 'element',\r
536 listeners: {\r
537 mouseenter: 'onMouseEnter',\r
538 mouseleave: 'onMouseLeave',\r
539 mousemove: 'onMouseMove'\r
540 },\r
541 style: {\r
542 display: 'inline-block',\r
543 position: 'relative',\r
544 overflow: 'hidden',\r
545 margin: '0px',\r
546 padding: '0px',\r
547 verticalAlign: 'top',\r
548 cursor: 'default'\r
549 },\r
550 children: [{\r
551 tag: 'svml:group',\r
552 reference: 'groupEl',\r
553 coordorigin: '0 0',\r
554 coordsize: '0 0',\r
555 style: 'position:absolute;width:0;height:0;pointer-events:none'\r
556 }]\r
557 };\r
558 }\r
559});\r