瀏覽代碼

HUE-278. Add HueCharts library to Hue
Move Number.Files.js up to core/static
Add HueChart, HueChart.Area, .Box, .Line and corresponding package.yml changes
Add HueChart.Circle
Response to Nutron's review comments on HueChart.

Marcus McLaughlin 15 年之前
父節點
當前提交
11af0ac5f0

+ 1 - 1
apps/filebrowser/src/filebrowser/static/js/package.yml

@@ -2,4 +2,4 @@ copyright: Apache License v2.0
 version: 0.9
 description: File Browser
 name: filebrowser
-sources: [Source/FileBrowser/CCS.FileBrowser.js, Source/FileBrowser/CCS.FileViewer.js, Source/FileBrowser/CCS.FileEditor.js, Source/Native/Number.Files.js]
+sources: [Source/FileBrowser/CCS.FileBrowser.js, Source/FileBrowser/CCS.FileViewer.js, Source/FileBrowser/CCS.FileEditor.js]

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

@@ -0,0 +1,79 @@
+/*
+---
+
+script: HueChart.Area.js
+
+description: Defines HueChart.Area, builds on HueChart.Box to produce stacked area graphs.
+
+license: MIT-style license
+
+authors:
+ - Marcus McLaughlin
+
+requires:
+ - ccs-shared/HueChart.Box
+
+provides: [HueChart.Area]
+...
+*/
+
+//Builds a stacked area graph.
+HueChart.Area = new Class({
+
+        Extends: HueChart.Box,
+
+        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.data.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);
+        },
+
+        //Build stacked area graph.
+        addGraph: function(vis) {
+                //Put colorArray in scope for fillStyle fn.
+                var colorArray = this.options.colorArray;
+                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.data array
+                        .values(this.data.getObjects())
+                        //The two commands below will be run this.series.length * this.data.length times.
+                        //The x-value, in pixels, is calculated as a conversion of the data object's xField value using the xScale.
+                        .x(function(d) {
+                                return this.xScale(d[this.options.xField]);
+                        }.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() {
+                                return colorArray[this.parent.index % colorArray.length];
+                        });
+        }
+
+});
+

+ 221 - 0
desktop/core/static/js/Source/CCS/HueChart.Box.js

@@ -0,0 +1,221 @@
+/*
+---
+
+script: HueChart.Box.js
+
+description: Defines HueChart.Box, which builds on HueChart and serves as a base class to build charts which are rectangular in nature, having x and y axes.
+
+license: MIT-style license
+
+authors:
+- Marcus McLaughlin
+
+requires:
+- protovis/Protovis
+- More/Date
+- Core/Events
+- Core/Options
+- ccs-shared/Number.Files
+- ccs-shared/HueChart
+
+provides: [ HueChart.Box ]
+
+...
+*/
+
+HueChart.Box = new Class({
+
+        Extends: HueChart,
+
+         options: {
+                //series: [], //the array of data series which are found in the chart data,
+                //xField: '', //the field in the data table which should be used as the xAxis label, and for determining where points are on the xAxis
+                xIsDate: false, //is the xField a date ?
+                positionIndicator: false, //should the position indicator be shown ?
+                ticks: false, //should tick marks be shown ?
+                labels: false, //should axis labels be shown ?
+                tickColor: "#555", //the color of the tick marks
+                dateFormat: "%b %d", //the format that should be used when displaying dates
+                verticalTickSpacing: 35, //the distance between vertical ticks
+                yFieldDisplayProcessor: function(field) {
+                        return field.replace("_", " ").capitalize();
+                }, //fn to use to process the yField for display, field is this.options.yField
+                xTickHeight: 10,
+                xLabel: "Date"
+                /*
+                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
+                */
+        },
+        
+
+        initialize: function(element, options) {
+                this.parent(element, options);
+                var requiredOptions = ["series", "xField"];
+                requiredOptions.each(function(opt) {
+                        if (!$defined(this.options[opt])) console.error("The required option " + opt + " is missing from a HueChart.Box instantiation.");
+                }.bind(this));
+                this.selected_index = -1;
+                if(this.options.xIsDate) {
+                        //If the xField is a date property, prepare the dates for sorting
+                        //Change the xField to the new property designed for sorting dates
+                        this.data.prepareDates(this.options.xField);
+                        this.options.xField = 'seconds_from_first';
+                } else {
+                        //Otherwise sort by the x property.
+                        this.data.sortByProperty(this.options.xField);
+                }
+                //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) this.setTicks(vis);
+                        //Add axis labels if enabled
+                        if (this.options.labels) this.setLabels(vis);
+                        //Add position indicator if enabled
+                        if (this.options.positionIndicator) this.setPositionIndicator(vis);
+                        //If there's an event, add the event bar to capture these events.
+                        if (this.$events.pointMouseOut && this.$events.pointMouseOver) this.addEventBar(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.data.getMinValue(this.options.xField);
+                var xMax = this.data.getMaxValue(this.options.xField);
+                //Get the maximum of the values that are to be graphed
+                var maxValue = this.data.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)));
+        },
+        
+        //Draw the X and Y tick marks.
+        setTicks:function(vis) {
+                //Add X-Ticks.
+                //Create tick array.
+                var xTicks = (this.options.xIsDate ? this.data.createTickArray(7, 'day') : this.xScale.ticks(7));
+                //Create rules (lines intended to denote scale)
+                vis.add(pv.Rule)
+                        //Use the tick array as data.
+                        .data(xTicks)
+                        //The bottom of the rule should be at the bottomPadding - the height of the rule.  
+                        .bottom(this.options.bottomPadding - this.options.xTickHeight)
+                        //The left of the rule should be at the data object's xField value scaled to pixels.
+                        .left(function(d) { return this.xScale(d[this.options.xField]); }.bind(this))
+                        //Set the height of the rule to the xTickHeight
+                        .height(this.options.xTickHeight)
+                        .strokeStyle(this.options.ruleColor)
+                        //Add label to bottom of each rule
+                        .anchor("bottom").add(pv.Label)
+                                .text(function(d) {
+                                        //If the option is a date, format the date property field.
+                                        //Otherwise, simply show it.
+                                        if(this.options.xIsDate) {
+                                                return d.sample_date.format(this.options.dateFormat);
+                                        } else {
+                                                return d[this.options.xField];
+                                        }
+                                }.bind(this));
+               
+                //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 minuis the hoizontal padding.
+                        .width(this.width - this.options.leftPadding - this.options.rightPadding + 1)
+                        .strokeStyle("#555")
+                        //Add label to the left which shows the number of bytes.
+                        .anchor("left").add(pv.Label)
+                                .text(function(d) { 
+                                        if(this.options.yField == 'bytes') return d.convertFileSize(); 
+                                        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(function() {
+                                return this.options.yFieldDisplayProcessor(this.options.yField.replace("_", " ").capitalize());
+                        }.bind(this))
+                        .font(this.options.labelFont)
+                        .left(12);
+                
+                //Add X-Label to center of chart.
+                vis.anchor("bottom").add(pv.Label)
+                        .text(this.options.xLabel)
+                        .font(this.options.labelFont)
+                        .bottom(0);
+        },
+
+        //Add a bar which indicates the position which is currently selected on the bar graph.
+        setPositionIndicator: function(vis) {
+                //Put selected_index in scope.
+                get_selected_index = this.getSelectedIndex.bind(this);
+                //Add a thin black bar which is approximately the height of the graphing area for each item on the graph.
+                vis.add(pv.Bar)
+                        .data(this.data.getObjects())
+                        .left(function(d) { 
+                                return this.xScale(d[this.options.xField]); 
+                        }.bind(this))
+                        .height(this.height - (this.options.bottomPadding + this.options.topPadding))
+                        .bottom(this.options.bottomPadding)
+                        .width(2)
+                        //Show bar if its index is selected, otherwise hide it.
+                        .fillStyle(function() {
+                                if(this.index == get_selected_index()) return "black";
+                                else return null;
+                        });
+        },
+        
+        //Add bar detecting mouse events.
+        addEventBar: function(vis) {
+                //Create functions which handle the graph specific aspects of the event and call the event arguments.
+                var outVisFn = function() {
+                        this.selected_index = -1;
+                        this.fireEvent('pointMouseOut');
+                        return vis;
+                }.bind(this);
+                var moveVisFn = function() {
+                        //Convert the mouse's xValue into its corresponding data value on the xScale. 
+                        var mx = this.xScale.invert(vis.mouse().x);
+                        //Search the data for the index of the element at this data value.
+                        i = pv.search(this.data.getObjects().map(function(d){ return d[this.options.xField]; }.bind(this)), Math.round(mx));
+                        //Adjust for ProtoVis search
+                        i = i < 0 ? (-i - 2) : i;
+                        //Set selected index and run moveFn if the item exists
+                        if(i >= 0 && i < this.data.getLength()) {
+                                this.selected_index = i;
+                                this.fireEvent('pointMouseOver', this.data.getObjects()[this.selected_index]);
+                        }
+                        return vis;
+                }.bind(this);
+                vis.add(pv.Bar)
+                        .fillStyle("rgba(0,0,0,.001)")
+                        .event("mouseout", outVisFn)
+                        .event("mousemove", moveVisFn);
+        }
+        
+});
+
+

+ 90 - 0
desktop/core/static/js/Source/CCS/HueChart.Circle.js

@@ -0,0 +1,90 @@
+/*
+---
+
+script: HueChart.Circle.js
+
+description: Defines HueChart.Circle, a base class for circular charts, which builds on HueChart.
+
+license: MIT-style license
+
+authors:
+- Marcus McLaughlin
+
+requires:
+- ccs-shared/HueChart
+
+provides: [HueChart.Circle]
+
+...
+*/
+
+
+HueChart.Circle = new Class({
+        
+        Extends: HueChart,
+
+        options: {
+                radius: null// the radius of the chart, (will default to width/2)
+                //graphField: '' // the field in the data object that should be graphed,
+                /*
+                onWedgeOver: function to be executed when the mouse is moved over a wedge of the chart, 
+                onWedgeOut: function to be executed when the mouse is moved off of a wedge of the chart,
+                onWedgeClick: function to be executed when a wedge of the chart is clicked
+                */
+        }, 
+
+        initialize: function(element, options) {
+                this.parent(element, options);
+                var requiredOptions = ["graphField"];
+                requiredOptions.each(function(opt) {
+                        if (!$defined(this.options[opt])) console.error("The required option " + opt + " is missing from a HueChart.Box instantiation.");
+                }.bind(this));
+                this.selected_index = -1;
+                this.radius = this.options.radius || this.width/2;
+                this.addEvent('setupChart', function(vis) {
+                        this.addGraph(vis);
+                });
+        },
+
+        addGraph: function(vis) {
+                //Hm...should this happen here or somewhere else ?
+                var valueSum = this.data.getSeriesSum(this.options.graphField);
+                //Put selected index, color array, and hue chart in scope
+                var get_selected_index = this.getSelectedIndex.bind(this);
+                var colorArray = this.options.colorArray;
+                var hueChart = this;
+                vis.add(pv.Wedge)
+                        .data(this.data.getObjects())
+                        .bottom(this.radius)
+                        .left(this.radius)
+                        .outerRadius(this.radius)
+                        .angle(function(d) { 
+                                return (d[this.options.graphField]/valueSum) * 2 * Math.PI; 
+                        }.bind(this))
+                        .fillStyle(function() {
+                                return this.index == get_selected_index() ? '#fff' : colorArray[this.index % colorArray.length];  
+                        }).event("mouseover", function() {
+                                hueChart.fireEvent('wedgeOver', this.index);
+                        }).event("mouseout", function() {
+                                hueChart.fireEvent('wedgeOut');
+                        }).event("click", function(d) {
+                                hueChart.fireEvent('wedgeClick', d);
+                        });                
+        },
+
+        highlightWedge: function(sliceIndex) {
+                this.setSelectedIndex(sliceIndex);
+                this.render();
+        },
+
+        unHighlightWedges: function() {
+                this.setSelectedIndex(-1);
+                this.render();
+        },
+
+        resize: function(width, height) {
+                this.radius = width/2;
+                this.parent(width, height);
+        }
+
+});

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

@@ -0,0 +1,60 @@
+/*
+---
+
+script: HueChart.Line.js
+
+description: Defines HueChart.Line, builds on HueChart.Box to produce line charts.
+
+license: MIT-style license
+
+authors:
+ - Marcus McLaughlin
+
+requires:
+ - ccs-shared/HueChart.Box
+
+provides: [HueChart.Line]
+
+...
+*/
+
+HueChart.Line = new Class({
+
+        Extends: HueChart.Box,
+
+        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.data.getObjects())
+                                //Closures used because the function argument isn't executed until the render phase.
+                                //Color the line, based on a color in the colarArray.
+                                .strokeStyle(function(itemIndex) {
+                                        return function() {
+                                                return this.options.colorArray[itemIndex % this.options.colorArray.length];
+                                        }.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.xField]);
+                                }.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(3);
+                }
+        }
+});
+

+ 310 - 0
desktop/core/static/js/Source/CCS/HueChart.js

@@ -0,0 +1,310 @@
+/*
+---
+
+script: HueChart.js
+
+description: Defines HueChart; a wrapper for Protovis, which produces charts of various sorts.
+
+license: MIT-style license
+
+authors:
+- Marcus McLaughlin
+
+requires:
+- protovis/Protovis
+- More/Date
+- Core/Events
+- Core/Options
+- ccs-shared/Number.Files
+- More/Element.Shortcuts
+
+provides: [ HueChart ]
+
+...
+*/
+//
+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: the initial width of the chart (required),
+                  height: the initial height of the chart (required),
+                  dataTable: the table containing the chart data,
+                  dataObject: array of data objects,
+                  One of dataTable or dataObject is required
+                  onSetupChart: function that runs when the protovis rendering information is being set up, but before the protovis object is rendered, takes the protovis objects as an argument
+                */
+                  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,
+        },
+
+
+        initialize: function(element, options) {
+                this.setOptions(options);
+                this.element = document.id(element);
+                //Check for required options
+                var requiredOptions = ["width", "height"];
+                requiredOptions.each(function(opt) {
+                        if (!$defined(this.options[opt])) console.error("The required option " + opt + " is missing from a HueChart instantiation.");
+                }.bind(this));
+                if (!(this.options.dataTable || this.options.dataObject)) {
+                        console.error("There is a HueChart instantiation which has no data source.");
+                }
+                //Width and height will potentially change often, make them instance variables.
+                this.width = this.options.width;
+                this.height = this.options.height;
+                
+                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);
+                }
+        },
+
+        //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;
+        } 
+});
+
+//Wrapper for data object to be charted.
+//Adds various functions on the data.
+HueChart.Data = new Class({
+        
+        initialize: function(dataArray) {
+                this.dataObjects = dataArray;
+        },
+        
+        //Function to sort by dates and convert date strings into date objects.
+        //Also creates seconds_from_first, a useful integer value for graphing.
+        prepareDates: function(dateProperty) {
+                //Convert date string to date object.
+                this.dataObjects.each(function(d) {
+                        d[dateProperty] = new Date().parse(d[dateProperty]);
+                });
+                //Sort data by date property.
+                this.sortByProperty(dateProperty);
+                //Store first date for comparison.
+                var firstDate = this.dataObjects[0][dateProperty];
+                //Create seconds from first, for comparison sake.
+                this.dataObjects.each(function(d) {
+                        d.seconds_from_first = firstDate.diff(d[dateProperty], 'second');
+                });
+        },
+        
+        //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;
+        },
+
+        getLength: function() {
+                return this.dataObjects.length;
+        },
+        
+        //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.
+                this array should contain no more than num_ticks values.
+                the increment in which the values should be shown is timespan
+        */
+        //Currently requires the dates in the data object to be exact days.
+
+        createTickArray: function(numTicks, timespan) {
+                //Get the largest number of seconds.
+                var mostSeconds = this.dataObjects.getLast().seconds_from_first;
+                var secondsInSpan;
+                //Choose the timespan -- currently only day
+                switch (timespan) {
+                        case 'day': 
+                                secondsInSpan = 86400;
+                                break;
+                }
+                var xTicks = [];
+                //If the number of ticks that would be generated for the timespan is less than the number of ticks requested, use the current data array.
+                if (mostSeconds/secondsInSpan <= numTicks) {
+                        xTicks = this.dataObjects;
+                } else {
+                //Generate ticks.
+                        //Calculate the size of the increment between ticks to produce the number of ticks. 
+                        var dateIncrement = mostSeconds/numTicks;
+                        var firstDate = this.dataObjects[0].sample_date;
+                        //Add ticks to the xTicks array.
+                        //Sample date - firstDate cloned and incremented by the increment times the iteration number.
+                        //Seconds_from_first - dateIncrement multiplied by the iteration number.
+                        for (var i = 0; i <= numTicks; i++) {
+                                xTicks.push({
+                                        sample_date: firstDate.clone().increment('second', dateIncrement * i),
+                                        seconds_from_first: dateIncrement * i
+                                });
+                        }
+                }
+                return xTicks;
+        }
+
+});
+
+//Function to build an array of data objects from a table.
+//The headers of the table are used as the property names of the data objects.
+//Table format
+/*
+   Header1 Header2 Header3 Header4
+   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('tr')).each(function(row) {
+                var datum = {};
+                $$(row.getElements('td')).each(function(cell, i) {
+                       datum[headers[i]] = cell.get('text'); 
+                });
+                data.push(datum);        
+        });
+        return data;
+};
+

+ 0 - 0
apps/filebrowser/src/filebrowser/static/js/Source/Native/Number.Files.js → desktop/core/static/js/Source/Native/Number.Files.js


+ 6 - 0
desktop/core/static/js/package.yml

@@ -29,6 +29,11 @@ sources: [
   Source/CCS/CCS.Desktop.Keys.js,
   Source/CCS/CCS.User.js,
   Source/CCS/CCS.Login.js,
+  Source/CCS/HueChart.js,
+  Source/CCS/HueChart.Box.js,
+  Source/CCS/HueChart.Area.js,
+  Source/CCS/HueChart.Line.js,
+  Source/CCS/HueChart.Circle.js,
   Source/Forms/Form.Request.JSON.js,
   Source/Forms/EditInline.js,
   Source/UI/StickyWin.Drawer.js,
@@ -50,6 +55,7 @@ sources: [
   Source/JFrameLinkers/CCS.JFrame.Target.js,
   Source/Native/String.CCS.js,
   Source/Native/Element.Data.js,
+  Source/Native/Number.Files.js,
   Source/StaticThirdParty/DynamicTextarea.js,
   Source/UI/ART.SideBySideSelect.js,
   Source/BehaviorFilters/Behavior.Autocomplete.js,