--- /dev/null
+Ext.require('Ext.chart.*');
+Ext.require(['Ext.Window', 'Ext.fx.target.Sprite', 'Ext.layout.container.Fit']);
+
+var Renderers = {};
+
+(function() {
+ var ColorManager = {
+ rgbToHsv: function(rgb) {
+ var r = rgb[0] / 255,
+ g = rgb[1] / 255,
+ b = rgb[2] / 255,
+ rd = Math.round,
+ minVal = Math.min(r, g, b),
+ maxVal = Math.max(r, g, b),
+ delta = maxVal - minVal,
+ h = 0, s = 0, v = 0,
+ deltaRgb;
+
+ v = maxVal;
+
+ if (delta == 0) {
+ return [0, 0, v];
+ } else {
+ s = delta / maxVal;
+ deltaRgb = {
+ r: (((maxVal - r) / 6) + (delta / 2)) / delta,
+ g: (((maxVal - g) / 6) + (delta / 2)) / delta,
+ b: (((maxVal - b) / 6) + (delta / 2)) / delta
+ };
+ if (r == maxVal) {
+ h = deltaRgb.b - deltaRgb.g;
+ } else if (g == maxVal) {
+ h = (1 / 3) + deltaRgb.r - deltaRgb.b;
+ } else if (b == maxVal) {
+ h = (2 / 3) + deltaRgb.g - deltaRgb.r;
+ }
+ //handle edge cases for hue
+ if (h < 0) {
+ h += 1;
+ }
+ if (h > 1) {
+ h -= 1;
+ }
+ }
+
+ h = rd(h * 360);
+ s = rd(s * 100);
+ v = rd(v * 100);
+
+ return [h, s, v];
+ },
+
+ hsvToRgb : function(hsv) {
+ var h = hsv[0] / 360,
+ s = hsv[1] / 100,
+ v = hsv[2] / 100,
+ r, g, b, rd = Math.round;
+
+ if (s == 0) {
+ v *= 255;
+ return [v, v, v];
+ } else {
+ var vh = h * 6,
+ vi = vh >> 0,
+ v1 = v * (1 - s),
+ v2 = v * (1 - s * (vh - vi)),
+ v3 = v * (1 - s * (1 - (vh - vi)));
+
+ switch(vi) {
+ case 0:
+ r = v; g = v3; b = v1;
+ break;
+ case 1:
+ r = v2; g = v; b = v1;
+ break;
+ case 2:
+ r = v1; g = v; b = v3;
+ break;
+ case 3:
+ r = v1; g = v2; b = v;
+ break;
+ case 4:
+ r = v3; g = v1; b = v;
+ break;
+ default:
+ r = v; g = v1; b = v2;
+ }
+ return [rd(r * 255),
+ rd(g * 255),
+ rd(b * 255)];
+ }
+ }
+ };
+ //Generic number interpolator
+ var delta = function(x, y, a, b, theta) {
+ return a + (b - a) * (y - theta) / (y - x);
+ };
+ //Add renderer methods.
+ Ext.apply(Renderers, {
+ color: function(fieldName, minColor, maxColor, minValue, maxValue) {
+ var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)\s*/,
+ minColorMatch = minColor.match(re),
+ maxColorMatch = maxColor.match(re),
+ interpolate = function(theta) {
+ return [ delta(minValue, maxValue, minColor[0], maxColor[0], theta),
+ delta(minValue, maxValue, minColor[1], maxColor[1], theta),
+ delta(minValue, maxValue, minColor[2], maxColor[2], theta) ];
+ };
+ minColor = ColorManager.rgbToHsv([ +minColorMatch[1], +minColorMatch[2], +minColorMatch[3] ]);
+ maxColor = ColorManager.rgbToHsv([ +maxColorMatch[1], +maxColorMatch[2], +maxColorMatch[3] ]);
+ //Return the renderer
+ return function(sprite, record, attr, index, store) {
+ var value = +record.get(fieldName),
+ rgb = ColorManager.hsvToRgb(interpolate(value)),
+ rgbString = 'rgb(' + rgb[0] + ', ' + rgb[1] + ', ' + rgb[2] + ')';
+ return Ext.apply(attr, {
+ fill: rgbString
+ });
+ };
+ },
+
+ grayscale: function(fieldName, minColor, maxColor, minValue, maxValue) {
+ var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)\s*/,
+ minColorMatch = minColor.match(re),
+ maxColorMatch = maxColor.match(re),
+ interpolate = function(theta) {
+ var ans = delta(minValue, maxValue, +minColorMatch[1], +maxColorMatch[1], theta) >> 0;
+ return [ ans, ans, ans ];
+ };
+ //Return the renderer
+ return function(sprite, record, attr, index, store) {
+ var value = +record.get(fieldName),
+ rgb = interpolate(value),
+ rgbString = 'rgb(' + rgb[0] + ', ' + rgb[1] + ', ' + rgb[2] + ')';
+
+ return Ext.apply(attr, {
+ fill: rgbString,
+ strokeFill: 'rgb(0, 0, 0)'
+ });
+ };
+ },
+
+ radius: function(fieldName, minRadius, maxRadius, minValue, maxValue) {
+ var interpolate = function(theta) {
+ return delta(minValue, maxValue, minRadius, maxRadius, theta);
+ };
+ //Return the renderer
+ return function(sprite, record, attr, index, store) {
+ var value = +record.get(fieldName),
+ radius = interpolate(value);
+
+ return Ext.apply(attr, {
+ radius: radius,
+ size: radius
+ });
+ };
+ }
+ });
+})();
+
+Ext.onReady(function () {
+ //current renderer configuration
+ var rendererConfiguration = {
+ xField: 'data1',
+ yField: 'data2',
+ color: false,
+ colorFrom: 'rgb(250, 20, 20)',
+ colorTo: 'rgb(127, 0, 240)',
+ scale: false,
+ scaleFrom: 'rgb(20, 20, 20)',
+ scaleTo: 'rgb(220, 220, 220)',
+ radius: false,
+ radiusSize: 50
+ };
+ //update the visualization with the new renderer configuration
+ function refresh() {
+ var chart = Ext.getCmp('chartCmp'),
+ series = chart.series.items,
+ len = series.length,
+ rc = rendererConfiguration,
+ color, grayscale, radius, s;
+
+ for(var i = 0; i < len; i++) {
+ s = series[i];
+ s.xField = rc.xField;
+ s.yField = rc.yField;
+ color = rc.color? Renderers.color(rc.color, rc.colorFrom, rc.colorTo, 0, 100) : function(a, b, attr) { return attr; };
+ grayscale = rc.grayscale? Renderers.grayscale(rc.grayscale, rc.scaleFrom, rc.scaleTo, 0, 100) : function(a, b, attr) { return attr; };
+ radius = rc.radius? Renderers.radius(rc.radius, 10, rc.radiusSize, 0, 100) : function(a, b, attr) { return attr; };
+ s.renderer = function(sprite, record, attr, index, store) {
+ return radius(sprite, record, grayscale(sprite, record, color(sprite, record, attr, index, store), index, store), index, store);
+ };
+ }
+ chart.redraw();
+ }
+ //form selection callbacks/handlers.
+ var xAxisHandler = function(elem) {
+ var xField = elem.text;
+ rendererConfiguration.xField = xField;
+ refresh();
+ };
+
+ var yAxisHandler = function(elem) {
+ var yField = elem.text;
+ rendererConfiguration.yField = yField;
+ refresh();
+ };
+
+ var colorVariableHandler = function(elem) {
+ var color = elem.text;
+ rendererConfiguration.color = color;
+ rendererConfiguration.grayscale = false;
+ refresh();
+ };
+
+ var grayscaleVariableHandler = function(elem) {
+ var color = elem.text;
+ rendererConfiguration.grayscale = color;
+ rendererConfiguration.color = false;
+ refresh();
+ };
+
+ var scaleFromHandler = function(elem) {
+ var from = elem.text;
+ rendererConfiguration.scaleFrom = from;
+ refresh();
+ };
+
+ var scaleToHandler = function(elem) {
+ var to = elem.text;
+ rendererConfiguration.scaleTo = to;
+ refresh();
+ };
+
+ var colorFromHandler = function(elem) {
+ var from = elem.text;
+ rendererConfiguration.colorFrom = from;
+ refresh();
+ };
+
+ var colorToHandler = function(elem) {
+ var to = elem.text;
+ rendererConfiguration.colorTo = to;
+ refresh();
+ };
+
+ var radiusHandler = function(elem) {
+ var radius = elem.text;
+ rendererConfiguration.radius = radius;
+ refresh();
+ };
+
+ var radiusSizeHandler = function(elem) {
+ var radius = elem.text;
+ rendererConfiguration.radiusSize = parseInt(radius, 10);
+ refresh();
+ };
+
+ var xAxisMenu = Ext.create('Ext.menu.Menu', {
+ id: 'xAxisMenu',
+ items: [ {
+ text: 'data1',
+ handler: xAxisHandler,
+ checked: true,
+ group: 'x'
+ },
+ {
+ text: 'data2',
+ handler: xAxisHandler,
+ checked: false,
+ group: 'x'
+ },
+ {
+ text: 'data3',
+ handler: xAxisHandler,
+ checked: false,
+ group: 'x'
+ } ]
+ });
+
+ var yAxisMenu = Ext.create('Ext.menu.Menu', {
+ id: 'yAxisMenu',
+ items: [ {
+ text: 'data1',
+ handler: yAxisHandler,
+ checked: false,
+ group: 'y'
+ },
+ {
+ text: 'data2',
+ handler: yAxisHandler,
+ checked: true,
+ group: 'y'
+ },
+ {
+ text: 'data3',
+ handler: yAxisHandler,
+ checked: false,
+ group: 'y'
+ } ]
+ });
+
+ var colorMenu = Ext.create('Ext.menu.Menu', {
+ id: 'colorMenu',
+ items: [ { text: 'data1', handler: colorVariableHandler, checked: false, group: 'color' },
+ { text: 'data2', handler: colorVariableHandler, checked: false, group: 'color' },
+ { text: 'data3', handler: colorVariableHandler, checked: false, group: 'color' },
+ { text: 'Color From',
+ menu: {
+ items: [{ text: 'rgb(250, 20, 20)', handler: colorToHandler, checked: true, group: 'colorrange' },
+ { text: 'rgb(20, 250, 20)', handler: colorToHandler, checked: false, group: 'colorrange' },
+ { text: 'rgb(20, 20, 250)', handler: colorToHandler, checked: false, group: 'colorrange' },
+ { text: 'rgb(127, 0, 240)', handler: colorFromHandler, checked: false, group: 'colorrange' },
+ { text: 'rgb(213, 70, 121)', handler: colorToHandler, checked: false, group: 'colorrange' },
+ { text: 'rgb(44, 153, 201)', handler: colorFromHandler, checked: false, group: 'colorrange' },
+ { text: 'rgb(146, 6, 157)', handler: colorFromHandler, checked: false, group: 'colorrange' },
+ { text: 'rgb(49, 149, 0)', handler: colorFromHandler, checked: false, group: 'colorrange' },
+ { text: 'rgb(249, 153, 0)', handler: colorFromHandler, checked: false, group: 'colorrange' }]
+ }
+ },
+ { text: 'Color To',
+ menu: {
+ items: [{ text: 'rgb(250, 20, 20)', handler: colorToHandler, checked: false, group: 'tocolorrange' },
+ { text: 'rgb(20, 250, 20)', handler: colorToHandler, checked: false, group: 'tocolorrange' },
+ { text: 'rgb(20, 20, 250)', handler: colorToHandler, checked: false, group: 'tocolorrange' },
+ { text: 'rgb(127, 0, 220)', handler: colorFromHandler, checked: true, group: 'tocolorrange' },
+ { text: 'rgb(213, 70, 121)', handler: colorToHandler, checked: false, group: 'tocolorrange' },
+ { text: 'rgb(44, 153, 201)', handler: colorToHandler, checked: false, group: 'tocolorrange' },
+ { text: 'rgb(146, 6, 157)' , handler: colorToHandler, checked: false, group: 'tocolorrange' },
+ { text: 'rgb(49, 149, 0)', handler: colorToHandler, checked: false, group: 'tocolorrange' },
+ { text: 'rgb(249, 153, 0)', handler: colorToHandler, checked: false, group: 'tocolorrange' }]
+ }
+ }
+ ]
+ });
+
+ var grayscaleMenu = Ext.create('Ext.menu.Menu', {
+ id: 'grayscaleMenu',
+ items: [ { text: 'data1', handler: grayscaleVariableHandler, checked: false, group: 'gs' },
+ { text: 'data2', handler: grayscaleVariableHandler, checked: false, group: 'gs' },
+ { text: 'data3', handler: grayscaleVariableHandler, checked: false, group: 'gs' },
+ { text: 'Scale From',
+ menu: {
+ items: [{ text: 'rgb(20, 20, 20)', handler: scaleFromHandler, checked: true, group: 'gsrange' },
+ { text: 'rgb(80, 80, 80)', handler: scaleFromHandler, checked: false, group: 'gsrange' },
+ { text: 'rgb(120, 120, 120)', handler: scaleFromHandler, checked: false, group: 'gsrange' },
+ { text: 'rgb(180, 180, 180)', handler: scaleFromHandler, checked: false, group: 'gsrange' },
+ { text: 'rgb(220, 220, 220)', handler: scaleFromHandler, checked: false, group: 'gsrange' },
+ { text: 'rgb(250, 250, 250)', handler: scaleFromHandler, checked: false, group: 'gsrange' }]
+ }
+ },
+ { text: 'Scale To',
+ menu: {
+ items: [{ text: 'rgb(20, 20, 20)', handler: scaleToHandler, checked: false, group: 'togsrange' },
+ { text: 'rgb(80, 80, 80)', handler: scaleToHandler, checked: false, group: 'togsrange' },
+ { text: 'rgb(120, 120, 120)', handler: scaleToHandler, checked: false, group: 'togsrange' },
+ { text: 'rgb(180, 180, 180)', handler: scaleToHandler, checked: false, group: 'togsrange' },
+ { text: 'rgb(220, 220, 220)', handler: scaleToHandler, checked: true, group: 'togsrange' },
+ { text: 'rgb(250, 250, 250)', handler: scaleToHandler, checked: false, group: 'togsrange' }]
+ }
+ }
+ ]
+ });
+
+ var radiusMenu = Ext.create('Ext.menu.Menu', {
+ id: 'radiusMenu',
+ style: {
+ overflow: 'visible' // For the Combo popup
+ },
+ items: [ { text: 'data1', handler: radiusHandler, checked: false, group: 'radius' },
+ { text: 'data2', handler: radiusHandler, checked: false, group: 'radius' },
+ { text: 'data3', handler: radiusHandler, checked: false, group: 'radius' },
+ { text: 'Max Radius',
+ menu: {
+ items: [{ text: '20', handler: radiusSizeHandler, checked: false, group: 'sradius' },
+ { text: '30', handler: radiusSizeHandler, checked: false, group: 'sradius' },
+ { text: '40', handler: radiusSizeHandler, checked: false, group: 'sradius' },
+ { text: '50', handler: radiusSizeHandler, checked: true, group: 'sradius' },
+ { text: '60', handler: radiusSizeHandler, checked: false, group: 'sradius' }]
+ }
+ }
+ ]
+ });
+
+ var win = Ext.create('Ext.Window', {
+ width: 800,
+ height: 600,
+ hidden: false,
+ maximizable: true,
+ title: 'Scatter Chart Renderer',
+ renderTo: Ext.getBody(),
+ layout: 'fit',
+ tbar: [
+ {
+ text: 'Reload Data',
+ handler: function() {
+ store1.loadData(generateData());
+ }
+ },
+ {
+ text: 'Select X Axis',
+ menu: xAxisMenu
+ },
+ {
+ text: 'Select Y Axis',
+ menu: yAxisMenu
+ },
+
+ {
+ text: 'Select Color',
+ menu: colorMenu
+ },
+ {
+ text: 'Select Grayscale',
+ menu: grayscaleMenu
+ },
+ {
+ text: 'Select Radius',
+ menu: radiusMenu
+ }
+ ],
+ items: {
+ id: 'chartCmp',
+ xtype: 'chart',
+ style: 'background:#fff',
+ animate: true,
+ store: store1,
+ axes: false,
+ insetPadding: 50,
+ series: [{
+ type: 'scatter',
+ axis: false,
+ xField: 'data1',
+ yField: 'data2',
+ color: '#ccc',
+ markerConfig: {
+ type: 'circle',
+ radius: 20,
+ size: 20
+ }
+ }]
+ }
+ });
+});