Parcourir la 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 il y a 15 ans
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;
                         });
                 }