/**
* @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:
*
series: [{
type: 'radar',
xField: 'name',
yField: 'data1',
showMarkers: true,
markerCfg: {
radius: 5,
size: 5
},
style: {
'stroke-width': 2,
fill: 'none'
}
},{
type: 'radar',
xField: 'name',
yField: 'data2',
showMarkers: true,
markerCfg: {
radius: 5,
size: 5
},
style: {
'stroke-width': 2,
fill: 'none'
}
},{
type: 'radar',
xField: 'name',
yField: 'data3',
showMarkers: true,
markerCfg: {
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 markerCfg 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.Shapes', 'Ext.fx.Anim'],
/* End Definitions */
type: "pie",
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;
Ext.apply(seriesStyle, me.style || {});
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 = [];
//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);
}
});
//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]
});
});
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.markerCfg),
items = me.items,
type = endMarkerStyle.type,
markerGroup = me.markerGroup,
centerX = me.centerX,
centerY = me.centerY,
item, i, l;
delete endMarkerStyle.type;
for (i = 0, l = items.length; i < l; i++) {
item = items[i];
marker = markerGroup.getAt(i);
if (!marker) {
marker = Ext.chart.Shapes[type](surface, Ext.apply({
group: markerGroup,
x: 0, y: 0,
translate: {
x: centerX,
y: centerY
}
}, endMarkerStyle));
} else {
marker.setAttributes({
x: 0, y: 0,
hidden: false
}, true);
}
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);
}
}
},
// @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);
}
}
});