Browse Source

HUE-302. Continued HueChart Improvments

  Adding more customizability (more options)
  Improving date parsing (now functions with both integer times and parsable date strings)
  Improving required option checking
Marcus McLaughlin 15 years ago
parent
commit
9a45a0b518

+ 2 - 2
desktop/core/static/js/Source/CCS/HueChart.Area.js

@@ -56,9 +56,9 @@ HueChart.Area = new Class({
                         //And as values the this.getData() array
                         .values(this.getData(true).getObjects())
                         //The two commands below will be run this.series.length * this.getData().length times.
-                        //The x-value, in pixels, is calculated as a conversion of the data object's xField value using the xScale.
+                        //The x-value, in pixels, is calculated as a conversion of the data object's xProperty value using the xScale.
                         .x(function(d) {
-                                return this.xScale(d[this.options.xField]);
+                                return this.xScale(d[this.options.xProperty]);
                         }.bind(this))
                         //The y-value, in pixels, is calculated as a conversion of the data object's layer field value using the yScale.
                         .y(function(d, layer) {

+ 51 - 44
desktop/core/static/js/Source/CCS/HueChart.Box.js

@@ -22,26 +22,34 @@ provides: [ HueChart.Box ]
 
 ...
 */
+(function() {
+var getXValueFn = function(field) {
+        return function(data) {
+                if (field) return data[field];
+                else return data;
+        };
+};
 
 HueChart.Box = new Class({
 
         Extends: HueChart,
 
          options: {
-                //series: [], //the array of data series which are found in the chart data,
-                //xField: '', //the field in the data table which should be used as the xAxis label, and for determining where points are on the xAxis
-                xIsDate: false, //is the xField a date ?
+                series: [], //the array of data series which are found in the chart data,
+                xProperty: 'x', //the field in the data table which should be used for determining where points are on the xAxis
+                dates: {x: false, y: false}, //is the data in an axis a date ? 
+                dateSpans:{x: 'day'}, //if an axis is a date, what time span should the tick marks for that axis be shown in
                 positionIndicator: false, //should the position indicator be shown ?
-                ticks: false, //should tick marks be shown ?
-                labels: false, //should axis labels be shown ?
+                showTicks: false, //should tick marks be shown ?
+                showLabels: false, //should axis labels be shown ?
                 tickColor: "#555", //the color of the tick marks
                 dateFormat: "%b %d", //the format that should be used when displaying dates
                 verticalTickSpacing: 35, //the distance between vertical ticks
-                yFieldDisplayProcessor: function(field) {
-                        return field.replace("_", " ").capitalize();
-                }, //fn to use to process the yField for display, field is this.options.yField
-                xTickHeight: 10,
-                xLabel: "Date"
+                xTickHeight: 10, //the height of the xTick marks.
+                labels:{x:"X", y: "Y"}, //the labels that should be used for each axis
+                selectColor: "rgba(255, 128, 128, .4)", //the color that should be used to fill selections in this chart
+                positionIndicatorColor: "black", //color that should be used to show the position of the selected index, when using the position indicator
+                yType: 'string' //the type of value that is being graphed on the y-axis
                 /*
                 onPointMouseOut: function that should be run when the mouse is moved out of the chart
                 onPointMouseOver: function that should be run when the mouse is moved over a datapoint, takes the dataPoint and index as arguments
@@ -50,22 +58,19 @@ HueChart.Box = new Class({
                 */
         },
         
-
         initialize: function(element, options) {
                 this.parent(element, options);
-                var requiredOptions = ["series", "xField"];
-                requiredOptions.each(function(opt) {
-                        if (!$defined(this.options[opt])) console.error("The required option " + opt + " is missing from a HueChart.Box instantiation.");
-                }.bind(this));
                 this.selected_index = -1;
-                if(this.options.xIsDate) {
-                        //If the xField is a date property, prepare the dates for sorting
-                        //Change the xField to the new property designed for sorting dates
-                        this.getData(true).prepareDates(this.options.xField);
-                        this.options.xField = 'seconds_from_first';
+                if(this.options.dates.x) {
+                        //If the xProperty is a date property, prepare the dates for sorting
+                        //Change the xProperty to the new property designed for sorting dates
+                        this.getData(true).prepareDates(this.options.xProperty);
+                        //Set dateProperty to the initial xProperty, this will hold a date object which will be used for rendering dates as strings 
+                        this.dateProperty = this.options.xProperty;
+                        this.options.xProperty = 'seconds_from_first';
                 } else {
                         //Otherwise sort by the x property.
-                        this.getData(true).sortByProperty(this.options.xField);
+                        this.getData(true).sortByProperty(this.options.xProperty);
                 }
                 //When the setupChart event is fired, the full ProtoVis visualization is being set up, in preparation for render.
                 //The addGraph function is responsible for adding the actual representation of the data, be that a group of lines, or a group of area graphs.
@@ -75,9 +80,9 @@ HueChart.Box = new Class({
                         //Add representation of the data.
                         this.addGraph(vis);
                         //Add tick marks (rules on side and bottom) if enabled
-                        if (this.options.ticks) this.setTicks(vis);
+                        if (this.options.showTicks) this.setTicks(vis);
                         //Add axis labels if enabled
-                        if (this.options.labels) this.setLabels(vis);
+                        if (this.options.showLabels) this.setLabels(vis);
                         //Add position indicator if enabled
                         if (this.options.positionIndicator) this.setPositionIndicator(vis);
                         //Create panel for capture of events
@@ -91,8 +96,8 @@ HueChart.Box = new Class({
         //Set the scales which will be used to convert data values into positions for graph objects
         setScales: function(vis) {
                 //Get the minimum and maximum x values.
-                var xMin = this.getData(true).getMinValue(this.options.xField);
-                var xMax = this.getData(true).getMaxValue(this.options.xField);
+                var xMin = this.getData(true).getMinValue(this.options.xProperty);
+                var xMax = this.getData(true).getMaxValue(this.options.xProperty);
                 //Get the maximum of the values that are to be graphed
                 var maxValue = this.getData(true).getMaxValue(this.options.series);
                 this.xScale = pv.Scale.linear(xMin, xMax).range(this.options.leftPadding, this.width - this.options.rightPadding);
@@ -103,27 +108,29 @@ HueChart.Box = new Class({
         setTicks:function(vis) {
                 //Add X-Ticks.
                 //Create tick array.
-                var xTicks = (this.options.xIsDate ? this.getData(true).createTickArray(7, 'day') : this.xScale.ticks(7));
+                var xTicks = (this.options.dates.x ? this.getData(true).createTickArray(7, this.options.dateSpans.x, this.dateProperty) : this.xScale.ticks(7));
+                //Function will return the correct xValue dependent on whether or not x is a date
+                var getXValue = getXValueFn(this.options.dates.x ? this.options.xProperty : null);
                 //Create rules (lines intended to denote scale)
                 vis.add(pv.Rule)
                         //Use the tick array as data.
                         .data(xTicks)
                         //The bottom of the rule should be at the bottomPadding - the height of the rule.  
                         .bottom(this.options.bottomPadding - this.options.xTickHeight)
-                        //The left of the rule should be at the data object's xField value scaled to pixels.
-                        .left(function(d) { return this.xScale(d[this.options.xField]); }.bind(this))
+                        //The left of the rule should be at the data object's xProperty value scaled to pixels.
+                        .left(function(d) { return this.xScale(getXValue(d)); }.bind(this))
                         //Set the height of the rule to the xTickHeight
                         .height(this.options.xTickHeight)
-                        .strokeStyle(this.options.ruleColor)
+                        .strokeStyle(this.options.tickColor)
                         //Add label to bottom of each rule
                         .anchor("bottom").add(pv.Label)
                                 .text(function(d) {
                                         //If the option is a date, format the date property field.
                                         //Otherwise, simply show it.
-                                        if(this.options.xIsDate) {
-                                                return d.sample_date.format(this.options.dateFormat);
+                                        if(this.options.dates.x) {
+                                                return d[this.dateProperty].format(this.options.dateFormat);
                                         } else {
-                                                return d[this.options.xField];
+                                                return getXValue(d);
                                         }
                                 }.bind(this));
                
@@ -145,11 +152,11 @@ HueChart.Box = new Class({
                         .bottom(function(d) {return tickScale(d);}.bind(this))
                         //The width of the rule should be the width minuis the hoizontal padding.
                         .width(this.width - this.options.leftPadding - this.options.rightPadding + 1)
-                        .strokeStyle("#555")
+                        .strokeStyle(this.options.tickColor)
                         //Add label to the left which shows the number of bytes.
                         .anchor("left").add(pv.Label)
                                 .text(function(d) { 
-                                        if(this.options.yField == 'bytes') return d.convertFileSize(); 
+                                        if(this.options.yType == 'bytes') return d.convertFileSize(); 
                                         return d;
                                 }.bind(this));
         },
@@ -159,15 +166,13 @@ HueChart.Box = new Class({
                 //Add Y-Label to center of chart. 
                 vis.anchor("center").add(pv.Label)
                         .textAngle(-Math.PI/2)
-                        .text(function() {
-                                return this.options.yFieldDisplayProcessor(this.options.yField.replace("_", " ").capitalize());
-                        }.bind(this))
+                        .text(this.options.labels.y)
                         .font(this.options.labelFont)
                         .left(12);
                 
                 //Add X-Label to center of chart.
                 vis.anchor("bottom").add(pv.Label)
-                        .text(this.options.xLabel)
+                        .text(this.options.labels.x)
                         .font(this.options.labelFont)
                         .bottom(0);
         },
@@ -176,18 +181,20 @@ HueChart.Box = new Class({
         setPositionIndicator: function(vis) {
                 //Put selected_index in scope.
                 get_selected_index = this.getSelectedIndex.bind(this);
+                var indicatorColor = this.options.positionIndicatorColor;
                 //Add a thin black bar which is approximately the height of the graphing area for each item on the graph.
                 vis.add(pv.Bar)
                         .data(this.getData(true).getObjects())
                         .left(function(d) { 
-                                return this.xScale(d[this.options.xField]); 
+                                return this.xScale(d[this.options.xProperty]); 
                         }.bind(this))
                         .height(this.height - (this.options.bottomPadding + this.options.topPadding))
                         .bottom(this.options.bottomPadding)
                         .width(2)
                         //Show bar if its index is selected, otherwise hide it.
+                        
                         .fillStyle(function() {
-                                if(this.index == get_selected_index()) return "black";
+                                if(this.index == get_selected_index()) return indicatorColor;
                                 else return null;
                         });
         },
@@ -196,7 +203,7 @@ HueChart.Box = new Class({
                 //Convert the passedin in xValue into its corresponding data value on the xScale. 
                 var mx = this.xScale.invert(x);
                 //Search the data for the index of the element at this data value.
-                var i = pv.search(this.getData(true).getObjects().map(function(d){ return d[this.options.xField]; }.bind(this)), Math.round(mx));
+                var i = pv.search(this.getData(true).getObjects().map(function(d){ return d[this.options.xProperty]; }.bind(this)), Math.round(mx));
                 //Adjust for ProtoVis search
                 i = i < 0 ? (-i - 2) : i;
                 return (i >= 0 && i < this.getData(true).getLength() ? i : null);
@@ -219,7 +226,7 @@ HueChart.Box = new Class({
                 }.bind(this);
                 var clickFn = function() {
                         //Only click if the movement is clearly not a drag.
-                        if (this.selectState && this.selectState.dx < 2) {
+                        if (!this.selectState || this.selectState.dx < 2) {
                                 var dataIndex = this.getDataIndexFromX(vis.mouse().x);
                                 //Fire pointClick if the item exists
                                 if(dataIndex != null) {
@@ -264,7 +271,7 @@ HueChart.Box = new Class({
                         //Otherwise give it a width of 0. 
                         .width(function(d) { return this.selectState.dx > 0 ? this.adjustToGraph('x', this.selectState.x + this.selectState.dx) - this.selectState.x : 0; 
                         }.bind(this))
-                        .fillStyle("rgba(255, 128, 128, .4)");
+                        .fillStyle(this.options.selectColor);
         },
         
         //Adjusts a point to the graph.  Ie...if you give it a point that's greater than or less than
@@ -290,5 +297,5 @@ HueChart.Box = new Class({
                 return point;
         }
 });
-
+})();
 

+ 10 - 13
desktop/core/static/js/Source/CCS/HueChart.Circle.js

@@ -24,9 +24,10 @@ HueChart.Circle = new Class({
         Extends: HueChart,
 
         options: {
-                selectedColor: '#fff'
+                selectedColor: '#fff',
                 //radius: null the radius of the chart, (will default to width/2)
-                //graphField: '' // the field in the data object that should be graphed,
+                graphProperty: 'data', // the property in the data object that should be graphed,
+                nameProperty: null // the property in the data object that should be used to name each wedge
                 /*
                 onWedgeOver: function to be executed when the mouse is moved over a wedge of the chart, 
                 onWedgeOut: function to be executed when the mouse is moved off of a wedge of the chart,
@@ -36,10 +37,6 @@ HueChart.Circle = new Class({
 
         initialize: function(element, options) {
                 this.parent(element, options);
-                var requiredOptions = ["graphField"];
-                requiredOptions.each(function(opt) {
-                        if (!$defined(this.options[opt])) console.error("The required option " + opt + " is missing from a HueChart.Box instantiation.");
-                }.bind(this));
                 this.selected_index = -1;
                 this.radius = this.options.radius || this.width/2;
                 this.addEvent('setupChart', function(vis) {
@@ -49,7 +46,7 @@ HueChart.Circle = new Class({
         },
 
         addGraph: function(vis) {
-                var valueSum = this.getData(false).getSeriesSum(this.options.graphField);
+                var valueSum = this.getData(false).getSeriesSum(this.options.graphProperty);
                 //Put selected index, color array, and hue chart in scope
                 var get_selected_index = this.getSelectedIndex.bind(this);
                 var colorArray = this.options.colorArray;
@@ -63,9 +60,9 @@ HueChart.Circle = new Class({
                         .left(this.radius)
                         //The outer radius is the radius
                         .outerRadius(this.radius)
-                        //The angle is a normalized value summing to 2PI based on the values in the graphField. 
+                        //The angle is a normalized value summing to 2PI based on the values in the graphProperty. 
                         .angle(function(d) { 
-                                return (d[this.options.graphField]/valueSum) * 2 * Math.PI; 
+                                return (d[this.options.graphProperty]/valueSum) * 2 * Math.PI; 
                         }.bind(this))
                         //Fill with white if selected, otherwise use corresponding value in colorArray
                         .fillStyle(function() {
@@ -82,9 +79,9 @@ HueChart.Circle = new Class({
                 //Shortcut to data array
                 var dataArray = this.getData(false).getObjects();
                 //Calculate sum of graph values
-                var valueSum = this.getData(false).getSeriesSum(this.options.graphField);
-                //Shortcut to graphField
-                var graphField = this.options.graphField;
+                var valueSum = this.getData(false).getSeriesSum(this.options.graphProperty);
+                //Shortcut to graphProperty
+                var graphProperty = this.options.graphProperty;
                 //Add an invisible bar to catch events
                 vis.add(pv.Bar)
                         //Left of bar at 0
@@ -126,7 +123,7 @@ HueChart.Circle = new Class({
                                 var angleSums = [];
                                 for (var i = 0; i < dataArray.length; i++) {
                                         //Calculate angle -- previousAngle (angleSums[i-1] plus the latest angle. 
-                                        newAngle = (angleSums[i -1] || 0) + ((dataArray[i][graphField]/valueSum) * 2 * Math.PI); 
+                                        newAngle = (angleSums[i -1] || 0) + ((dataArray[i][graphProperty]/valueSum) * 2 * Math.PI); 
                                         angleSums.push(newAngle);
                                 }
 

+ 34 - 30
desktop/core/static/js/Source/CCS/HueChart.js

@@ -54,31 +54,25 @@ HueChart = new Class({
                 //The font which will be used for the axis labels.
                 labelFont: '14px sans-serif',
                 //The height of the x axis rules
-                /*width: the initial width of the chart (required),
-                  height: the initial height of the chart (required),
-                  dataTable: the table containing the chart data,
-                  dataObject: array of data objects,
-                  One of dataTable or dataObject is required
-                  onSetupChart: function that runs when the protovis rendering information is being set up, but before the protovis object is rendered, takes the protovis objects as an argument
+                width: 200, //the initial width of the chart 
+                height: 200, //the initial height of the chart 
+                dataTable: null, //table containing the chart data
+                dataObject: [], //array of data objects
+                //One of dataTable or dataObject is required
+                /*
+                onSetupChart: function that runs when the protovis rendering information is being set up, but before the protovis object is rendered, takes the protovis objects as an argument
                 */
                   bottomPadding: 0, // the padding between the bottom of the element, and the bottom of the graph,
                   topPadding: 0, // the padding between the top of the element, and the left of the graph,
                   leftPadding: 0, // the padding between the left of the element, and the left of the graph,
-                  rightPadding: 0 // the padding between the right of the element, and the right of the graph,
+                  rightPadding: 0, // the padding between the right of the element, and the right of the graph,
+                  url: "noUrl" //the url of the page. this will be used as a key in the colorManager
         },
 
 
         initialize: function(element, options) {
                 this.setOptions(options);
                 this.element = document.id(element);
-                //Check for required options
-                var requiredOptions = ["width", "height"];
-                requiredOptions.each(function(opt) {
-                        if (!$defined(this.options[opt])) console.error("The required option " + opt + " is missing from a HueChart instantiation.");
-                }.bind(this));
-                if (!(this.options.dataTable || this.options.dataObject)) {
-                        console.error("There is a HueChart instantiation which has no data source.");
-                }
                 //Width and height will potentially change often, make them instance variables.
                 this.width = this.options.width;
                 this.height = this.options.height;
@@ -166,10 +160,24 @@ HueChart.Data = new Class({
         
         //Function to sort by dates and convert date strings into date objects.
         //Also creates seconds_from_first, a useful integer value for graphing.
+        //Accepts date formats parsable in Native/Date.js, or integral ms values.
         prepareDates: function(dateProperty) {
                 //Convert date string to date object.
                 this.dataObjects.each(function(d) {
-                        d[dateProperty] = new Date().parse(d[dateProperty]);
+                        //Attempt to parse the date
+                        //Doing it here rather than using Date().parse because we need
+                        //to understand whether or not it is parseable and react to that.
+                        var unParsed = d[dateProperty]; 
+                        var parsed;
+                        Date.parsePatterns.some(function(pattern){
+                                var bits = pattern.re.exec(unParsed);
+                                return (bits) ? (parsed = pattern.handler(bits)) : false;
+                        });
+                        if (parsed) {
+                                d[dateProperty] = parsed;
+                        } else {
+                                d[dateProperty] =  new Date().parse(parseInt(unParsed, 10));
+                        }
                 });
                 //Sort data by date property.
                 this.sortByProperty(dateProperty);
@@ -288,23 +296,19 @@ HueChart.Data = new Class({
                 take this data array and give me back an array of data objects to use as ticks (lines representing scale).
                 this array should contain no more than num_ticks values.
                 the increment in which the values should be shown is timespan
+                the dateProperty is the property in the data object which contains the date
         */
         //Currently requires the dates in the data object to be exact days.
 
-        createTickArray: function(numTicks, timespan) {
+        createTickArray: function(numTicks, timespan, dateProperty) {
                 //Get the largest number of seconds.
                 var mostSeconds = this.dataObjects.getLast().seconds_from_first;
                 //Get the smallest number of seconds.
                 var leastSeconds = this.dataObjects[0].seconds_from_first;
                 //Get the total number of seconds spanned by the array.
                 var totalSeconds = mostSeconds - leastSeconds;
-                var secondsInSpan;
-                //Choose the timespan -- currently only day
-                switch (timespan) {
-                        case 'day': 
-                                secondsInSpan = 86400;
-                                break;
-                }
+                //Get the number of seconds from Date.js
+                var secondsInSpan = Date.units[timespan]()/1000;
                 var xTicks = [];
                 //If the number of ticks that would be generated for the timespan is less than the number of ticks requested, use the current data array.
                 if (totalSeconds/secondsInSpan <= numTicks) {
@@ -316,16 +320,16 @@ HueChart.Data = new Class({
                         var spansInIncrement = parseInt((totalSeconds/secondsInSpan)/numTicks, 10);
                         //Find secondsToIncrement each value by
                         var secondsInIncrement = spansInIncrement * secondsInSpan; 
-                        var firstDate = this.dataObjects[0].sample_date;
+                        var firstDate = this.dataObjects[0][dateProperty];
                         var firstSeconds = this.dataObjects[0].seconds_from_first;
                         //Add ticks to the xTicks array.
                         //Sample date - firstDate cloned and incremented by the increment times the iteration number.
                         //Seconds_from_first - dateIncrement multiplied by the iteration number.
                         for (var i = 0; i <= numTicks; i++) {
-                                xTicks.push({
-                                        sample_date: firstDate.clone().increment('second', secondsInIncrement * i),
-                                        seconds_from_first: firstSeconds + (secondsInIncrement * i)
-                                });
+                                var tickObject = {};
+                                tickObject[dateProperty] = firstDate.clone().increment('second', secondsInIncrement * i);
+                                tickObject.seconds_from_first = firstSeconds + (secondsInIncrement * i);
+                                xTicks.push(tickObject);
                         }
                         //Center tick marks.
                         var lastTick = xTicks.getLast();
@@ -335,7 +339,7 @@ HueChart.Data = new Class({
                         //Get number of spans to move ticks forward to result in evenly spaced, centered ticks.
                         var centerAdjustment = parseInt(spansAtEnd/2, 10);
                         xTicks.each(function(tick) {
-                                tick.sample_date.increment('second', secondsInSpan * centerAdjustment);
+                                tick[dateProperty].increment('second', secondsInSpan * centerAdjustment);
                                 tick.seconds_from_first += secondsInSpan * centerAdjustment;
                         });
                 }