<?xml version="1.0" encoding="UTF-8"?>
<!-- plot_dataset.xml -->
<!-- Author: Ian Smith (imsmith@uw.edu) -->
<Module>
<ModulePrefs title="Plot">
    <Require feature="locked-domain" />  
    <Require feature="dynamic-height"/>
    <Require feature="opensocial-0.8"/> 
    <Require feature="minimessage"/>
    <Require feature="settitle"/>

</ModulePrefs>
<Content type="html"><![CDATA[

<!-- chart dependencies -->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" type="text/javascript"></script>
<script src="js/highcharts_old/highcharts.src.js" type="text/javascript"></script>

<link href="css/gadget.css" rel="stylesheet" type="text/css">

<div id="divSelectDataset" style="width: 100%;">
    
    <table><tbody>
        <tr>
            <td> Dataset: <select id="selDatasetName" onchange="pl.populateXYSelect(this.options[this.selectedIndex].value);">
                          </select>
            </td>
                <td>    
                X Axis: <input id="butXInvert" type="button" title="Invert X Axis" value=">" onclick="pl.invertX();"></input> <select id="selXAxis"></select>
            </td>
            <td>
                Y Axis: <input id="butYInvert" type="button" title="Invert Y Axis" value="^" onclick="pl.invertY();"></input> <select id="selYAxis"></select>
            </td>

        </tr>
    </tbody></table>

<div id="divSelections" style="width: 100%;">
    <table> 
    <TBODY>
        <TR>
            <td>
            </td>
        </TR>
        <TR>
            <td>
               <input id="btDoPlot" onclick="pl.doPlot();" type="button" value="Plot"/>
            </td> 
            <td>
               <img id="imgSpinner" src="images/spinnerGrey.gif" style="display:none" />
            </td>
            <td>
                <div id="divStatusText"></div> 
            </td>
        </TR> 
<!-- TODO: to toggle the display of unselected points
        <TR>
            <td>
                <input type='checkbox' id='ckViewUnselected' onclick="pl.toggleViewUnselected();"/>
                View unselected
            </td>

        </TR>   
-->
    </TBODY>
    </table>
</div>
<div id="divPlotToolbar">
    <form name="formSelectZoom">
        <div id="divSelectZoom">
            Selection Color: <select id="selSelectionColor">
            </select>

            <input type="radio" id="radSelectZoom" name="radSelectZoom" value="select" checked="checked" /><label for="radSelect">Select</label>
            <input type="radio" id="radSelectZoom" name="radSelectZoom" value="zoom" /><label for="radZoom">Zoom</label>
            
            &nbsp;&nbsp; Info: <input type='checkbox' id='ckVerboseInfo'  onclick="pl.redrawChart();" value="0"/>

        </div>
    </form>
</div>

<div id="divChart1">
</div>

<script src="js/DataInterface.js" type="text/javascript"></script>
<script src="js/astroObjectLib.js" type="text/javascript"></script>
<script src="js/dataset.js" type="text/javascript"></script>
<script type="text/javascript">
    

// Plot (pl) object is global
var pl = null,
    DEBUG = false;

function debugLog(msg){if(DEBUG){UW.astro.debugLog(GADGET_NAME + ": " + msg);}}

// GLOBAL CONSTANTS
var GADGET_NAME = "Plot_Dataset" + "_" + Math.floor(Math.random()*100000).toString();
var chart1; // globally available


// create new Plotter object
function init(){
    gadgets.window.setTitle("Plot");
    setHtmlStyles();
    pl = new Plotter();
    // add colors to the select box (pass the select Name and color array)
    pl.setSelectOptions("selSelectionColor", ["red", "green", "blue", "yellow", "unselect"]);
    gadgets.window.adjustHeight();
}

function setHtmlStyles() {
    $(document).ready(function(){
        $('a#button').click(function(){
            $(this).toggleClass("down");
        });
    });
}

function Plotter(){
    var my = new Object();
    // CONSTANTS
    var PLOT_HEIGHT = 400,
        PLOT_WIDTH = 300;
    
        // CHART CONSTANTS
    var DEFAULT_MARKER_COLOR = 'rgba(100, 100, 100, .7)'; //  old red: 'rgba(223, 83, 83, .5)';
        SELECTED_MARKER_COLOR = 'rgba(210,30,100,.9)'// old yellow: 'rgba(210,210,0,.5)',
        DEFAULT_MARKER_SHAPE = 'circle',
        SERIES_NAME = 'series1',
        MARKER_COLOR = { red: 'rgba(255, 0, 0, .7)',
                         green: 'rgba(0, 255, 0, .7)',
                         blue: 'rgba(0, 0, 255, .7)',
                         yellow: 'rgba(220, 220, 0, .7)',
                         unselect: DEFAULT_MARKER_COLOR
                        }
    
    // PRIVATE
    var _namespaceList = [ UW.astro.DEFAULT_NAMESPACE ],      // list of our current Namespaces. Just init with the default
        _objList = [],
        _lastSelectedObjs = [],
        _currentDatasetName = "",
        // contains pointers to HTML fields on page so we can avoid many document.getElementById() calls
        _myFields = { },
        _axisInverted = { x: false,
                          y: false
                        };
    
    
    // Private Helper Variables
    var di = new UW.astro.DataInterface(GADGET_NAME); // make dataInterface object, init with default

    // INIT
    var triggerArray = [ ["Dataset_List", updateDataset_List] ];

    di.registerMe( { namespaceList : _namespaceList,
                    variableList : [ "Viewport_Center_Coordinate", 
                                     "Active_Point_List", 
                                     "Viewport_Point_List", 
                                     "Dataset_List" ],
                    triggerList : triggerArray,
                } );
 
    
    // PUBLIC METHODS  
    my.invertX = function() {
        var anchor = document.getElementById('butXInvert');
        if (anchor.value === ">") {
            anchor.value = "<";
        } else {
            anchor.value = ">";
        }
        _axisInverted.x = !_axisInverted.x;
        if (chart1 !== null && chart1 !== undefined) {
            this.doPlot();
        }
    }

    my.invertY = function() {
        var anchor = document.getElementById('butYInvert');
        if (anchor.value === "^") {
            anchor.value = "v";
        } else {
            anchor.value = "^";
        }
        _axisInverted.y = !_axisInverted.y;
        if (chart1 !== null && chart1 !== undefined) {
            this.doPlot();
        }
    }


    // When the list of public datasets change, rewrite the list of available datasets to plot    
    function updateDataset_List(){
        var list = di.readVariable(UW.astro.DEFAULT_NAMESPACE, "Dataset_List").slice(0);  // copy array
        list.unshift("");
        setSelectOptions("selDatasetName", list);
    }
    
    // Populates the X/Y axis dropdowns with values from the chosen datasets column names
    // If no dataset is chosen, then it blanks them
    my.populateXYSelect = function(datasetName) {
        // TODO get dataset names
        var columnNames = [""];
        if (datasetName !== "") {
            var ds = di.readVariable(UW.astro.DEFAULT_NAMESPACE, datasetName);
            columnNames = ds.getNonDBColumns();
        }
        setSelectOptions("selXAxis", columnNames);
        setSelectOptions("selYAxis", columnNames);
    }
    
    // GETS for the HTML controls on the page       
    function getSelectedDataset() {
        return getSelectedOption("selDatasetName");
    }
    function getSelectedXAxis() {
        return getSelectedOption("selXAxis");
    }
    function getSelectedYAxis() {
        return getSelectedOption("selYAxis");
    }
    
    my.doPlot = function() {
        this._currentDatasetName = getSelectedDataset();
        var datasetName = this._currentDatasetName;
        var xAxisName = getSelectedXAxis();
        var yAxisName = getSelectedYAxis();
    
        //TODO: confirm dataset actually exists with this name
        if (datasetName ===  "" || xAxisName === "" || yAxisName === "") {
            alert("Choose a dataset, x axis, and y axis");
            return;
        }
        
        var ds = di.readVariable(UW.astro.DEFAULT_NAMESPACE, datasetName);
        
        plotDataset({ dataset:ds,
                      xAxis: getSelectedXAxis(),
                      yAxis: getSelectedYAxis()
                    });
        
        // register trigger to this dataset variable so updates happen when it changes
        di.registerVariable(datasetName, null);
        di.registerTrigger(UW.astro.DEFAULT_NAMESPACE, datasetName, updateSelectedPoints);
        // TODO: need to unregister the previous dataset!
        
    }
    
   
    function setSelectedByMinMax(seriesId, xMin, xMax, yMin, yMax) {
        debugLog('x: ' + xMin + ', ' + xMax + '\ny: ' + yMin + ', ' + yMax);

        // 'select' all the points in the chart to change the way they look
        var data = chart1.get(seriesId).data;
        var ds = di.readVariable(UW.astro.DEFAULT_NAMESPACE, getSelectedDataset());
        var selectedIds = [];
        var selectedMarkerColor = getSelectionMarkerColor();
        for (var i=0, len=data.length; i<len; i++){
            var point = data[i];
            if (point.x <= xMax && point.x >= xMin && point.y <= yMax && point.y >= yMin) {
                // point.id looks like: p0 
                // so we strip the first letter to get the db_id
                selectedIds.push(getPointId(point));
                
                colorChartPoint(point, selectedMarkerColor);
            }
        }
        chart1.redraw();
        var color = (selectedMarkerColor === "unselect") ? "" : selectedMarkerColor;
        // update the dataset with the selected points
        // set dataset points colors in bulk
        ds.setIndexesColor(selectedIds, color);
        di.writeVariable(UW.astro.DEFAULT_NAMESPACE, getSelectedDataset(), ds);
    }
    
    // Checks the public dataset to update the selection status of all the points in the chart
    // TODO: set the color according to the ds color
    function updateSelectedPoints() {
        debugLog("updateSelectedPoints()");
        
        chart1.showLoading();
        // select or unselect all the points in the chart to change the way they look
        var ds = di.readVariable(UW.astro.DEFAULT_NAMESPACE, getSelectedDataset());
        if (ds === undefined || ds === null) {
            return;
        }
        var colorIndexArray = ds.getColoredIndexArray();
        
        // color selected points
        for (var i=0, len=colorIndexArray.length; i<len; i++){
            var point = chart1.get("p" + colorIndexArray[i][0]);  // id is [0]
            if (point !== null) {
                colorChartPoint(point, colorIndexArray[i][1] );  // color is [1]
            }
        }

        chart1.redraw();
        chart1.hideLoading();
    }

    // Colors a selected point on chart by the color name
    function colorChartPoint(point, colorName) {
        if (colorName === "") {
            colorName = "unselect";
        }
        point.update({color: MARKER_COLOR[colorName], marker: {fillColor: MARKER_COLOR[colorName]}}, false);
    }
    
    // colors chart point and writes it to dataset
    function setPointColor(point, colorName) {
        debugLog("setPointColor(" + colorName + ")");

        if (colorName === "unselect") {
            colorName = "";
        }
        colorChartPoint(point, colorName);
        // write to dataset
        setTimeout( function() {
                    var dsName = getCurrentDatasetName();
                    var ds = di.readVariable(UW.astro.DEFAULT_NAMESPACE, dsName);
                    ds.setIndexesColor( [ getPointId(point) ], colorName);
                    di.writeVariable(UW.astro.DEFAULT_NAMESPACE, dsName, ds);
                }, 1 );
            
    }
    
    // Zooms the viewport to point by writing CelestialCoordinate to public variable
    function zoomToPoint(point) {
        // get the cc of the point
        // write cc to public variable
        var cc = getPointCc(point);
        if (cc !== null && cc !== undefined) {
            di.writeVariable(UW.astro.DEFAULT_NAMESPACE, "Viewport_Center_Coordinate", cc); 
        }
    }
    function getPointColor(point) {
        var ds = di.readVariable(UW.astro.DEFAULT_NAMESPACE, getCurrentDatasetName());
        return ds.getColorByIndex(getPointId(point));
    }
    
    function getPointId(point) {
        return point.id.substr(1);
    }
    function getPointCc(point) {
        var ds = di.readVariable(UW.astro.DEFAULT_NAMESPACE, getCurrentDatasetName());
        return ds.getCelestialCoordByIndex(getPointId(point));
    }
    function plotDataset(args) {
        // TODO: verify the necessary args were passed
        var xCol = args.xAxis;
        var yCol = args.yAxis;
        var ds = args.dataset;
        
        var graphData = [];
        var dsData = ds.toJSON();
        for (i=0,len=dsData.length; i<len; i++) {
            var xVal = parseFloat(dsData[i][xCol]);
            var yVal = parseFloat(dsData[i][yCol]);
            // each point has unique id so a pointer to point can be had by chart.get(id)
            var id = "p" + dsData[i]["db_id"];
            if (xVal !== NaN && yVal !== NaN){
                graphData.push( {
                        id: id,
                        x: xVal, 
                        y: yVal,
                        color: DEFAULT_MARKER_COLOR,
                        marker: {
                    //      symbol: DEFAULT_MARKER_SHAPE,
                    //      radius: 2,
                            fillColor: DEFAULT_MARKER_COLOR
                        }
                    });
                }
    
        }
        if (chart1 !== null && chart1 !== undefined) {
            chart1.destroy();
        }
        displayPlot({graphData:graphData, title:ds.getName(), xAxis:args.xAxis, yAxis:args.yAxis, seriesName:args.yAxis + " vs " + args.xAxis });
    }


    function displayPlot(args) {
        $(document).ready(function() {
            chart1 = new Highcharts.Chart({
                chart: {
                    renderTo: 'divChart1',
                    defaultSeriesType: 'scatter',
                    zoomType: 'xy',
                    events : {
                        selection: function(event) {
                            // check if the zoom or select radio is checked
                            // also, only select if there is an xAxis property in event (allows reset zoom)
                            if (!isRadioZoom() && ("xAxis" in event)) {
                                event.preventDefault();
                                setSelectedByMinMax(SERIES_NAME, event.xAxis[0].min, event.xAxis[0].max, event.yAxis[0].min, event.yAxis[0].max);
                            }
                        }
                    }
                },
                credits: {
                    enabled: false
                },
                title: {
                    text: args.title
                },
                loading: {
                    showDuration: 0,
                    hideDuration: 0
                },
                xAxis: {
                    title: {
                         enabled: false,
                         text: args.xAxis
                    },
                    reversed: _axisInverted.x,
                    startOnTick: true,
                    endOnTick: true,
                    showLastLabel: true
                },
                yAxis: {
                    title: {
                        text: args.yAxis
                    },
                    reversed: _axisInverted.y
                },
                tooltip: {
                    formatter: function() {
                        // EVALUATED ON CHART DRAW, NOT ON MOUSEOVER
                        if (isVerboseInfo()) {
                            return getVerboseInfo(this.point.id);
                        } else {
                            return args.yAxis + ': ' + this.y + '<br/>' + args.xAxis + ': ' + this.x;
                        }
                    }
                },
                plotOptions: {
                    scatter: {
                        animation: false,
                        color: DEFAULT_MARKER_COLOR, 
                        //cursor: 'pointer',
                        id: SERIES_NAME
                        
                    }
                },
                series: [{
                    name: args.seriesName,
                    data: args.graphData,
                    point: {
                        events: {
                            click: function(event) {
                                if (!(event.ctrlKey || event.metaKey)) { // check windows ctrl and mac meta
                                    // color point
                                    var selectedMarkerColor = getSelectionMarkerColor();
                                    if (getPointColor(this) !== selectedMarkerColor) {
                                        setPointColor(this, selectedMarkerColor);
                                    } else {
                                        setPointColor(this, "");
                                    }
                                    chart1.redraw();
                                } else { // zoom to point
                                    zoomToPoint(this); 
                                }
                            }
                        }
                    }
                }]
              });
        }); 
        getField('divChart1').style.height = PLOT_HEIGHT + "px";

        // colors already selected points
        updateSelectedPoints();
    
        showVisualization(true);
    }

    // TODO: This should remove the unselected points from the plot 
    toggleViewUnselected = function() {

    }
    // HELPER
    function getVerboseInfo(pointId) {
        if (pointId === "") {
            return "no info";
        }
        var info = [];
        ds = di.readVariable(UW.astro.DEFAULT_NAMESPACE, getCurrentDatasetName());
        rec = ds.getRecordByIndex(pointId.substr(1));   // substr chops the 'p' off the point id
        for (var prop in rec) {
            if (!(rec[prop] instanceof Function)) {
                info.push(prop + ": " + rec[prop] + "<br/>");
            }
        }
        return info.join("");
    }
        
    // returns the color name in the selection color dropdown
    function getSelectionMarkerColor() {
        return colorText = getSelectedOption("selSelectionColor");
    }
    // returns the value in the selection color dropdown as an 'rgba(r,g,b,a)' string for chart
    function getSelectionMarkerRgba() {
        return MARKER_COLOR[getSelectionMarkerColor()];
    }
    
    function isViewUnselected(view) {
        return getField('ckViewUnselected').checked;
    }
    
    function showVisualization(show) {
        if (show) {
            getField('divChart1').style.display = "block";
        }
        else {
            getField('divChart1').style.display = "hidden";
        }
        gadgets.window.adjustHeight();
    }

    function showSpinner(show, text){
        // set text status
        getField('divStatusText').innerHTML = text;
        if (show){
            getField('imgSpinner').style.visibility = 'block';
        }
        else{
            getField('imgSpinner').style.visibility = 'hidden';
        }
    }
    
    function xmlStringToDoc(text){
        if (window.DOMParser){
            parser=new DOMParser();
            xmlDoc=parser.parseFromString(text,"text/xml");
        }
        else // Internet Explorer
        {
            xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
            xmlDoc.async="false";
            xmlDoc.loadXML(text); 
        } 
        // TODO: test to verify its created
        return xmlDoc;
    }
    
    function getRadioValue(radio) {
        if (!radio) {
            return "";
        }
        for (var i=0, len=radio.length; i<len; i++) {
            if (radio[i].checked) {
                return radio[i].value;
            }
        }
        return "";
    }

    function isRadioZoom() {
        return (getRadioValue(document.formSelectZoom.radSelectZoom) === "zoom");
    }
    
    function isVerboseInfo() {
        return getField("ckVerboseInfo").checked;
    }
    
    // accepts name of select box
    // returns the value in the select box, or ""
    function getSelectedOption(select) {
        var selName = getField(select);
        if (selName.length > 0){
            return selName.options[selName.selectedIndex].value;
        } else {
            return "";
        }
    }
 
    my.setSelectOptions = function(sel, array) {
        setSelectOptions(sel, array);
    }
    
    // Accepts the name of a select object, and an array of strings
    // replaces the options with the array of strings
    function setSelectOptions(selectName, options) {
        var select = getField(selectName);
        if (select === null) {
            return;
        }
        // save old selected option so maybe it can be reselected
        var oldOpt = getSelectedOption(selectName);
        // clear options
        for (var i=select.length-1; i>=0; i--) {
            select.remove(i);
        }
        // add options
        for (var i=0, len=options.length; i<len; i++) {
            // if oldOption is in the list, then select it
            select.add(new Option(options[i], options[i], false, (oldOpt === options[i])), null);
        }
    
    }
    
    function getCurrentDatasetName() {
        // TODO: get it from this._currentDatasetName
        return getSelectedDataset();
    }
        
    
    function getField(field) {
        if (!(field in _myFields) || (_myFields[field] === null)) {
            _myFields[field] = document.getElementById(field);
        }
        return _myFields[field];
    }

    my.redrawChart = function() {
        if (chart1 !== null && chart1 !== undefined) {
            chart1.redraw();
        }
    }
    
    return my;
}


    gadgets.util.registerOnLoadHandler(init); 


</script>
  
  ]]>
 </Content>

 </Module>
