/** * @class Ext.chart.series.Radar * @extends Ext.chart.series.Series * * Creates a Radar Chart. A Radar Chart is a useful visualization technique for comparing different quantitative values for * a constrained number of categories. * As with all other series, the Radar series must be appended in the *series* Chart array configuration. See the Chart * documentation for more information. A typical configuration object for the radar series could be: * * {@img Ext.chart.series.Radar/Ext.chart.series.Radar.png Ext.chart.series.Radar chart series} * * var store = Ext.create('Ext.data.JsonStore', { * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'], * data: [ * {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13}, * {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3}, * {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7}, * {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23}, * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33} * ] * }); * * Ext.create('Ext.chart.Chart', { * renderTo: Ext.getBody(), * width: 500, * height: 300, * animate: true, * theme:'Category2', * store: store, * axes: [{ * type: 'Radial', * position: 'radial', * label: { * display: true * } * }], * series: [{ * type: 'radar', * xField: 'name', * yField: 'data3', * showInLegend: true, * showMarkers: true, * markerConfig: { * radius: 5, * size: 5 * }, * style: { * 'stroke-width': 2, * fill: 'none' * } * },{ * type: 'radar', * xField: 'name', * yField: 'data2', * showMarkers: true, * showInLegend: true, * markerConfig: { * radius: 5, * size: 5 * }, * style: { * 'stroke-width': 2, * fill: 'none' * } * },{ * type: 'radar', * xField: 'name', * yField: 'data5', * showMarkers: true, * showInLegend: true, * markerConfig: { * radius: 5, * size: 5 * }, * style: { * 'stroke-width': 2, * fill: 'none' * } * }] * }); * * In this configuration we add three series to the chart. Each of these series is bound to the same categories field, `name` but bound to different properties for each category, * `data1`, `data2` and `data3` respectively. All series display markers by having `showMarkers` enabled. The configuration for the markers of each series can be set by adding properties onto * the markerConfig object. Finally we override some theme styling properties by adding properties to the `style` object. * * @xtype radar */ Ext.define('Ext.chart.series.Radar', { /* Begin Definitions */ extend: 'Ext.chart.series.Series', requires: ['Ext.chart.Shape', 'Ext.fx.Anim'], /* End Definitions */ type: "radar", alias: 'series.radar', rad: Math.PI / 180, showInLegend: false, /** * @cfg {Object} style * An object containing styles for overriding series styles from Theming. */ style: {}, constructor: function(config) { this.callParent(arguments); var me = this, surface = me.chart.surface, i, l; me.group = surface.getGroup(me.seriesId); if (me.showMarkers) { me.markerGroup = surface.getGroup(me.seriesId + '-markers'); } }, /** * Draws the series for the current chart. */ drawSeries: function() { var me = this, store = me.chart.substore || me.chart.store, group = me.group, sprite, chart = me.chart, animate = chart.animate, field = me.field || me.yField, surface = chart.surface, chartBBox = chart.chartBBox, rendererAttributes, centerX, centerY, items, radius, maxValue = 0, fields = [], max = Math.max, cos = Math.cos, sin = Math.sin, pi2 = Math.PI * 2, l = store.getCount(), startPath, path, x, y, rho, i, nfields, seriesStyle = me.seriesStyle, seriesLabelStyle = me.seriesLabelStyle, first = chart.resizing || !me.radar, axis = chart.axes && chart.axes.get(0), aggregate = !(axis && axis.maximum); me.setBBox(); maxValue = aggregate? 0 : (axis.maximum || 0); Ext.apply(seriesStyle, me.style || {}); //if the store is empty then there's nothing to draw if (!store || !store.getCount()) { return; } me.unHighlightItem(); me.cleanHighlights(); centerX = me.centerX = chartBBox.x + (chartBBox.width / 2); centerY = me.centerY = chartBBox.y + (chartBBox.height / 2); me.radius = radius = Math.min(chartBBox.width, chartBBox.height) /2; me.items = items = []; if (aggregate) { //get all renderer fields chart.series.each(function(series) { fields.push(series.yField); }); //get maxValue to interpolate store.each(function(record, i) { for (i = 0, nfields = fields.length; i < nfields; i++) { maxValue = max(+record.get(fields[i]), maxValue); } }); } //ensure non-zero value. maxValue = maxValue || 1; //create path and items startPath = []; path = []; store.each(function(record, i) { rho = radius * record.get(field) / maxValue; x = rho * cos(i / l * pi2); y = rho * sin(i / l * pi2); if (i == 0) { path.push('M', x + centerX, y + centerY); startPath.push('M', 0.01 * x + centerX, 0.01 * y + centerY); } else { path.push('L', x + centerX, y + centerY); startPath.push('L', 0.01 * x + centerX, 0.01 * y + centerY); } items.push({ sprite: false, //TODO(nico): add markers point: [centerX + x, centerY + y], series: me }); }); path.push('Z'); //create path sprite if (!me.radar) { me.radar = surface.add(Ext.apply({ type: 'path', group: group, path: startPath }, seriesStyle || {})); } //reset on resizing if (chart.resizing) { me.radar.setAttributes({ path: startPath }, true); } //render/animate if (chart.animate) { me.onAnimate(me.radar, { to: Ext.apply({ path: path }, seriesStyle || {}) }); } else { me.radar.setAttributes(Ext.apply({ path: path }, seriesStyle || {}), true); } //render markers, labels and callouts if (me.showMarkers) { me.drawMarkers(); } me.renderLabels(); me.renderCallouts(); }, // @private draws the markers for the lines (if any). drawMarkers: function() { var me = this, chart = me.chart, surface = chart.surface, markerStyle = Ext.apply({}, me.markerStyle || {}), endMarkerStyle = Ext.apply(markerStyle, me.markerConfig), items = me.items, type = endMarkerStyle.type, markerGroup = me.markerGroup, centerX = me.centerX, centerY = me.centerY, item, i, l, marker; delete endMarkerStyle.type; for (i = 0, l = items.length; i < l; i++) { item = items[i]; marker = markerGroup.getAt(i); if (!marker) { marker = Ext.chart.Shape[type](surface, Ext.apply({ group: markerGroup, x: 0, y: 0, translate: { x: centerX, y: centerY } }, endMarkerStyle)); } else { marker.show(); } if (chart.resizing) { marker.setAttributes({ x: 0, y: 0, translate: { x: centerX, y: centerY } }, true); } marker._to = { translate: { x: item.point[0], y: item.point[1] } }; //render/animate if (chart.animate) { me.onAnimate(marker, { to: marker._to }); } else { marker.setAttributes(Ext.apply(marker._to, endMarkerStyle || {}), true); } } }, isItemInPoint: function(x, y, item) { var point, tolerance = 10, abs = Math.abs; point = item.point; return (abs(point[0] - x) <= tolerance && abs(point[1] - y) <= tolerance); }, // @private callback for when creating a label sprite. onCreateLabel: function(storeItem, item, i, display) { var me = this, group = me.labelsGroup, config = me.label, centerX = me.centerX, centerY = me.centerY, point = item.point, endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config); return me.chart.surface.add(Ext.apply({ 'type': 'text', 'text-anchor': 'middle', 'group': group, 'x': centerX, 'y': centerY }, config || {})); }, // @private callback for when placing a label sprite. onPlaceLabel: function(label, storeItem, item, i, display, animate) { var me = this, chart = me.chart, resizing = chart.resizing, config = me.label, format = config.renderer, field = config.field, centerX = me.centerX, centerY = me.centerY, opt = { x: item.point[0], y: item.point[1] }, x = opt.x - centerX, y = opt.y - centerY; label.setAttributes({ text: format(storeItem.get(field)), hidden: true }, true); if (resizing) { label.setAttributes({ x: centerX, y: centerY }, true); } if (animate) { label.show(true); me.onAnimate(label, { to: opt }); } else { label.setAttributes(opt, true); label.show(true); } }, // @private for toggling (show/hide) series. toggleAll: function(show) { var me = this, i, ln, shadow, shadows; if (!show) { Ext.chart.series.Radar.superclass.hideAll.call(me); } else { Ext.chart.series.Radar.superclass.showAll.call(me); } if (me.radar) { me.radar.setAttributes({ hidden: !show }, true); //hide shadows too if (me.radar.shadows) { for (i = 0, shadows = me.radar.shadows, ln = shadows.length; i < ln; i++) { shadow = shadows[i]; shadow.setAttributes({ hidden: !show }, true); } } } }, // @private hide all elements in the series. hideAll: function() { this.toggleAll(false); this.hideMarkers(0); }, // @private show all elements in the series. showAll: function() { this.toggleAll(true); }, // @private hide all markers that belong to `markerGroup` hideMarkers: function(index) { var me = this, count = me.markerGroup && me.markerGroup.getCount() || 0, i = index || 0; for (; i < count; i++) { me.markerGroup.getAt(i).hide(true); } } });