X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/src/chart/series/Radar.js diff --git a/src/chart/series/Radar.js b/src/chart/series/Radar.js new file mode 100644 index 00000000..0f5b1551 --- /dev/null +++ b/src/chart/series/Radar.js @@ -0,0 +1,420 @@ +/** + * @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); + } + } +}); +