Selaa lähdekoodia

HUE-395. Various HueChart Improvements
-- Removing ms_from_first and using UTC ms values instead.
-- Integrating Shawn's getTicks method.
-- Adding ability to change HueChart data after initialization: setData and addDataSeries.

Marcus McLaughlin 15 vuotta sitten
vanhempi
commit
031f163568

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

@@ -20,90 +20,90 @@ provides: [HueChart.Area]
 //Builds a stacked area graph.
 HueChart.Area = new Class({
 
-        Extends: HueChart.Box,
+		Extends: HueChart.Box,
 
-        initialize: function(element, options) {
-                this.parent(element, options);
-                this.render();
-                return;
-        },
+		initialize: function(element, options) {
+				this.parent(element, options);
+				this.render();
+				return;
+		},
 
-        setScales: function(vis) {
-                this.parent();
-                //Scale with peakSum as max, since this is a stacked chart.
-                var peakSum = this.getData(true).getPeakSum(this.options.series);
-                //Start yScale range from 0, rather than from the bottom padding, since the areas will be stacked on top of one another.
-                this.yScale = pv.Scale.linear(0, peakSum * 1.2).range(0, (this.height - (this.options.topPadding + this.options.bottomPadding)));
-                //Create yScaleTicks which has a domain which goes up to a function of peakSum and a range from the bottomPadding, to the bottom of the topPadding. 
-                this.yScaleTicks = pv.Scale.linear(0, peakSum * 1.2).range(this.options.bottomPadding, this.height-this.options.topPadding);
-                //Defining a yValueReverse function there, since it is so closely related to the scale.
-                //This function reverses a value returned by yScale.invert to a value that corresponds to the scale from 0 to peakSum, rather than from peakSum to 0.
-                this.yValueReverse = function(reversedValue) {
-                        //Account for difference between top and bottom padding.
-                        var paddingBasedDifference = this.yScaleTicks.invert(this.options.bottomPadding-this.options.topPadding) - this.yScaleTicks.invert(0);
-                        return ((reversedValue - peakSum * 1.2) * -1) - paddingBasedDifference;
-                };
-        },
+		setScales: function(vis) {
+				this.parent();
+				//Scale with peakSum as max, since this is a stacked chart.
+				var peakSum = this.getData(true).getPeakSum(this.series);
+				//Start yScale range from 0, rather than from the bottom padding, since the areas will be stacked on top of one another.
+				this.yScale = pv.Scale.linear(0, peakSum * 1.2).range(0, (this.height - (this.options.topPadding + this.options.bottomPadding)));
+				//Create yScaleTicks which has a domain which goes up to a function of peakSum and a range from the bottomPadding, to the bottom of the topPadding. 
+				this.yScaleTicks = pv.Scale.linear(0, peakSum * 1.2).range(this.options.bottomPadding, this.height-this.options.topPadding);
+				//Defining a yValueReverse function there, since it is so closely related to the scale.
+				//This function reverses a value returned by yScale.invert to a value that corresponds to the scale from 0 to peakSum, rather than from peakSum to 0.
+				this.yValueReverse = function(reversedValue) {
+						//Account for difference between top and bottom padding.
+						var paddingBasedDifference = this.yScaleTicks.invert(this.options.bottomPadding-this.options.topPadding) - this.yScaleTicks.invert(0);
+						return ((reversedValue - peakSum * 1.2) * -1) - paddingBasedDifference;
+				};
+		},
 
-        getYRange: function(y) {
-                return this.parent(y, this.yScaleTicks);
-        },
-        getDataSeriesFromPointAndY: function(dataPoint, y) {
-                var yRange = this.getYRange(y);
-                var yCenter = (yRange[0] + yRange[1])/2;
-                //Create array of series peaks
-                var seriesPeaks = [];
-                for (var i = 0; i < this.options.series.length; i++) {
-                        //Calculate peak -- previousPeak (seriesPeaks[i-1] plus the latest value.
-                        newPeak = (seriesPeaks[i - 1] || 0).toFloat() + (dataPoint[this.options.series[i]].toFloat());
-                        seriesPeaks.push(newPeak);
-                }
-                //Return series and value if the center of the range is greater than the sum of previous values, but less than that sum plus the next value.
-                var toReturn = null;
-                for (i = 0; i < this.options.series.length; i++) {
-                        lastPeak = seriesPeaks[i - 1] || 0;
-                        if (seriesPeaks[i] > yCenter && yCenter > lastPeak) {
-                                toReturn = {'name' : this.options.series[i], 'value': dataPoint[this.options.series[i]] };
-                        }
-                }
-                return toReturn;
-        },
+		getYRange: function(y) {
+				return this.parent(y, this.yScaleTicks);
+		},
+		getDataSeriesFromPointAndY: function(dataPoint, y) {
+				var yRange = this.getYRange(y);
+				var yCenter = (yRange[0] + yRange[1])/2;
+				//Create array of series peaks
+				var seriesPeaks = [];
+				for (var i = 0; i < this.series.length; i++) {
+						//Calculate peak -- previousPeak (seriesPeaks[i-1] plus the latest value.
+						newPeak = (seriesPeaks[i - 1] || 0).toFloat() + (dataPoint[this.series[i]].toFloat());
+						seriesPeaks.push(newPeak);
+				}
+				//Return series and value if the center of the range is greater than the sum of previous values, but less than that sum plus the next value.
+				var toReturn = null;
+				for (i = 0; i < this.series.length; i++) {
+						lastPeak = seriesPeaks[i - 1] || 0;
+						if (seriesPeaks[i] > yCenter && yCenter > lastPeak) {
+								toReturn = {'name' : this.series[i], 'value': dataPoint[this.series[i]] };
+						}
+				}
+				return toReturn;
+		},
 
-        //Build stacked area graph.
-        addGraph: function(vis) {
-                //Put colorArray in scope for fillStyle fn.
-                var stack = vis.add(pv.Layout.Stack);
-                /*  [{'date': 10, 'series1': 5, 'series2': 10, 'series3': 15},
-                     {'date': 20, 'series1': 7, 'series2': 14, 'series3': 21},
-                     {'date': 30, 'series1': 12, 'series2': 24, 'series3': 36}]
-                From a data object which looks like the one above, create a graph
-                using the values in each series[1-3] as a layer, stacked on the previous layers.
-                */
-                //Create a stack with its bottom at the bottom padding
-                stack = stack.bottom(this.options.bottomPadding)
-                        //Using as layers the values in the this.series array.
-                        .layers(this.options.series)
-                        //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 xProperty value using the xScale.
-                        .x(function(d) {
-                                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) {
-                                return this.yScale(d[layer]);
-                        }.bind(this));  
-                
-                //In the stack, add an area graph for every layer.
-                //The stack object contains the data which was added above.
-                //The stack is a layout which says, for every layer defined (above), add an area graph, offsetting it based on the layers below.
-                //Appears as a signle contiguous object, but is actually a bunch of different area graphs.
-                stack.layer.add(pv.Area)
-                        .fillStyle(function(d, layer) {
-                                return this.getColor(layer);
-                        }.bind(this));
-        }
+		//Build stacked area graph.
+		addGraph: function(vis) {
+				//Put colorArray in scope for fillStyle fn.
+				var stack = vis.add(pv.Layout.Stack);
+				/*  [{'date': 10, 'series1': 5, 'series2': 10, 'series3': 15},
+					 {'date': 20, 'series1': 7, 'series2': 14, 'series3': 21},
+					 {'date': 30, 'series1': 12, 'series2': 24, 'series3': 36}]
+				From a data object which looks like the one above, create a graph
+				using the values in each series[1-3] as a layer, stacked on the previous layers.
+				*/
+				//Create a stack with its bottom at the bottom padding
+				stack = stack.bottom(this.options.bottomPadding)
+						//Using as layers the values in the this.series array.
+						.layers(this.series)
+						//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 xProperty value using the xScale.
+						.x(function(d) {
+								return this.xScale(d[this.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) {
+								return this.yScale(d[layer]);
+						}.bind(this));  
+				
+				//In the stack, add an area graph for every layer.
+				//The stack object contains the data which was added above.
+				//The stack is a layout which says, for every layer defined (above), add an area graph, offsetting it based on the layers below.
+				//Appears as a signle contiguous object, but is actually a bunch of different area graphs.
+				stack.layer.add(pv.Area)
+						.fillStyle(function(d, layer) {
+								return this.getColor(layer);
+						}.bind(this));
+		}
 
 });
 

+ 482 - 447
desktop/core/static/js/Source/CCS/HueChart.Box.js

@@ -24,469 +24,504 @@ provides: [ HueChart.Box ]
 */
 (function() {
 var getXValueFn = function(field) {
-        return function(data) {
-                if (field) return data[field];
-                else return data;
-        };
+		return function(data) {
+				if (field) return data[field];
+				else return data;
+		};
 };
 
 HueChart.Box = new Class({
 
-        Extends: HueChart,
+		Extends: HueChart,
 
-         options: {
-                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: {x: false, y: 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
-                xTickHeight: 10, //the height of the xTick marks.
-                labels:{x:"X", y: "Y"}, //the labels that should be used for each axis
-                selectBarColor: "rgba(0, 0, 0, .2)", //the color that should be used to fill selections in this chart
-                selectBarBorderColor: "rgba(0, 0, 0, 1)", //the color that should be used as a border for selections in this chart
-                selectedIndicatorColor: "black", //color that should be used to show the position of the selected index, when using the position indicator
-                highlightedIndicatorColor: "rgba(255, 255, 255, .5)",
-                yType: 'string', //the type of value that is being graphed on the y-axis,
-                showPointValue: false, //show the value at the point when moused over
-                selectable: false, //make the chart selectable
-                //initialSelectValue: {left: 0, right: 0}, //the initial chart selection, must be same type as x values 
-                draggable: false, //make the chart selection draggable,
-                fireSelectOnDrag: true //fires the select event on completion of a drag
-                /*
-                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
-                onPointClick: function that should be run when the mouse is clicked on a datapoint, takes the dataPoint and index as arguments
-                onSeriesMouseOver: function that should be run when the mouse is moved over a dataSeries, takes an object containing the seriesName and value at that point as argument.
-                onSeriesClick: function that should be run when the mouse is clicked on a data series, takes an object containing the seriesName and value at that as an argument.
-                onSpanSelect: function that should be run when a segment of the chart is selected.  Takes a left object and a right object as arguments, each of which contains the corresponding index and data object. 
-                */
-        },
-        
-        initialize: function(element, options) {
-                this.parent(element, options);
-                this.selected_index = -1;
-                this.highlighted_index = -1;
-                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 = 'ms_from_first';
-                } else {
-                        //Otherwise sort by the x property.
-                        this.getData(true).sortByProperty(this.options.xProperty);
-                }
-                //Create tip to show if showPointValue is true.
-                if (this.options.showPointValue) {
-                        this.tip = new DynamicTips(this.element, {
-                                className: 'huechart tip-wrap',
-                                title: $lambda("Title"),
-                                text: $lambda("Text"),
-                                showOnEnter: false
-                        });
-                        this.tip.hide();
-                        this.addEvent('seriesMouseOver', this.updatePointValue);
-                }
-                //Initialize dragState and selectState
-                this.dragState = {x: 0, y: 0};
-                this.selectState = {x: 0, dx: 0};
-                //Set this.draggable and this.selectable to reflect whether or not the chart has these capabilities, based on the existence of events and/or options.
-                this.draggable = this.options.draggable || this.hasEvent('drag') || this.hasEvent('dragStart') || this.hasEvent('dragEnd');
-                this.selectable = this.options.selectable || this.draggable || this.hasEvent('spanSelect') || this.hasEvent('select') || this.hasEvent('selectStart') || this.hasEvent('selectEnd');
-                //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.
-                this.addEvent('setupChart', function(vis) {
-                        //Set up the scales which will be used to convert values into positions for graphing.
-                        this.setScales(vis);
-                        //Add representation of the data.
-                        this.addGraph(vis);
-                        //Add tick marks (rules on side and bottom) if enabled
-                        if (this.options.ticks.x || this.options.ticks.y) this.setTicks(vis);
-                        //Add axis labels if enabled
-                        if (this.options.showLabels) this.setLabels(vis);
-                        //Add position indicator if enabled
-                        if (this.options.positionIndicator) this.setPositionIndicators(vis);
-                        //Create panel for capture of events
-                        this.eventPanel = vis.add(pv.Panel).fillStyle("rgba(0,0,0,.001)");
-                        //If there's a mouse event, add the functionality to capture these events.
-                        if (this.hasEvent('pointMouseOut') && this.hasEvent('pointMouseOver') || this.hasEvent('pointClick') || this.options.showPointValue) this.addMouseEvents(vis);
-                        //If there is a selection or drag event of any sort make the graph selectable.
-                        if (this.selectable) this.makeSelectable(vis);
-                }.bind(this));
-        },
-        
-        //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.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);
-                this.yScale = pv.Scale.linear(0, maxValue * 1.2).range(this.options.bottomPadding, (this.height - (this.options.topPadding)));
-                //Defining a yValueReverse function here, since it is so closely related to the scale.
-                //This function reverses a value returned by yScale.invert to a value that corresponds to the scale from 0 to maxValue, rathen than from maxValue to 0.
-                this.yValueReverse = function(reversedValue) {
-                        var paddingBasedDifference = this.yScale.invert(this.options.bottomPadding - this.options.topPadding) - this.yScale.invert(0);
-                        return ((reversedValue - maxValue * 1.2) * -1) - paddingBasedDifference;
-                };
-        },
-        
-        //Draw the X and Y tick marks.
-        setTicks:function(vis) {
-                if (this.options.ticks.x) {
-                        //Add X-Ticks.
-                        //Create tick array.
-                        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 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.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.dates.x) {
-                                                        return d[this.dateProperty].format(this.options.dateFormat);
-                                                } else {
-                                                        return getXValue(d);
-                                                }
-                                        }.bind(this));
-                }
-                
-                if (this.options.ticks.y) {      
-                        //Add Y-Ticks
-                        //Calculate number of yTicks to show.
-                        //Approximate goal of 35 pixels between yTicks.
-                        var yTickCount = (this.height - (this.options.bottomPadding + this.options.topPadding))/this.options.verticalTickSpacing;
-                        //In some box-style charts, there is a need to have a different scale for yTicks and for y values.
-                        //If there is a scale defined for yTicks, use it, otherwise use the standard yScale.
-                        var tickScale = this.yScaleTicks || this.yScale;
-                        //Create rules
-                        vis.add(pv.Rule)
-                                //Always show at least two ticks.
-                                //tickScale.ticks returns an array of values which are evenly spaced to be used as tick marks.
-                                .data(tickScale.ticks(yTickCount > 1 ? yTickCount : 2))
-                                //The left side of the rule should be at leftPadding pixels.
-                                .left(this.options.leftPadding)
-                                //The bottom of the rule should be at the tickScale.ticks value scaled to pixels.
-                                .bottom(function(d) {return tickScale(d);}.bind(this))
-                                //The width of the rule should be the width minus the hoizontal padding.
-                                .width(this.width - this.options.leftPadding - this.options.rightPadding + 1)
-                                .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.yType == 'bytes') return d.convertFileSize(); 
-                                                return d;
-                                        }.bind(this));
-                }
-        },
-        
-        //Add X and Y axis labels.
-        setLabels: function(vis) {
-                //Add Y-Label to center of chart. 
-                vis.anchor("center").add(pv.Label)
-                        .textAngle(-Math.PI/2)
-                        .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.labels.x)
-                        .font(this.options.labelFont)
-                        .bottom(0);
-        },
+		 options: {
+				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: null}, //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: {x: false, y: false}, //should tick marks be shown ?
+				showLabels: false, //should axis labels be shown ?
+				tickColor: "#555", //the color of the tick marks
+				tickDateFormats: {
+					'ms': "%I:%M:%z %p",
+					'second': "%I:%M %p",
+					'minute': "%I:%M %p",
+					'hour': "%H %p",
+					'day': "%m %D",
+					'month': "%m",
+					'year': "%Y"
+				},
+				dateFormat: "%b %d", //the format that should be used when displaying dates
+				verticalTickSpacing: 35, //the distance between vertical ticks
+				xTickHeight: 10, //the height of the xTick marks.
+				labels:{x:"X", y: "Y"}, //the labels that should be used for each axis
+				selectBarColor: "rgba(0, 0, 0, .2)", //the color that should be used to fill selections in this chart
+				selectBarBorderColor: "rgba(0, 0, 0, 1)", //the color that should be used as a border for selections in this chart
+				selectedIndicatorColor: "black", //color that should be used to show the position of the selected index, when using the position indicator
+				highlightedIndicatorColor: "rgba(255, 255, 255, .5)",
+				yType: 'string', //the type of value that is being graphed on the y-axis,
+				showPointValue: false, //show the value at the point when moused over
+				selectable: false, //make the chart selectable
+				//initialSelectValue: {left: 0, right: 0}, //the initial chart selection, must be same type as x values 
+				draggable: false, //make the chart selection draggable,
+				fireSelectOnDrag: true //fires the select event on completion of a drag
+				/*
+				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
+				onPointClick: function that should be run when the mouse is clicked on a datapoint, takes the dataPoint and index as arguments
+				onSeriesMouseOver: function that should be run when the mouse is moved over a dataSeries, takes an object containing the seriesName and value at that point as argument.
+				onSeriesClick: function that should be run when the mouse is clicked on a data series, takes an object containing the seriesName and value at that as an argument.
+				onSpanSelect: function that should be run when a segment of the chart is selected.  Takes a left object and a right object as arguments, each of which contains the corresponding index and data object. 
+				*/
+		},
+		
+		initialize: function(element, options) {
+				this.parent(element, options);
+				this.selected_index = -1;
+				this.highlighted_index = -1;
+				//Build initial list of data series
+				//Initialize data object
+				if (this.hasData()) this.initializeData();
+				//Create tip to show if showPointValue is true.
+				if (this.options.showPointValue) {
+						this.tip = new DynamicTips(this.element, {
+								className: 'huechart tip-wrap',
+								title: $lambda("Title"),
+								text: $lambda("Text"),
+								showOnEnter: false
+						});
+						this.tip.hide();
+						this.addEvent('seriesMouseOver', this.updatePointValue);
+				}
+				//Initialize dragState and selectState
+				this.dragState = {x: 0, y: 0};
+				this.selectState = {x: 0, dx: 0};
+				//Set this.draggable and this.selectable to reflect whether or not the chart has these capabilities, based on the existence of events and/or options.
+				this.draggable = this.options.draggable || this.hasEvent('drag') || this.hasEvent('dragStart') || this.hasEvent('dragEnd');
+				this.selectable = this.options.selectable || this.draggable || this.hasEvent('spanSelect') || this.hasEvent('select') || this.hasEvent('selectStart') || this.hasEvent('selectEnd');
+				//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.
+				this.addEvent('setupChart', function(vis) {
+						if(this.hasData()) {
+							//Set up the scales which will be used to convert values into positions for graphing.
+							this.setScales(vis);
+							//Add representation of the data.
+							this.addGraph(vis);
+							//Add tick marks (rules on side and bottom) if enabled
+							if (this.options.ticks.x || this.options.ticks.y) this.setTicks(vis);
+							//Add axis labels if enabled
+							if (this.options.showLabels) this.setLabels(vis);
+							//Add position indicator if enabled
+							if (this.options.positionIndicator) this.setPositionIndicators(vis);
+							//Create panel for capture of events
+							this.eventPanel = vis.add(pv.Panel).fillStyle("rgba(0,0,0,.001)");
+							//If there's a mouse event, add the functionality to capture these events.
+							if (this.hasEvent('pointMouseOut') && this.hasEvent('pointMouseOver') || this.hasEvent('pointClick') || this.options.showPointValue) this.addMouseEvents(vis);
+							//If there is a selection or drag event of any sort make the graph selectable.
+							if (this.selectable) this.makeSelectable(vis);
+						}
+				}.bind(this));
+		},
 
-        //Add bars which indicate the positions which are currently selected and/or highlighted on the box graph.
-        setPositionIndicators: function(vis) {
-                //Put selected_index and highlighted_index in scope.
-                get_selected_index = this.getSelectedIndex.bind(this);
-                get_highlighted_index = this.getHighlightedIndex.bind(this);
-                var selectedColor = this.options.selectedIndicatorColor;
-                var highlightedColor = this.options.highlightedIndicatorColor;
-                //Add a thin 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.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 or highlighted, otherwise hide it.
-                        .fillStyle(function() {
-                                if (this.index == get_selected_index()) return selectedColor;
-                                if (this.index == get_highlighted_index()) return highlightedColor;
-                                else return null;
-                        });
-        },
+		initializeData: function() {
+				this.series = [];
+				Hash.each(this.getData(true).getObjects()[0], function(value, key) {
+					if (!this.series.contains(key) && key != this.options.xProperty) this.series.push(key);
+				}.bind(this));
+				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.xProperty = 'ms';
+				} else {
+						//Otherwise sort by the x property.
+						this.xProperty = this.options.xProperty;
+						this.getData(true).sortByProperty(this.xProperty);
+				}
 
-        getDataIndexFromPoint: function(axis, x) {
-                if(axis == 'x') {
-                        //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.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);
-                }
-        },
+		},
 
-        getYRange: function(y, inversionScale) {
-                var scale = inversionScale || this.yScale;
-                var yBuffer = 5; //Pixel buffer for usability.
-                //Must use yValueReverse to reverse the mouse value because drawing happens from the bottom up.  Mouse position is from the top down.
-                var invertedYValue = scale.invert(y);
-                //Since range will be inverted, the array goes from greatest to least initially.
-                var invertedYRange = [scale.invert(y + yBuffer), scale.invert(y - yBuffer)];
-                var yValue = this.yValueReverse(invertedYValue); 
-                //Convert the inverted yRange to a non-inverted yRange.
-                var yRange = invertedYRange.map(function(value) { return this.yValueReverse(value); }.bind(this));
-                return yRange;
-        },
+		//Add data -- redefined to add series to the array of series
+		addData: function(data) {
+			var added = this.parent(data, this.dateProperty);
+			if (added) {
+			    var firstObj = data[0];
+				Hash.each(firstObj, function(value, key) {
+					if (key != this.dateProperty){
+						if (!this.series.contains(key)) this.series.push(key);
+					}
+			    }.bind(this));
+			}
+		},
+		
+		//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.xProperty);
+				var xMax = this.getData(true).getMaxValue(this.xProperty);
+				//Get the maximum of the values that are to be graphed
+				var maxValue = this.getData(true).getMaxValue(this.series);
+				this.xScale = pv.Scale.linear(xMin, xMax).range(this.options.leftPadding, this.width - this.options.rightPadding);
+				this.yScale = pv.Scale.linear(0, maxValue * 1.2).range(this.options.bottomPadding, (this.height - (this.options.topPadding)));
+				//Defining a yValueReverse function here, since it is so closely related to the scale.
+				//This function reverses a value returned by yScale.invert to a value that corresponds to the scale from 0 to maxValue, rathen than from maxValue to 0.
+				this.yValueReverse = function(reversedValue) {
+						var paddingBasedDifference = this.yScale.invert(this.options.bottomPadding - this.options.topPadding) - this.yScale.invert(0);
+						return ((reversedValue - maxValue * 1.2) * -1) - paddingBasedDifference;
+				};
+		},
+		
+		//Draw the X and Y tick marks.
+		setTicks:function(vis) {
+				if (this.options.ticks.x) {
+						//Add X-Ticks.
+						//Create tick array.
+						var xTicks = (this.options.dates.x ? this.getData(true).getTicks(10, 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.xProperty : null);
+						//Create rules (lines intended to denote scale)
+						vis.add(pv.Rule)
+								//Use the tick array as data.
+								.data(xTicks.ticks)
+								//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 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.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.dates.x) {
+														return d[this.dateProperty].format(this.options.tickDateFormats[xTicks.unit]);
+												} else {
+														return getXValue(d);
+												}
+										}.bind(this));
+				}
+				
+				if (this.options.ticks.y) {
+						//Add Y-Ticks
+						//Calculate number of yTicks to show.
+						//Approximate goal of 35 pixels between yTicks.
+						var yTickCount = (this.height - (this.options.bottomPadding + this.options.topPadding))/this.options.verticalTickSpacing;
+						//In some box-style charts, there is a need to have a different scale for yTicks and for y values.
+						//If there is a scale defined for yTicks, use it, otherwise use the standard yScale.
+						var tickScale = this.yScaleTicks || this.yScale;
+						//Create rules
+						vis.add(pv.Rule)
+								//Always show at least two ticks.
+								//tickScale.ticks returns an array of values which are evenly spaced to be used as tick marks.
+								.data(tickScale.ticks(yTickCount > 1 ? yTickCount : 2))
+								//The left side of the rule should be at leftPadding pixels.
+								.left(this.options.leftPadding)
+								//The bottom of the rule should be at the tickScale.ticks value scaled to pixels.
+								.bottom(function(d) {return tickScale(d);}.bind(this))
+								//The width of the rule should be the width minus the hoizontal padding.
+								.width(this.width - this.options.leftPadding - this.options.rightPadding + 1)
+								.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.yType == 'bytes') return d.convertFileSize();
+												return d;
+										}.bind(this));
+				}
+		},
+		
+		//Add X and Y axis labels.
+		setLabels: function(vis) {
+				//Add Y-Label to center of chart. 
+				vis.anchor("center").add(pv.Label)
+						.textAngle(-Math.PI/2)
+						.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.labels.x)
+						.font(this.options.labelFont)
+						.bottom(0);
+		},
 
-        getDataSeriesFromPointAndY: function(dataPoint, y) {
-                var yRange = this.getYRange(y);
-                //Find closest y-values
-                for (var i = 0; i < this.options.series.length; i++) {
-                        var item = this.options.series[i];
-                        if(yRange[0] < dataPoint[item] && dataPoint[item] < yRange[1]) {
-                                return {'name': item, 'value': dataPoint[item]};
-                        }  
-                }
-                return null;
-        },
-         
-        //Add handlers to detect mouse events.
-        addMouseEvents: function(vis) {
-                //Function that controls the search for data points and fires mouse positioning events. 
-                var mousePositionEvent = function(eventName, position) {
-                        var dataIndex = this.getDataIndexFromPoint('x', position.x);
-                        if(dataIndex != null) {
-                                var dataPoint = this.getData(true).getObjects()[dataIndex];
-                                this.fireEvent('point' + eventName.capitalize(), [ dataPoint, dataIndex ]);
-                                var dataSeries = this.getDataSeriesFromPointAndY(dataPoint, position.y);
+		//Add bars which indicate the positions which are currently selected and/or highlighted on the box graph.
+		setPositionIndicators: function(vis) {
+				//Put selected_index and highlighted_index in scope.
+				get_selected_index = this.getSelectedIndex.bind(this);
+				get_highlighted_index = this.getHighlightedIndex.bind(this);
+				var selectedColor = this.options.selectedIndicatorColor;
+				var highlightedColor = this.options.highlightedIndicatorColor;
+				//Add a thin 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.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 or highlighted, otherwise hide it.
+						.fillStyle(function() {
+								if (this.index == get_selected_index()) return selectedColor;
+								if (this.index == get_highlighted_index()) return highlightedColor;
+								else return null;
+						});
+		},
 
-                                this.fireEvent('series' + eventName.capitalize(), dataSeries);
-                        }
-                }.bind(this);
-                //Create functions which handle the graph specific aspects of the event and call the event arguments.
-                var outVisFn = function() {
-                        this.fireEvent('pointMouseOut');
-                        return vis;
-                }.bind(this);
-                var moveVisFn = function() {
-                        mousePositionEvent('mouseOver', vis.mouse());
-                        return vis;
-                }.bind(this);
-                var clickFn = function() {
-                        //Only click if the movement is clearly not a drag.
-                        if (!this.selectState || this.selectState.dx < 2) {
-                                mousePositionEvent('click', vis.mouse());
-                                return vis;
-                        }
-                }.bind(this);
+		getDataIndexFromPoint: function(axis, x) {
+				if(axis == 'x') {
+						//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.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);
+				}
+		},
 
-                
-                this.eventPanel
-                        .events("all")
-                        .event("mouseout", outVisFn)
-                        .event("mousemove", moveVisFn)
-                        .event("click", clickFn);
+		getYRange: function(y, inversionScale) {
+				var scale = inversionScale || this.yScale;
+				var yBuffer = 5; //Pixel buffer for usability.
+				//Must use yValueReverse to reverse the mouse value because drawing happens from the bottom up.  Mouse position is from the top down.
+				var invertedYValue = scale.invert(y);
+				//Since range will be inverted, the array goes from greatest to least initially.
+				var invertedYRange = [scale.invert(y + yBuffer), scale.invert(y - yBuffer)];
+				var yValue = this.yValueReverse(invertedYValue); 
+				//Convert the inverted yRange to a non-inverted yRange.
+				var yRange = invertedYRange.map(function(value) { return this.yValueReverse(value); }.bind(this));
+				return yRange;
+		},
 
-        },
-        
-        //Given points on an axis, return an array of data objects for each point
-        getObjectsForPoints: function(axis /*points*/) {
-                var argArray = $A(arguments).slice(1);
-                return argArray.map(function(point) {
-                        var toGraph = this.adjustToGraph(axis, point);
-                        var index = this.getDataIndexFromPoint(axis, toGraph);
-                        return { index: index, data: this.getData(true).getObjects()[index] };
-                }.bind(this));
-                
-        },
-        
-        //Make selection in graph draggable
-        makeSelectionDraggable: function() {
-                //Attach the ProtoVis drag behavior to the select bar and attach events for drag occurrences
-                this.selectBar = this.selectBar 
-                //Set cursor to be a mouse pointer
-                .cursor("move")
-                .event("mousedown", pv.Behavior.drag())
-                        .event("drag", function() {
-                                this.fireEvent('drag');
-                        }.bind(this))
-                        .event("dragstart", function() {
-                                this.fireEvent('dragStart');
-                        }.bind(this))
-                        .event("dragend", function() {
-                                if (this.options.fireSelectOnDrag) {
-                                        //Get objects for edge points
-                                        var leftPoint = this.dragState.x;
-                                        var rightPoint = this.dragState.x + this.selectWidth;
-                                        var objectArray = this.getObjectsForPoints('x', leftPoint, rightPoint);
-                                        this.fireEvent('spanSelect', objectArray);
-                                }
-                                this.fireEvent('dragEnd');
-                        }.bind(this));
-        },
-        
-        //Make graph selectable
-        makeSelectable: function(){
-                //Create select bar
-                this.selectBar = this.eventPanel.add(pv.Bar);
-                
-                //If there is a need for draggability, make the selection draggable.
-                if (this.draggable) this.makeSelectionDraggable();
-                
-                //Set the basic settings for the selectBar 
-                this.selectBar = this.selectBar
-                                //Initialize dragState as selectBar's data.
-                                .data([this.dragState])
-                                //Set fillStyle to the selectBarColor
-                                .fillStyle(this.options.selectBarColor)
-                                //Set height so that it only covers the chart
-                                .height(this.height - (this.options.bottomPadding + this.options.topPadding))
-                                //Set top to start at top padding
-                                .top(this.options.topPadding)
-                                //Set line width to 1 pixel
-                                .lineWidth(1)
-                                //Set the left value to the dragState's x value.
-                                .left(function(d) {
-                                        return d.x;
-                                });
-                
-                //Initialize selection to nothing
-                this.selectRange(0, 0);
-                //If the initialSelectValue has been set, select that range.
-                if(this.options.initialSelectValue) {
-                        //If date, convert to date
-                        if (this.options.dates.x) {
-                                startValue = this.data.getMsFromFirst(this.data.parseDate(this.options.initialSelectValue.start));
-                                endValue = this.data.getMsFromFirst(this.data.parseDate(this.options.initialSelectValue.end));
-                        }
-                        //Convert to points on xScale
-                        startX = this.xScale(startValue);
-                        endX = this.xScale(endValue);
-                        this.selectRange(startX, endX);
-                        this.selectBar.strokeStyle(this.options.selectBarBorderColor);
-                }
-                //Initialize eventPanel's select events.
-                this.eventPanel
-                        .data([this.selectState])
-                        .event("mousedown", pv.Behavior.select());
-                         
-                //If d.dx has a value greater than 0...meaning we're in the middle of a
-                //drag, adjust the width value to the graph.
-                //Otherwise give it a width of 0.
-                this.eventPanel
-                        .event("selectstart", function() {
-                                this.selectBar.width(0);
-                                this.selectBar.strokeStyle(this.options.selectBarBorderColor);
-                                this.fireEvent('selectStart');
-                        }.bind(this))
-                        .event("select", function() {
-                                this.selectRange(this.adjustToGraph('x', this.selectState.x), this.adjustToGraph('x', this.selectState.x + this.selectState.dx));
-                                this.fireEvent('select');
-                        }.bind(this))
-                        .event("selectend", function() {
-                                if (this.selectState.dx > 2) {
-                                        //Get objects for edge points
-                                        //left - this.selectState.x
-                                        //right - this.selectState.dx
-                                        var objectArray = this.getObjectsForPoints('x', this.selectState.x, this.selectState.x + this.selectState.dx);
-                                        this.fireEvent('spanSelect', objectArray);
-                                }
-                                this.fireEvent('selectEnd');
-                        }.bind(this));
-        },
-        
-        //Selects a pixel range in the graph.
-        selectRange: function(leftValue, rightValue) {
-                this.dragState.x = this.adjustToGraph('x', leftValue);
-                this.selectWidth = rightValue - leftValue;
-                this.selectBar
-                        .width(rightValue - leftValue);
-        },
+		getDataSeriesFromPointAndY: function(dataPoint, y) {
+				var yRange = this.getYRange(y);
+				//Find closest y-values
+				for (var i = 0; i < this.series.length; i++) {
+						var item = this.series[i];
+						if(yRange[0] < dataPoint[item] && dataPoint[item] < yRange[1]) {
+								return {'name': item, 'value': dataPoint[item]};
+						}  
+				}
+				return null;
+		},
+		 
+		//Add handlers to detect mouse events.
+		addMouseEvents: function(vis) {
+				//Function that controls the search for data points and fires mouse positioning events. 
+				var mousePositionEvent = function(eventName, position) {
+						var dataIndex = this.getDataIndexFromPoint('x', position.x);
+						if(dataIndex != null) {
+								var dataPoint = this.getData(true).getObjects()[dataIndex];
+								this.fireEvent('point' + eventName.capitalize(), [ dataPoint, dataIndex ]);
+								var dataSeries = this.getDataSeriesFromPointAndY(dataPoint, position.y);
 
-        //Adjusts a point to the graph.  Ie...if you give it a point that's greater than or less than
-        //points in the graph, it will reset it to points within the graph.
-        //This is easily accomplished using the range of the graphing scales.
-        adjustToGraph: function(axis, point){
-                var scale;
-                switch(axis){
-                        case 'x': scale = this.xScale;
-                                    break;
-                        case 'y': scale = this.yScale;
-                                    break;
-                        //Return if axis is not x or y.
-                        default: return;
-                }
-                //scale.range() returns an array of two values.
-                //The first is the low end of the range, while the second is the highest.
-                var low = scale.range()[0];
-                var high = scale.range()[1];
-                //Return low or high is the value is outside their interval.  Otherwise, return point.
-                if (point < low) return low;
-                if (point > high) return high;
-                return point;
-        },
-        
-        //Updates the display of the currently visible tip
-        updatePointValue: function(series) {
-                if (series != null) {
-                        var tipColor = new Element('div', {'class': 'tip-series-color'});
-                        var tipBlock = new Element('div', {'class': 'tip-block'});
-                        tipBlock.set('text', series.name);
-                        tipColor.inject(tipBlock, 'top');
-                        tipColor.setStyle('background-color', this.getColor(series.name));
-                        this.tip.setTitle(tipBlock);
-                        this.tip.setText(this.options.yType == 'bytes' ? series.value.toInt().convertFileSize() : series.value);
-                        var tipElem = this.tip.toElement();
-                        if(!tipElem.getParent() || !document.body.hasChild(tipElem))tipElem.dispose();
-                        this.tip.show();
-                } else {
-                        this.tip.hide();
-                }
-        }, 
+								this.fireEvent('series' + eventName.capitalize(), dataSeries);
+						}
+				}.bind(this);
+				//Create functions which handle the graph specific aspects of the event and call the event arguments.
+				var outVisFn = function() {
+						this.fireEvent('pointMouseOut');
+						return vis;
+				}.bind(this);
+				var moveVisFn = function() {
+						mousePositionEvent('mouseOver', vis.mouse());
+						return vis;
+				}.bind(this);
+				var clickFn = function() {
+						//Only click if the movement is clearly not a drag.
+						if (!this.selectState || this.selectState.dx < 2) {
+								mousePositionEvent('click', vis.mouse());
+								return vis;
+						}
+				}.bind(this);
 
-        //Returns highlighted data index
-        getHighlightedIndex: function() {
-                return this.highlighted_index;
-        },
+				
+				this.eventPanel
+						.events("all")
+						.event("mouseout", outVisFn)
+						.event("mousemove", moveVisFn)
+						.event("click", clickFn);
 
-        //Sets higlighted data index
-        setHighlightedIndex: function(index) {
-                this.highlighted_index = index;
-        },
+		},
+		
+		//Given points on an axis, return an array of data objects for each point
+		getObjectsForPoints: function(axis /*points*/) {
+				var argArray = $A(arguments).slice(1);
+				return argArray.map(function(point) {
+						var toGraph = this.adjustToGraph(axis, point);
+						var index = this.getDataIndexFromPoint(axis, toGraph);
+						return { index: index, data: this.getData(true).getObjects()[index] };
+				}.bind(this));
+				
+		},
+		
+		//Make selection in graph draggable
+		makeSelectionDraggable: function() {
+				//Attach the ProtoVis drag behavior to the select bar and attach events for drag occurrences
+				this.selectBar = this.selectBar 
+				//Set cursor to be a mouse pointer
+				.cursor("move")
+				.event("mousedown", pv.Behavior.drag())
+						.event("drag", function() {
+								this.fireEvent('drag');
+						}.bind(this))
+						.event("dragstart", function() {
+								this.fireEvent('dragStart');
+						}.bind(this))
+						.event("dragend", function() {
+								if (this.options.fireSelectOnDrag) {
+										//Get objects for edge points
+										var leftPoint = this.dragState.x;
+										var rightPoint = this.dragState.x + this.selectWidth;
+										var objectArray = this.getObjectsForPoints('x', leftPoint, rightPoint);
+										this.fireEvent('spanSelect', objectArray);
+								}
+								this.fireEvent('dragEnd');
+						}.bind(this));
+		},
+		
+		//Make graph selectable
+		makeSelectable: function(){
+				//Create select bar
+				this.selectBar = this.eventPanel.add(pv.Bar);
+				
+				//If there is a need for draggability, make the selection draggable.
+				if (this.draggable) this.makeSelectionDraggable();
+				
+				//Set the basic settings for the selectBar 
+				this.selectBar = this.selectBar
+								//Initialize dragState as selectBar's data.
+								.data([this.dragState])
+								//Set fillStyle to the selectBarColor
+								.fillStyle(this.options.selectBarColor)
+								//Set height so that it only covers the chart
+								.height(this.height - (this.options.bottomPadding + this.options.topPadding))
+								//Set top to start at top padding
+								.top(this.options.topPadding)
+								//Set line width to 1 pixel
+								.lineWidth(1)
+								//Set the left value to the dragState's x value.
+								.left(function(d) {
+										return d.x;
+								});
+				
+				//Initialize selection to nothing
+				this.selectPixelRange(0, 0);
+				//If the initialSelectValue has been set, select that range.
+				if(this.options.initialSelectValue) {
+						this.selectRange(this.options.initialSelectValue.start, this.options.initialSelectValue.end);
+				}
+				//Initialize eventPanel's select events.
+				this.eventPanel
+						.data([this.selectState])
+						.event("mousedown", pv.Behavior.select());
+						 
+				//If d.dx has a value greater than 0...meaning we're in the middle of a
+				//drag, adjust the width value to the graph.
+				//Otherwise give it a width of 0.
+				this.eventPanel
+						.event("selectstart", function() {
+								this.selectBar.width(0);
+								this.selectBar.strokeStyle(this.options.selectBarBorderColor);
+								this.fireEvent('selectStart');
+						}.bind(this))
+						.event("select", function() {
+								this.selectPixelRange(this.adjustToGraph('x', this.selectState.x), this.adjustToGraph('x', this.selectState.x + this.selectState.dx));
+								this.fireEvent('select');
+						}.bind(this))
+						.event("selectend", function() {
+								if (this.selectState.dx > 2) {
+										//Get objects for edge points
+										//left - this.selectState.x
+										//right - this.selectState.dx
+										var objectArray = this.getObjectsForPoints('x', this.selectState.x, this.selectState.x + this.selectState.dx);
+										this.fireEvent('spanSelect', objectArray);
+								}
+								this.fireEvent('selectEnd');
+						}.bind(this));
+		},
+		
+		//Selects a pixel range in the graph.
+		selectPixelRange: function(leftValue, rightValue) {
+				this.dragState.x = this.adjustToGraph('x', leftValue);
+				this.selectWidth = rightValue - leftValue;
+				this.selectBar
+						.width(rightValue - leftValue);
+		},
+		
+		//Selects a range in the graph based on the x value
+		selectRange: function(leftValue, rightValue) {
+				//Convert to points on xScale
+				startX = this.xScale(leftValue);
+				endX = this.xScale(rightValue);
+				this.selectPixelRange(startX, endX);
+				this.selectBar.strokeStyle(this.options.selectBarBorderColor);
+		},
 
-        //Do any cleanup necessary of chart
-        destroy: function() {
-                if(this.tip) {
-                         this.tip.toElement().destroy();
-                         delete this.tip;
-                }
-        } 
+		//Adjusts a point to the graph.  Ie...if you give it a point that's greater than or less than
+		//points in the graph, it will reset it to points within the graph.
+		//This is easily accomplished using the range of the graphing scales.
+		adjustToGraph: function(axis, point){
+				var scale;
+				switch(axis){
+						case 'x': scale = this.xScale;
+									break;
+						case 'y': scale = this.yScale;
+									break;
+						//Return if axis is not x or y.
+						default: return;
+				}
+				//scale.range() returns an array of two values.
+				//The first is the low end of the range, while the second is the highest.
+				var low = scale.range()[0];
+				var high = scale.range()[1];
+				//Return low or high is the value is outside their interval.  Otherwise, return point.
+				if (point < low) return low;
+				if (point > high) return high;
+				return point;
+		},
+		
+		//Updates the display of the currently visible tip
+		updatePointValue: function(series) {
+				if (series != null) {
+						var tipColor = new Element('div', {'class': 'tip-series-color'});
+						var tipBlock = new Element('div', {'class': 'tip-block'});
+						tipBlock.set('text', series.name);
+						tipColor.inject(tipBlock, 'top');
+						tipColor.setStyle('background-color', this.getColor(series.name));
+						this.tip.setTitle(tipBlock);
+						this.tip.setText(this.options.yType == 'bytes' ? series.value.toInt().convertFileSize() : series.value);
+						var tipElem = this.tip.toElement();
+						if(!tipElem.getParent() || !document.body.hasChild(tipElem))tipElem.dispose();
+						this.tip.show();
+				} else {
+						this.tip.hide();
+				}
+		}, 
+
+		//Returns highlighted data index
+		getHighlightedIndex: function() {
+				return this.highlighted_index;
+		},
+
+		//Sets higlighted data index
+		setHighlightedIndex: function(index) {
+				this.highlighted_index = index;
+		},
+
+		//Do any cleanup necessary of chart
+		destroy: function() {
+				if(this.tip) {
+						 this.tip.toElement().destroy();
+						 delete this.tip;
+				}
+		} 
 });
 })();
 

+ 38 - 38
desktop/core/static/js/Source/CCS/HueChart.Line.js

@@ -20,45 +20,45 @@ provides: [HueChart.Line]
 
 HueChart.Line = new Class({
 
-        Extends: HueChart.Box,
+		Extends: HueChart.Box,
 
-        options: {
-                lineWidth: 3 //Width of the lines in the chart
-        },
+		options: {
+				lineWidth: 3 //Width of the lines in the chart
+		},
 
-        initialize: function(element, options) {
-                this.parent(element, options);
-                this.render();
-        },
-        
-        //Build line graph.
-        addGraph: function(vis) {
-                //In effort to maintain same data structure for lines and stacked charts, iterating
-                //through the series and adding a line using each series for its data points.
-                for (var itemIndex = 0; itemIndex < this.options.series.length; itemIndex++) {
-                        //Add a line to the visualization, connecting points produced from the data object.
-                        vis.add(pv.Line)
-                                //Using as data, this.data.getObjects.
-                                .data(this.getData(true).getObjects())
-                                //Closures used because the function argument isn't executed until the render phase.
-                                //Color the line, based on the color returned by the colorManager.
-                                .strokeStyle(function(itemIndex) {
-                                        return function() {
-                                                return this.getColor(this.options.series[itemIndex]);
-                                        }.bind(this);
-                                }.bind(this)(itemIndex))
-                                //For each data object, create a point with its left position at the data object's xField value scaled to pixels and its bottom position at the data object's value for this series scaled to pixels.
-                                .left(function(d) {
-                                        return this.xScale(d[this.options.xProperty]);
-                                }.bind(this))
-                                .bottom(function(itemIndex) {
-                                        return function(d) {
-                                                return this.yScale(d[this.options.series[itemIndex]]);
-                                        }.bind(this);
-                                }.bind(this)(itemIndex))
-                                //Make the line's width 3 pixels.
-                                .lineWidth(this.options.lineWidth);
-                }
-        }
+		initialize: function(element, options) {
+				this.parent(element, options);
+				this.render();
+		},
+		
+		//Build line graph.
+		addGraph: function(vis) {
+				//In effort to maintain same data structure for lines and stacked charts, iterating
+				//through the series and adding a line using each series for its data points.
+				for (var itemIndex = 0; itemIndex < this.series.length; itemIndex++) {
+						//Add a line to the visualization, connecting points produced from the data object.
+						vis.add(pv.Line)
+								//Using as data, this.data.getObjects.
+								.data(this.getData(true).getObjects())
+								//Closures used because the function argument isn't executed until the render phase.
+								//Color the line, based on the color returned by the colorManager.
+								.strokeStyle(function(itemIndex) {
+										return function() {
+												return this.getColor(this.series[itemIndex]);
+										}.bind(this);
+								}.bind(this)(itemIndex))
+								//For each data object, create a point with its left position at the data object's xField value scaled to pixels and its bottom position at the data object's value for this series scaled to pixels.
+								.left(function(d) {
+										return this.xScale(d[this.xProperty]);
+								}.bind(this))
+								.bottom(function(itemIndex) {
+										return function(d) {
+												return this.yScale(d[this.series[itemIndex]]);
+										}.bind(this);
+								}.bind(this)(itemIndex))
+								//Make the line's width 3 pixels.
+								.lineWidth(this.options.lineWidth);
+				}
+		}
 });
 

+ 459 - 395
desktop/core/static/js/Source/CCS/HueChart.js

@@ -28,393 +28,457 @@ provides: [ HueChart ]
 (function() {
 var colorManager = GroupValueManager;
 
-Date.extend({
-        spans: ['ms', 'second', 'minute', 'hour', 'day'],
-        subspan: function(span) {
-                return this.spans[this.spans.indexOf(span) - 1];
-        }
-});
-
-Date.implement({
-        truncateTo: function(target) {
-                //Set value of spans below truncation target to 0
-                var subspan = Date.subspan(target);
-                var subspanIndex = Date.spans.indexOf(subspan);
-                var toZero = Date.spans.slice(0, subspanIndex + 1);
-                var truncated = 0;
-                toZero.each(function(zeroee) {
-                        //Zeroees stores the plural version of zeroee, used in get and set
-                        var zeroees = zeroee;
-                        if (zeroee != 'ms') {
-                                zeroees += 's';
-                        }
-                        truncated += Date.units[zeroee]() * this.get(zeroees);
-                        this.set(zeroees, 0);
-                }.bind(this));       
-                return truncated;
-        }
-});
-
 HueChart = new Class({
 
-        Implements: [Events, Options],
-
-        options: {
-                //The array of colors which will be used for the chart.
-                colorArray:  [
-                        '#1f77b4',
-                        '#aec7e8',
-                        '#ff7f0e',
-                        '#ffbb78',
-                        '#2ca02c',
-                        '#98df8a',
-                        '#d62728',
-                        '#ff9896',
-                        '#9467bd',
-                        '#c5b0d5',
-                        '#8c564b',
-                        '#c49c94',
-                        '#e377c2',
-                        '#f7b6d2',
-                        '#7f4f7f',
-                        '#c7c7c7',
-                        '#bcbd22',
-                        '#dbdb8d',
-                        '#17becf',
-                        '#9edae5'
-                ],
-                //The font which will be used for the axis labels.
-                labelFont: '14px sans-serif',
-                //The height of the x axis rules
-                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
-                */
-                padding:[0], //the array of padding values.  Accepts between 1 and 4 values, in CSS TRBL format.  Also allows named padding values below.
-                  //If there are named values, they will supercede the result from the padding array.  
-                  /*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,*/
-                  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);
-                //Width and height will potentially change often, make them instance variables.
-                this.width = this.options.width;
-                this.height = this.options.height;
-                //Process padding array with named values -- interpreted in same way CSS side-oriented values are.
-                this.options.padding = $splat(this.options.padding);
-                this.options.topPadding = $pick(this.options.topPadding, this.options.padding[0]);
-                this.options.rightPadding = $pick(this.options.rightPadding, this.options.padding[1], this.options.padding[0]);
-                this.options.bottomPadding = $pick(this.options.bottomPadding, this.options.padding[2], this.options.padding[0]);
-                this.options.leftPadding = $pick(this.options.leftPadding, this.options.padding[3], this.options.padding[1], this.options.padding[0]);
-                var table = document.id(this.options.dataTable);
-                if (table) {
-                        this.data = new HueChart.Data(HueChart.buildData(table));
-                        table.hide();
-                } else {
-                        this.data = new HueChart.Data(this.options.dataObject);
-                }
-
-                //Setup color manager
-                this.colorManager = colorManager;
-                this.colorManager.define(this.options.url, this.options.colorArray);
-        },
-
-        //Resize graph to width and height.
-        resize: function(width, height) {
-                this.width = width;
-                this.height = height;
-                this.render();
-        },
-
-        //Hide graph.
-        hide: function(){
-                this.element.hide();
-        },
-        
-        //Show graph.
-        show: function(){
-                this.element.show();
-        },
-        
-        //Render graph.
-        render: function() {
-                this.vis = new pv.Panel();
-                this.vis.width(this.width)
-                        .height(this.height);
-                this.vis.$dom = this.element.empty();
-                this.fireEvent('setupChart', this.vis);
-                this.vis.render();
-        },
-        
-        //Returns selected data index
-        getSelectedIndex: function() {
-                return this.selected_index;
-        },
-        
-        //Sets selected data index
-        setSelectedIndex: function(index) {
-                this.selected_index = index;
-        },
-        
-        
-        //Set the currentData, which is the data currently being displayed, assuming it is different from the base data.  Should be a subset of the main data object. 
-        setCurrentData: function(data) {
-                this.currentData = new HueChart.Data(data);
-        },
-        
-        //Return a data object.
-        //current- (boolean) if true and there is a currentData object, return the currentData object.
-        //Otherwise, return the base data object.
-        getData: function(current) {
-                if (current && this.currentData) return this.currentData;
-                return this.data;
-        },
-        
-        //Delete this.currentData to reset data to the base data.
-        resetData: function() {
-                delete this.currentData;
-        },
-
-        //Check that there is an event handler registered for an event
-        hasEvent: function(name) {
-                return this.$events[name] && this.$events[name].length;
-        },
-
-        //Get color given a series
-        getColor: function(series) {
-                return this.colorManager.get(this.options.url, series);
-        }
+		Implements: [Events, Options],
+
+		options: {
+				//The array of colors which will be used for the chart.
+				colorArray:  [
+						'#1f77b4',
+						'#aec7e8',
+						'#ff7f0e',
+						'#ffbb78',
+						'#2ca02c',
+						'#98df8a',
+						'#d62728',
+						'#ff9896',
+						'#9467bd',
+						'#c5b0d5',
+						'#8c564b',
+						'#c49c94',
+						'#e377c2',
+						'#f7b6d2',
+						'#7f4f7f',
+						'#c7c7c7',
+						'#bcbd22',
+						'#dbdb8d',
+						'#17becf',
+						'#9edae5'
+				],
+				//The font which will be used for the axis labels.
+				labelFont: '14px sans-serif',
+				//The height of the x axis rules
+				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
+				*/
+				padding:[0], //the array of padding values.  Accepts between 1 and 4 values, in CSS TRBL format.  Also allows named padding values below.
+				  //If there are named values, they will supercede the result from the padding array.  
+				  /*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,*/
+				  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);
+				//Width and height will potentially change often, make them instance variables.
+				this.width = this.options.width;
+				this.height = this.options.height;
+				//Process padding array with named values -- interpreted in same way CSS side-oriented values are.
+				this.options.padding = $splat(this.options.padding);
+				this.options.topPadding = $pick(this.options.topPadding, this.options.padding[0]);
+				this.options.rightPadding = $pick(this.options.rightPadding, this.options.padding[1], this.options.padding[0]);
+				this.options.bottomPadding = $pick(this.options.bottomPadding, this.options.padding[2], this.options.padding[0]);
+				this.options.leftPadding = $pick(this.options.leftPadding, this.options.padding[3], this.options.padding[1], this.options.padding[0]);
+				var table = document.id(this.options.dataTable);
+				if (table) {
+						this.data = new HueChart.Data(HueChart.buildData(table));
+						table.hide();
+				} else {
+						this.data = new HueChart.Data(this.options.dataObject);
+				}
+
+				//Setup color manager
+				this.colorManager = colorManager;
+				this.colorManager.define(this.options.url, this.options.colorArray);
+		},
+
+		//Resize graph to width and height.
+		resize: function(width, height) {
+				this.width = width;
+				this.height = height;
+				this.render();
+		},
+
+		//Hide graph.
+		hide: function(){
+				this.element.hide();
+		},
+		
+		//Show graph.
+		show: function(){
+				this.element.show();
+		},
+		
+		//Render graph.
+		render: function() {
+				this.vis = new pv.Panel();
+				this.vis.width(this.width)
+						.height(this.height);
+				this.vis.$dom = this.element.empty();
+				this.fireEvent('setupChart', this.vis);
+				this.vis.render();
+		},
+		
+		//Returns selected data index
+		getSelectedIndex: function() {
+				return this.selected_index;
+		},
+		
+		//Sets selected data index
+		setSelectedIndex: function(index) {
+				this.selected_index = index;
+		},
+		
+		//Change full data object
+		setData: function(data) {
+				this.data = new HueChart.Data(data);
+				delete this.currentData;
+				if (this.hasData() && this.initializeData) this.initializeData();
+		},
+
+		//Set the currentData, which is the data currently being displayed, assuming it is different from the base data.  Should be a subset of the main data object. 
+		setCurrentData: function(data) {
+				this.currentData = new HueChart.Data(data);
+		},
+		
+		//Return a data object.
+		//current- (boolean) if true and there is a currentData object, return the currentData object.
+		//Otherwise, return the base data object.
+		getData: function(current) {
+				if (current && this.currentData) return this.currentData;
+				return this.data;
+		},
+		
+		//Delete this.currentData to reset data to the base data.
+		resetData: function() {
+				delete this.currentData;
+		},
+
+		//Add data series
+		addData: function(data, dateProperty) {
+			return this.data.addData(data, dateProperty);
+		},
+
+		hasData: function() {
+			return this.getData().getLength() > 0;
+		},
+
+		//Check that there is an event handler registered for an event
+		hasEvent: function(name) {
+				return this.$events[name] && this.$events[name].length;
+		},
+
+		//Get color given a series
+		getColor: function(series) {
+				return this.colorManager.get(this.options.url, series);
+		}
 });
 
 //Wrapper for data object to be charted.
 //Adds various functions on the data.
 HueChart.Data = new Class({
-        
-        initialize: function(data) {
-                //Check if it's a HC.Data object, and return the data if it is. 
-                if(data._getExtreme) return data;
-                //Otherwise, set the dataObjects property to data.
-                this.dataObjects = data;
-        },
-        
-        //Function to sort by dates and convert date strings into date objects.
-        //Also creates ms_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 in each object to date object
-                this.dataObjects.each(function(d) {
-                        d[dateProperty] = this.parseDate(d[dateProperty]);
-                }.bind(this));
-                //Sort data by date property.
-                this.sortByProperty(dateProperty);
-                //Store first date for comparison.
-                var firstDate = this.dataObjects[0][dateProperty];
-                this.getMsFromFirst = function(dateObject) {
-                        return firstDate.diff(dateObject, 'ms'); 
-                };
-                //Create ms from first, for comparison sake.
-                this.dataObjects.each(function(d) {
-                        d.ms_from_first = this.getMsFromFirst(d[dateProperty]);
-                }.bind(this));
-        },
-        
-        //Parse date -- accepts date formats parsable in Native/Date.js, or integral ms values.
-        parseDate: function(d) {
-                //Check if dateProperty value is parsable as string.
-                //Parse as string if it is, otherwise parse as ms value.
-                var parseResult = Date.parse(d);
-                if (parseResult.isValid()) {
-                        return parseResult;
-                } else {
-                        parseResult = Date.parse(d.toInt());
-                        if (parseResult.isValid()) return parseResult;
-                }
-        },
-
-        //Sort data by some property within it.
-        sortByProperty: function(property) {
-                this.dataObjects.sort(function(a, b) {
-                        if (a[property] < b[property]) return -1;
-                        if (a[property] == b[property]) return 0;
-                        if (a[property] > b[property]) return 1;
-                });
-        },
-
-        //Sort data by the return value of a function which takes as an argument an element of the data array. 
-        sortByFunction: function(sortFn) {
-                this.dataObjects.sort(function(a, b) {
-                        if (sortFn(a) < sortFn(b)) return -1;
-                        if (sortFn(a) == sortFn(b)) return 0;
-                        if (sortFn(a) > sortFn(b)) return 1;
-                });
-        },
-
-        //Get the maximum value of the values in the data table.
-        getMaxValue: function(seriesToInclude) {
-                return this._getExtreme('max', seriesToInclude, pv.max);
-        },
-        
-        //Get the peak sum of the values in a given object in the data table.
-        getPeakSum: function(seriesToInclude) {
-                return this._getExtreme('peak', seriesToInclude, pv.max);
-        },
-        
-        //Get min value of the values in the data table.
-        getMinValue: function(seriesToInclude) {
-                return this._getExtreme('min', seriesToInclude, pv.min);
-        },
-        
-        //Get the min sum of the values in a 
-        getMinSum: function(seriesToInclude) {
-                return this._getExtreme('valley', seriesToInclude, pv.min);
-        },
-        
-        getSeriesSum: function(series) {
-                return pv.sum(this.dataObjects, function(d) { return d[series]; });
-        },
-               
-        //Get an extreme value from the data table.
-        //seriesToInclude can be a single field in the data object or an array of fields. 
-        _getExtreme:  function(type, seriesToInclude, extremeFn) {
-                seriesToInclude = $splat(seriesToInclude);
-                //Use extremeFn, which should be either pv.min or pv.max to iterate over data objects.
-                return extremeFn(this.dataObjects, function(d) {
-                        //If looking for min, initialize currentExtreme to max value.
-                        //If looking for max, initialize currentExtreme to min value.
-                        var currentExtreme = (extremeFn == pv.min ? Number.MAX_VALUE : Number.MIN_VALUE);
-                        //Iterate through the data object
-                        Hash.each(d, function(value, key) {
-                                //If the key is one of the fields we're inspecting.
-                                if (seriesToInclude.contains(key)) {
-                                        switch (type){
-                                                case 'max':
-                                                        if (Number(value) > currentExtreme) currentExtreme = Number(value);
-                                                        break;
-                                                case 'min':
-                                                        if (Number(value) < currentExtreme) currentExtreme = Number(value);
-                                                        break;
-                                                case 'peak':
-                                                        currentExtreme += Number(value);
-                                                        break;
-                                                case 'valley':
-                                                        currentExtreme += Number(value);
-                                                        break;
-                                        }
-
-                                };
-                        });
-                        return currentExtreme; 
-                });
-        },
-        
-        //Return the array of data objects
-        getObjects: function() {
-                return this.dataObjects;
-        },
-
-        //Uses the protovis pv.normalize function to return an array containing values which sum to 1
-        //Essentially returns an array containing the percentage that each dataObject's field value
-        //contributes to the sum of the dataObjects' array field values.
-
-        getNormalizedForField: function(field) {
-                pv.normalize(this.dataObjects, function(d) { return d[field]; });        
-        },
-
-        getLength: function() {
-                return this.dataObjects.length;
-        },
-
-        //Return a HueChart.Data object containing the dataObjects from the first index to the last index.
-        //Optional argument flatArray - boolean.  If true, will return a flatArray object. 
-        getRange: function(first, last, flatArray) {
-                var sliced = this.dataObjects.slice(first, last);
-                if (flatArray) return sliced;
-                return new HueChart.Data(sliced);
-        },
-        
-        //Need to figure out an answer for this.  This is very date specific.  
-        //Possibly need to genericize a bit more.
-        /* Get Ticks --
-                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, dateProperty) {
-                //Get the largest number of ms.
-                var mostMs = this.dataObjects.getLast().ms_from_first;
-                //Get the smallest number of ms.
-                var leastMs = this.dataObjects[0].ms_from_first;
-                //Get the total number of ms spanned by the array.
-                var totalMs = mostMs - leastMs;
-                //Get the number of ms from Date.js
-                var msInSpan = Date.units[timespan]();
-                var xTicks = [];
-                //The number of spans contained in the total span of ms 
-                var spansContained = totalMs/msInSpan;
-                //If the number of ticks that would be generated for the timespan is less than the number of ticks requested and the number of dataObjects is less than the number of ticks, use the current data array.
-                if (spansContained <= numTicks && this.dataObjects.length < numTicks) {
-                        xTicks = this.dataObjects;
-                } else {
-                //Generate ticks.
-                        //Calculate the size of the increment between ticks to produce the number of ticks. 
-                        //Find number of timespans in an increment.
-                        var spansInIncrement = Math.ceil(spansContained/numTicks);
-                        //Find msToIncrement each value by
-                        var msInIncrement = spansInIncrement * msInSpan; 
-                        var firstDate = this.dataObjects[0][dateProperty];
-                        var firstMs = this.dataObjects[0].ms_from_first;
-                        //Add ticks to the xTicks array.
-                        //Sample date - firstDate cloned and incremented by the increment times the iteration number.
-                        //Ms_from_first - dateIncrement multiplied by the iteration number.
-                        for (var i = 0; i <= Math.min(numTicks, spansContained); i++) {
-                                var tickObject = {};
-                                tickObject[dateProperty] = firstDate.clone().increment('ms', msInIncrement * i);
-                                tickObject.ms_from_first = firstMs + (msInIncrement * i);
-                                xTicks.push(tickObject);
-                        }
-                        //Ensure tick is exactly on timespan
-                        xTicks.each(function(tick) {
-                                //Truncate the date to timespan and get the amount of the subspan which was truncated
-                                var msToTruncate = tick[dateProperty].truncateTo(timespan);
-                                //Adjust ms from first by subtracting the number of ms in the truncated subspans
-                                tick.ms_from_first -= msToTruncate; 
-                        });
-                        var lastDataPoint = this.dataObjects.getLast();
-                        //Filter xTicks to only contain numbers on chart
-                        xTicks = xTicks.filter(function(tick) {
-                                if(tick.ms_from_first >= 0 && lastDataPoint.ms_from_first >= tick.ms_from_first) return true; 
-                        });
-                        //Center tick marks.
-                        var lastTick = xTicks.getLast();
-                        var firstDataPoint = this.dataObjects[0];
-                        var firstTick = xTicks[0];
-                        //Get the number of spans between the lastDataPoint and the lastTick
-                        var spansAtEnd = (lastDataPoint.ms_from_first - lastTick.ms_from_first)/msInSpan;
-                        //Get the number of spans between the firstDataPoint and the firstTick
-                        var spansAtBeginning = (firstTick.ms_from_first - firstDataPoint.ms_from_first)/msInSpan; 
-                        //Get number of spans to move ticks forward to result in evenly spaced, centered ticks.
-                        var centerAdjustment = parseInt((spansAtEnd - spansAtBeginning)/2, 10);
-                        xTicks.each(function(tick) {
-                                tick[dateProperty].increment('ms', msInSpan * centerAdjustment);
-                                tick.ms_from_first += msInSpan * centerAdjustment;
-                        });
-                }
-                return xTicks;
-        }
-
+		
+		initialize: function(data) {
+				//Check if it's a HC.Data object, and return the data if it is. 
+				if(data._getExtreme) return data;
+				//Otherwise, set the dataObjects property to data.
+				this.dataObjects = data;
+		},
+
+		//Add data series
+		//Argument is an array of objects containing dateProperty, and series entries
+		//Data object should be same time frame as current dataSet
+		//Returns boolean reflecting whether or not data was successfully added
+		addData: function(data, dateProperty) {
+			var dataAdded = false;
+		    //Integrate data into current data object.
+			var dataLength = data.length;
+			var dataObject, datum;
+			for (var i = 0; i < dataLength; i++) {
+				dataObject = this.dataObjects[i];
+				datum = data[i];
+				if (dataObject[dateProperty].valueOf() == datum[dateProperty]) {
+					//Remove dateProperty so we don't overwrite it.
+					delete datum[dateProperty];
+					dataObject = $merge(dataObject, datum);
+					if(!dataAdded) dataAdded = true;
+			    }
+			}
+			return dataAdded;
+		},
+		
+		//Function to sort by dates and convert date strings into date objects.
+		//Also creates ms, a useful integer value for graphing.
+		//Accepts date formats parsable in Native/Date.js, or integral ms values.
+		prepareDates: function(dateProperty) {
+				//Convert date string in each object to date object
+				this.dataObjects.each(function(d) {
+						d[dateProperty] = this.parseDate(d[dateProperty]);
+				}.bind(this));
+				//Sort data by date property.
+				this.sortByProperty(dateProperty);
+				//Store first date for comparison.
+				//Create ms, so as to not have to continually look it up.
+				this.dataObjects.each(function(d) {
+						d.ms = d[dateProperty].valueOf();
+				}.bind(this));
+		},
+		
+		//Parse date -- accepts date formats parsable in Native/Date.js, or integral ms values.
+		parseDate: function(d) {
+				//Check if dateProperty value is parsable as string.
+				//Parse as string if it is, otherwise parse as ms value.
+				var parseResult = Date.parse(d);
+				if (parseResult.isValid()) {
+						return parseResult;
+				} else {
+						parseResult = Date.parse(d.toInt());
+						if (parseResult.isValid()) return parseResult;
+				}
+		},
+
+		//Sort data by some property within it.
+		sortByProperty: function(property) {
+				this.dataObjects.sort(function(a, b) {
+						if (a[property] < b[property]) return -1;
+						if (a[property] == b[property]) return 0;
+						if (a[property] > b[property]) return 1;
+				});
+		},
+
+		//Sort data by the return value of a function which takes as an argument an element of the data array. 
+		sortByFunction: function(sortFn) {
+				this.dataObjects.sort(function(a, b) {
+						if (sortFn(a) < sortFn(b)) return -1;
+						if (sortFn(a) == sortFn(b)) return 0;
+						if (sortFn(a) > sortFn(b)) return 1;
+				});
+		},
+
+		//Get the maximum value of the values in the data table.
+		getMaxValue: function(seriesToInclude) {
+				return this._getExtreme('max', seriesToInclude, pv.max);
+		},
+		
+		//Get the peak sum of the values in a given object in the data table.
+		getPeakSum: function(seriesToInclude) {
+				return this._getExtreme('peak', seriesToInclude, pv.max);
+		},
+		
+		//Get min value of the values in the data table.
+		getMinValue: function(seriesToInclude) {
+				return this._getExtreme('min', seriesToInclude, pv.min);
+		},
+		
+		//Get the min sum of the values in a 
+		getMinSum: function(seriesToInclude) {
+				return this._getExtreme('valley', seriesToInclude, pv.min);
+		},
+		
+		getSeriesSum: function(series) {
+				return pv.sum(this.dataObjects, function(d) { return d[series]; });
+		},
+			   
+		//Get an extreme value from the data table.
+		//seriesToInclude can be a single field in the data object or an array of fields. 
+		_getExtreme:  function(type, seriesToInclude, extremeFn) {
+				seriesToInclude = $splat(seriesToInclude);
+				//Use extremeFn, which should be either pv.min or pv.max to iterate over data objects.
+				return extremeFn(this.dataObjects, function(d) {
+						//If looking for min, initialize currentExtreme to max value.
+						//If looking for max, initialize currentExtreme to min value.
+						var currentExtreme = (extremeFn == pv.min ? Number.MAX_VALUE : Number.MIN_VALUE);
+						//Iterate through the data object
+						Hash.each(d, function(value, key) {
+								//If the key is one of the fields we're inspecting.
+								if (seriesToInclude.contains(key)) {
+										switch (type){
+												case 'max':
+														if (Number(value) > currentExtreme) currentExtreme = Number(value);
+														break;
+												case 'min':
+														if (Number(value) < currentExtreme) currentExtreme = Number(value);
+														break;
+												case 'peak':
+														currentExtreme += Number(value);
+														break;
+												case 'valley':
+														currentExtreme += Number(value);
+														break;
+										}
+
+								};
+						});
+						return currentExtreme; 
+				});
+		},
+		
+		//Return the array of data objects
+		getObjects: function() {
+				return this.dataObjects;
+		},
+
+		//Uses the protovis pv.normalize function to return an array containing values which sum to 1
+		//Essentially returns an array containing the percentage that each dataObject's field value
+		//contributes to the sum of the dataObjects' array field values.
+
+		getNormalizedForField: function(field) {
+				pv.normalize(this.dataObjects, function(d) { return d[field]; });        
+		},
+
+		getLength: function() {
+				return this.dataObjects.length;
+		},
+
+		//Return a HueChart.Data object containing the dataObjects from the first index to the last index.
+		//Optional argument flatArray - boolean.  If true, will return a flatArray object. 
+		getRange: function(first, last, flatArray) {
+				var sliced = this.dataObjects.slice(first, last);
+				if (flatArray) return sliced;
+				return new HueChart.Data(sliced);
+		},
+		
+
+		// TODO: This should become class static constant instead of object property
+		tickIntervals: {
+			ms: [1, 10, 100, 500],
+			second: [1, 5, 10, 15, 30],
+			minute: [1, 2, 5, 10, 15, 30],
+			hour: [1, 3, 6, 12],
+			day: [1, 3, 7, 14, 28],
+			month: [1, 2, 3, 6],
+			year: [1, 2, 5, 10, 20, 50, 100]
+		},
+		tickUniformUnits: ['ms','second','minute','hour','day'],
+		
+		// TODO: This should become class static constant instead of object property
+		
+		
+		dataMsProperty: "ms",
+		/* getTicks --
+				Returns an array of data objects to be marked as ticks on the domain (x) axis.
+				Arguments:
+				maxTickCount (integer) - the upper bound on size of the return array
+				dateProperty (string) - the property which stores date objects
+				startMs (integer) - milliseconds since some reference time
+				endMs (integer) - milliseconds since the same reference time
+		*/
+		getTicks: function(maxTickCount, dateProperty, startMs, endMs) {
+			var maxIntervalCount = maxTickCount + 1;
+			
+			if (!$defined(startMs)) {
+				startMs = this.dataObjects[0][this.dataMsProperty];
+				endMs = this.dataObjects.getLast()[this.dataMsProperty];
+			}
+			
+			var domainMs = endMs - startMs;
+			var adjustedDomainMs, unit, unitMs, intervals, interval, intervalMs, tickObject;
+			
+			var uniformUnitsLength = this.tickUniformUnits.length;
+			unitsLoop:
+				for (var i=0; i<uniformUnitsLength; i++) {
+					unit = this.tickUniformUnits[i];
+					unitMs = Date.units[unit]();
+					intervals = this.tickIntervals[unit];
+					if (intervals.getLast() * unitMs * maxIntervalCount > domainMs) {
+						// Very good chance we've found the right unit; check each interval
+						for (var j=0; j<intervals.length; j++) {
+							interval = intervals[j];
+							intervalMs = interval * unitMs;
+							adjustedDomainMS = domainMs + startMs % unitMs;
+							if (intervalMs * maxIntervalCount > adjustedDomainMS) {
+								break unitsLoop;
+							}
+						}
+					}
+				}
+			
+			var ticksMs = [];
+			
+			if (i < uniformUnitsLength) {
+				// Broke free from uniform unit loop; selected unit has uniform intervals.
+				// Get tick marks with intervals aligned at 0 mod intervalMs
+				var tickMs = startMs + intervalMs - startMs % intervalMs;
+				var tickDate = Date.parse(tickMs);
+				while (tickMs < endMs) {
+					tickObject = {};
+					tickObject[this.dataMsProperty] = tickMs;
+					tickObject[dateProperty] = tickDate;
+					tickMs += intervalMs;
+					tickDate = tickDate.clone().increment('ms', intervalMs);
+					ticksMs.push(tickObject);
+				}
+			} else {
+				// Months/year tick intervals not uniform in number of days (or ms)
+				unit = null;
+				
+				var date = Date.parse(startMs);
+				var endDate = Date.parse(endMs);
+				
+				var startUnitIndex = date.get('month');
+				var domainSize = (endDate.get('year') - date.get('year')) * 12 +
+					endDate.get('month') - startUnitIndex;
+				
+				intervals = this.tickIntervals['month'];
+				if (intervals.getLast() * maxIntervalCount > domainSize) {
+					// Very good chance we should use months; check each interval
+					for (j=0; j<intervals.length; j++) {
+						interval = intervals[j];
+						adjustedDomainSize = domainSize + startUnitIndex % interval;
+						if (interval * maxIntervalCount > adjustedDomainSize) {
+							unit = 'month';
+							break;
+						}
+					}
+				}
+				
+				if (!unit) {
+					startUnitIndex = date.get('year');
+					domainSize = endDate.get('year') - startUnitIndex;
+					
+					intervals = this.tickIntervals['year'];
+					for (j=0; true; j++) {
+						// If max interval not big enough, then double interval until big enough
+						interval = intervals[j] || interval * 2;
+						adjustedDomainSize = domainSize + startUnitIndex % interval;
+						if (interval * maxIntervalCount > adjustedDomainSize) {
+							unit = 'year';
+							break;
+						}
+					}
+				}
+				
+				// Adjust date to align tick marks at 0 mod interval
+				date.increment(unit, interval - startUnitIndex % interval);
+				
+				// Get tick marks using Date.increment and convert each to ms
+				var dateMs;
+				tickObjects = [];
+				do {
+					tickObject = {};
+					dateMs = date.valueOf();
+					tickObject[this.dataMsProperty] = dateMs;
+					tickObject[dateProperty] = date.clone();
+					ticksMs.push(tickObject);
+					date.increment(unit, interval);
+				} while (dateMs < endMs);
+			}
+			
+			return {
+				'ticks': ticksMs,
+				'unit': unit
+			};
+		}
 });
 
 //Function to build an array of data objects from a table.
@@ -425,21 +489,21 @@ HueChart.Data = new Class({
    val1-1  va11-2  value1-3  value1-4
    val2-1  val2-2  val2-3  val2-4
 */
-HueChart.buildData = function(table) {
-        data = [];
-        //Iterate through headers.
-        var headers = $$(table.getElements('th')).map(function(header) {
-               return header.get('text');
-        });
-        //Iterate through table row and cells.
-        $$(table.getElements('tbody tr')).each(function(row) {
-                var datum = {};
-                $$(row.getElements('td')).each(function(cell, i) {
-                       datum[headers[i]] = cell.get('text'); 
-                });
-                data.push(datum);        
-        });
-        return data;
+HueChart.buildDataFromTable = function(table) {
+		data = [];
+		//Iterate through headers.
+		var headers = $$(table.getElements('th')).map(function(header) {
+			   return header.get('text');
+		});
+		//Iterate through table row and cells.
+		$$(table.getElements('tbody tr')).each(function(row) {
+				var datum = {};
+				$$(row.getElements('td')).each(function(cell, i) {
+					   datum[headers[i]] = cell.get('text'); 
+				});
+				data.push(datum);
+		});
+		return data;
 };
 
 })();