<commit_before>Parse.initialize("7YyZO6oZNVongKR6pluyULEkMEmvLVRtVZvd6OXn", "JTOrsTnWqTBhxqxFMEvPdOUzDd9Ro01JqpiAF003");
window.fbAsyncInit = function() {
      Parse.FacebookUtils.init({ // this line replaces FB.init({
            appId      : '971388552927772', // Facebook App ID
            status     : true,  // check Facebook Login status
            cookie     : true,  // enable cookies to allow Parse to access the session
            xfbml      : true,  // initialize Facebook social plugins on the page
            version    : 'v2.5' // point to the latest Facebook Graph API version
      });

  // Run code after the Facebook SDK is loaded
  FB.getLoginStatus(function(response) {
    if (response.status === 'connected') {
            getUserName();
            document.getElementById("lgn-btn").style.display="none";
            document.getElementById("lgot-btn").style.display="block";
    }
    else if (response.status === 'not_authorized') {
            getUserName();
            document.getElementById("lgn-btn").style.display="none";
            document.getElementById("lgot-btn").style.display="block";
    }
});
};

function checkLoginState() {
      if (typeof FB !== 'undefined') {
            Parse.FacebookUtils.logIn(null, {
                  success: function(user) {
                        if (!user.existed()) {
                              //alert("User signed up and logged in through Facebook!");
                              getUserName();
                              document.getElementById("lgn-btn").style.display="none";
                              document.getElementById("lgot-btn").style.display="block";
                        } else {
                              //alert("User logged in through Facebook!");
                              getUserName();
                              document.getElementById("lgn-btn").style.display="none";
                              document.getElementById("lgot-btn").style.display="block";
                        }
                  },
                  error: function(user, error) {
                        alert("Please login to access the Locaholic services!!");
                  }
            });
      }
      else{
            alert("Your network does not support FaceBook Login!!")
      }
}
function getUserName() {
    FB.api('/me', function(response) {
      document.getElementById('usrname').innerHTML = response.name;
    });
  }
function lgOut(){
      //alert(Parse.User.current().id);
      Parse.User.logOut();
      FB.logout();
      window.location = window.location.hostname;
}

(function(d, s, id){
      var js, fjs = d.getElementsByTagName(s)[0];
      if (d.getElementById(id)) {return;}
      js = d.createElement(s); js.id = id;
      js.src = "//connect.facebook.net/en_US/sdk.js";
      fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
<commit_msg>Update fb.js<commit_after>Parse.initialize("7YyZO6oZNVongKR6pluyULEkMEmvLVRtVZvd6OXn", "JTOrsTnWqTBhxqxFMEvPdOUzDd9Ro01JqpiAF003");
window.fbAsyncInit = function() {
      Parse.FacebookUtils.init({ // this line replaces FB.init({
            appId      : '971388552927772', // Facebook App ID
            status     : true,  // check Facebook Login status
            cookie     : true,  // enable cookies to allow Parse to access the session
            xfbml      : true,  // initialize Facebook social plugins on the page
            version    : 'v2.5' // point to the latest Facebook Graph API version
      });

  // Run code after the Facebook SDK is loaded
  FB.getLoginStatus(function(response) {
    if (response.status === 'connected') {
            getUserName();
            document.getElementById("lgn-btn").style.display="none";
            document.getElementById("lgot-btn").style.display="block";
    }
    else if (response.status === 'not_authorized') {
            getUserName();
            document.getElementById("lgn-btn").style.display="none";
            document.getElementById("lgot-btn").style.display="block";
    }
});
};

function checkLoginState() {
      if (typeof FB !== 'undefined') {
            Parse.FacebookUtils.logIn(null, {
                  success: function(user) {
                        if (!user.existed()) {
                              //alert("User signed up and logged in through Facebook!");
                              getUserName();
                              document.getElementById("lgn-btn").style.display="none";
                              document.getElementById("lgot-btn").style.display="block";
                        } else {
                              //alert("User logged in through Facebook!");
                              getUserName();
                              document.getElementById("lgn-btn").style.display="none";
                              document.getElementById("lgot-btn").style.display="block";
                        }
                  },
                  error: function(user, error) {
                        alert("Please login to access the Locaholic services!!");
                  }
            });
      }
      else{
            alert("Your network does not support FaceBook Login!!")
      }
}
function getUserName() {
    FB.api('/me', function(response) {
      document.getElementById('usrname').innerHTML = response.name;
    });
  }
function lgOut(){
      //alert(Parse.User.current().id);
      Parse.User.logOut();
      FB.logout();
      window.location.href = "http://www.locaholic.co/";
}

(function(d, s, id){
      var js, fjs = d.getElementsByTagName(s)[0];
      if (d.getElementById(id)) {return;}
      js = d.createElement(s); js.id = id;
      js.src = "//connect.facebook.net/en_US/sdk.js";
      fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
<|endoftext|>


<commit_before>/*global VeloxWebView */

; (function (global, factory) {
        if (typeof exports === 'object' && typeof module !== 'undefined') {
        var VeloxScriptLoader = require("velox-scriptloader") ;
        module.exports = factory(VeloxScriptLoader) ;
    } else if (typeof define === 'function' && define.amd) {
        define(['VeloxScriptLoader'], factory);
    } else {
        global.VeloxWebView.registerExtension(factory(global.veloxScriptLoader));
    }
}(this, (function (VeloxScriptLoader) { 'use strict';

    var confirmNoLabel = function(){ return VeloxWebView.tr?VeloxWebView.tr("global.no"):"No"; };
    var confirmYesLabel = function(){ return VeloxWebView.tr?VeloxWebView.tr("global.yes"):"Yes"; };
    var okLabel = function(){ return VeloxWebView.tr?VeloxWebView.tr("global.ok"):"OK"; };

    /**
     * waiter extension definition
     */
    var extension = {} ;
    extension.name = "bootstrap4-dialogs" ;

    extension.extendsProto = {} ;

    function makeDraggable($elModal){
        var $elHeader = $elModal.find(".modal-header") ;
        var draggableData = {};
        $elHeader.on('mousedown', function (event) {
            draggableData.isMouseDown = true;
            var dialogOffset = $elModal.offset();
            draggableData.mouseOffset = {
                top: event.clientY - dialogOffset.top,
                left: event.clientX - dialogOffset.left
            };
        });
        $elModal.on('mouseup mouseleave', function () {
            draggableData.isMouseDown = false;
        });
        window.jQuery('body').on('mousemove', function (event) {
            if (!draggableData.isMouseDown) {
                return;
            }
            $elModal.offset({
                top: event.clientY - draggableData.mouseOffset.top,
                left: event.clientX - draggableData.mouseOffset.left
            });
        });
    }

    /**
     * Display info box
     *
     * @param {string} message the message to display
     * @param {function} [callback] called when user close message
     */
    extension.extendsProto.info = function (message, callback) {
        var modalHtml = '
'+
        '
'+
        '
'+
        '
'+
        '        <button type="button" class="close" data-dismiss="modal" aria-label="Close">'+
        '          ×'+
        '        </button>'+
        '
'+
        '
'+
        message+
        '
'+
        '
'+
        '        <button type="button" class="btn btn-primary" data-dismiss="modal">'+okLabel()+'</button>'+
        '
'+
        '
'+
        '
'+
        '
' ;
        var $modal = window.jQuery(modalHtml);
        $modal.modal() ;
        makeDraggable($modal) ;
        $modal.on('hidden.bs.modal', function () {
            callback() ;
        });
    } ;

    /**
     * Display error box
     *
     * @param {string} message the message to display
     * @param {function} [callback] called when user close message
     */
    extension.extendsProto.error = function (message, callback) {
        var modalHtml = '
'+
        '
'+
        '
'+
        '
'+
        '        <button type="button" class="close" data-dismiss="modal" aria-label="Close">'+
        '          ×'+
        '        </button>'+
        '
'+
        '
'+
        message+
        '
'+
        '
'+
        '        <button type="button" class="btn btn-primary" data-dismiss="modal">'+okLabel()+'</button>'+
        '
'+
        '
'+
        '
'+
        '
' ;
        var $modal = window.jQuery(modalHtml);
        $modal.modal() ;
        makeDraggable($modal) ;
        $modal.on('hidden.bs.modal', function () {
            callback() ;
        });
    } ;

    /**
     * @typedef VeloxPopupOptions
     * @type {object}
     * @property {boolean} [modal] indicates if modal (defaut false)
     * @property {string} [title] Title (default '')
     * @property {number} [width] width (pixels) (default 500)
     * @property {number} [height] height (pixels) (default 300)
     * @property {boolean} [closeWithEsc] close when user press ESC (default true)
     */

    /**
     * Open the view in a popup
     *
     * @param {VeloxPopupOptions} [options] popup options
     * @param {function} callback called when opened
     */
    extension.extendsProto.openInPopup = function (options, callback) {
        if(typeof(options) === "function"){
            callback = options;
            options = null ;
        }
        if(!options){options = {} ;}

        if(!callback){ callback = function(){} ;}

        options.keyboard = options.closeWithEsc||true ;

        var popup = null;
        this.closePopup = function(){
            if(popup){
                popup.modal('hide') ;
            }
        } ;
        this.close = function(){
            VeloxWebView.prototype.close.apply(this) ;
            this.closePopup() ;
        } ;

        var modalHtml = '
'+
        '
'+
        '
'+
        '
'+
        '
'+(options.title||' ')+'
'+
        '        <button type="button" class="close" data-dismiss="modal" aria-label="Close">'+
        '          ×'+
        '        </button>'+
        '
'+
        '
'+
        '
'+
        '
'+
        '
'+
        '
' ;
        var $modal = window.jQuery(modalHtml);

        this.options.container = $modal.find(".modal-body")[0];
        this.open(function(err){
            if(err){ return callback(err) ;}
            $modal.modal(options) ;
            if(options.draggable){
                makeDraggable($modal) ;
            }
            $modal.on('shown.bs.modal', function () {
                callback() ;
            }) ;
        }.bind(this)) ;
    } ;

    /**
     * Open HTML or an element in popup
     *
     * @param {string|HTMLElement} htmlOrElement the HTML or element to display in popup
     * @param {VeloxPopupOptions} [options] popup options
     * @param {function} callback called when opened
     */
    extension.extendsProto.popup = function (htmlOrElement, options, callback) {
        if(typeof(options) === "function"){
            callback = options;
            options = null ;
        }
        if(!options){options = {} ;}

        if(!callback){ callback = function(){} ;}

        options.keyboard = options.closeWithEsc||true;

        var modalHtml = '
'+
        '
'+
        '
'+
        '
'+
        '
'+(options.title||' ')+'
'+
        '        <button type="button" class="close" data-dismiss="modal" aria-label="Close">'+
        '          ×'+
        '        </button>'+
        '
'+
        '
'+
        '
'+
        '
'+
        '
'+
        '
' ;
        var $modal = window.jQuery(modalHtml);

        var contentEl = $modal.find(".modal-body")[0];
        if(typeof(htmlOrElement) === "string"){
            contentEl.innerHTML = htmlOrElement ;
        }else{
            contentEl.appendChild(htmlOrElement) ;
        }
        $modal.modal(options) ;
        if(options.draggable){
            makeDraggable($modal) ;
        }
        $modal.on('shown.bs.modal', function () {
            callback() ;
        }) ;
    } ;

     /**
     * @typedef VeloxDialogConfirmOptions
     * @type {object}
     * @property {boolean} focusToNoButton Focus to the NO button instead of YES button
     * @property {string} yesLabel Yes button label
     * @property {string} noLabel No button label
     */

     /**
     * This callback is called when user answer confirm dialong
     * @callback VeloxDialogConfirmCallback
     * @param {boolean} yes is user answer YES ?
     */

    /**
     * Display confirm box
     *
     * @param {string} message the message to display
     * @param {VeloxDialogConfirmOptions} [options] display options
     * @param {VeloxDialogConfirmCallback} callback called when user answer dialog
     */
    extension.extendsProto.confirm = function (message, options, callback) {
        if(typeof(options) === "function"){
            callback = options;
            options = {} ;
        }
        if(!options){ options = {} ; }

        var labelYes = options.yesLabel||confirmYesLabel() ;
        if(!labelYes){
            labelYes = VeloxWebView.tr?VeloxWebView.tr("global.yes"):"Yes";
        }
        var labelNo = options.noLabel||confirmNoLabel() ;
        if(!labelNo){
            labelNo = VeloxWebView.tr?VeloxWebView.tr("global.no"):"No";
        }

        var modalHtml = '
'+
        '
'+
        '
'+
        '
'+
        '
'+(options.title||' ')+'
'+
        '        <button type="button" class="close" data-dismiss="modal" aria-label="Close">'+
        '          ×'+
        '        </button>'+
        '
'+
        '
'+
        message+
        '
'+
        '
'+
        '        <button type="button" class="btn btn-secondary btn-no">'+labelNo+'</button>'+
        '        <button type="button" class="btn btn-primary btn-yes">'+labelYes+'</button>'+
        '
'+
        '
'+
        '
'+
        '
' ;
        var $modal = window.jQuery(modalHtml);
        $modal.modal() ;
        var $btnYes = $modal.find(".btn-yes");
        var $btnNo = $modal.find(".btn-no");
        makeDraggable($modal) ;
        $modal.on('shown.bs.modal', function () {
            options.focusToNoButton?$btnNo.focus():$btnYes.focus() ;
        });
        $btnYes.on("click", function(){
            $modal.modal("hide");
            callback(true) ;
        });
        $btnNo.on("click", function(){
            $modal.modal("hide");
            callback(false) ;
        });
    } ;

    extension.extendsGlobal = {} ;

    extension.extendsGlobal.dialogs = {} ;

     /**
     * @typedef VeloxDialogGlobalOptions
     * @type {object}
     * @property {string} [confirmYesLabel] the default YES label in confirm (tr("global.yes") if not given and i18n activate, "Yes" if no i18n)
     * @property {string} [confirmNoLabel] the default NO label in confirm (tr("global.no") if not given and i18n activate, "No" if no i18n)
     */

    /**
     * Configure the dialogs
     *
     * options are :
     * {
     *       custombox :
     *        :
     *       customHTMLWaiter :
     *        : use your custom HTML for info dialog
     *        : use your custom HTML for error dialog
     *        :
     *   }
     *
     * @param {object} options - Init option of dialog
     */
    extension.extendsGlobal.dialogs.configure = function(options){
        if(options.confirmYesLabel){ confirmYesLabel = options.confirmYesLabel ; }
        if(options.confirmNoLabel){ confirmNoLabel = options.confirmNoLabel ; }
    } ;

    extension.extendsGlobal.info = extension.extendsProto.info ;
    extension.extendsGlobal.error = extension.extendsProto.error ;
    extension.extendsGlobal.confirm = extension.extendsProto.confirm ;
    extension.extendsGlobal.popup = extension.extendsProto.popup ;

    return extension ;

})));<commit_msg>carriage return handling<commit_after>/*global VeloxWebView */

; (function (global, factory) {
        if (typeof exports === 'object' && typeof module !== 'undefined') {
        var VeloxScriptLoader = require("velox-scriptloader") ;
        module.exports = factory(VeloxScriptLoader) ;
    } else if (typeof define === 'function' && define.amd) {
        define(['VeloxScriptLoader'], factory);
    } else {
        global.VeloxWebView.registerExtension(factory(global.veloxScriptLoader));
    }
}(this, (function (VeloxScriptLoader) { 'use strict';

    var confirmNoLabel = function(){ return VeloxWebView.tr?VeloxWebView.tr("global.no"):"No"; };
    var confirmYesLabel = function(){ return VeloxWebView.tr?VeloxWebView.tr("global.yes"):"Yes"; };
    var okLabel = function(){ return VeloxWebView.tr?VeloxWebView.tr("global.ok"):"OK"; };

    /**
     * waiter extension definition
     */
    var extension = {} ;
    extension.name = "bootstrap4-dialogs" ;

    extension.extendsProto = {} ;

    function makeDraggable($elModal){
        var $elHeader = $elModal.find(".modal-header") ;
        var draggableData = {};
        $elHeader.on('mousedown', function (event) {
            draggableData.isMouseDown = true;
            var dialogOffset = $elModal.offset();
            draggableData.mouseOffset = {
                top: event.clientY - dialogOffset.top,
                left: event.clientX - dialogOffset.left
            };
        });
        $elModal.on('mouseup mouseleave', function () {
            draggableData.isMouseDown = false;
        });
        window.jQuery('body').on('mousemove', function (event) {
            if (!draggableData.isMouseDown) {
                return;
            }
            $elModal.offset({
                top: event.clientY - draggableData.mouseOffset.top,
                left: event.clientX - draggableData.mouseOffset.left
            });
        });
    }

    /**
     * Display info box
     *
     * @param {string} message the message to display
     * @param {function} [callback] called when user close message
     */
    extension.extendsProto.info = function (message, callback) {
        var modalHtml = '
'+
        '
'+
        '
'+
        '
'+
        '        <button type="button" class="close" data-dismiss="modal" aria-label="Close">'+
        '          ×'+
        '        </button>'+
        '
'+
        '
'+
        message.replace(/\n/g, "
")+
        '
'+
        '
'+
        '        <button type="button" class="btn btn-primary" data-dismiss="modal">'+okLabel()+'</button>'+
        '
'+
        '
'+
        '
'+
        '
' ;
        var $modal = window.jQuery(modalHtml);
        $modal.modal() ;
        makeDraggable($modal) ;
        $modal.on('hidden.bs.modal', function () {
            callback() ;
        });
    } ;

    /**
     * Display error box
     *
     * @param {string} message the message to display
     * @param {function} [callback] called when user close message
     */
    extension.extendsProto.error = function (message, callback) {
        var modalHtml = '
'+
        '
'+
        '
'+
        '
'+
        '        <button type="button" class="close" data-dismiss="modal" aria-label="Close">'+
        '          ×'+
        '        </button>'+
        '
'+
        '
'+
        message.replace(/\n/g, "
")+
        '
'+
        '
'+
        '        <button type="button" class="btn btn-primary" data-dismiss="modal">'+okLabel()+'</button>'+
        '
'+
        '
'+
        '
'+
        '
' ;
        var $modal = window.jQuery(modalHtml);
        $modal.modal() ;
        makeDraggable($modal) ;
        if(callback){
            $modal.on('hidden.bs.modal', function () {
                callback() ;
            });
        }
    } ;

    /**
     * @typedef VeloxPopupOptions
     * @type {object}
     * @property {boolean} [modal] indicates if modal (defaut false)
     * @property {string} [title] Title (default '')
     * @property {number} [width] width (pixels) (default 500)
     * @property {number} [height] height (pixels) (default 300)
     * @property {boolean} [closeWithEsc] close when user press ESC (default true)
     */

    /**
     * Open the view in a popup
     *
     * @param {VeloxPopupOptions} [options] popup options
     * @param {function} callback called when opened
     */
    extension.extendsProto.openInPopup = function (options, callback) {
        if(typeof(options) === "function"){
            callback = options;
            options = null ;
        }
        if(!options){options = {} ;}

        if(!callback){ callback = function(){} ;}

        options.keyboard = options.closeWithEsc||true ;

        var popup = null;
        this.closePopup = function(){
            if(popup){
                popup.modal('hide') ;
            }
        } ;
        this.close = function(){
            VeloxWebView.prototype.close.apply(this) ;
            this.closePopup() ;
        } ;

        var modalHtml = '
'+
        '
'+
        '
'+
        '
'+
        '
'+(options.title||' ')+'
'+
        '        <button type="button" class="close" data-dismiss="modal" aria-label="Close">'+
        '          ×'+
        '        </button>'+
        '
'+
        '
'+
        '
'+
        '
'+
        '
'+
        '
' ;
        var $modal = window.jQuery(modalHtml);

        this.options.container = $modal.find(".modal-body")[0];
        this.open(function(err){
            if(err){ return callback(err) ;}
            $modal.modal(options) ;
            if(options.draggable){
                makeDraggable($modal) ;
            }
            $modal.on('shown.bs.modal', function () {
                callback() ;
            }) ;
        }.bind(this)) ;
    } ;

    /**
     * Open HTML or an element in popup
     *
     * @param {string|HTMLElement} htmlOrElement the HTML or element to display in popup
     * @param {VeloxPopupOptions} [options] popup options
     * @param {function} callback called when opened
     */
    extension.extendsProto.popup = function (htmlOrElement, options, callback) {
        if(typeof(options) === "function"){
            callback = options;
            options = null ;
        }
        if(!options){options = {} ;}

        if(!callback){ callback = function(){} ;}

        options.keyboard = options.closeWithEsc||true;

        var modalHtml = '
'+
        '
'+
        '
'+
        '
'+
        '
'+(options.title||' ')+'
'+
        '        <button type="button" class="close" data-dismiss="modal" aria-label="Close">'+
        '          ×'+
        '        </button>'+
        '
'+
        '
'+
        '
'+
        '
'+
        '
'+
        '
' ;
        var $modal = window.jQuery(modalHtml);

        var contentEl = $modal.find(".modal-body")[0];
        if(typeof(htmlOrElement) === "string"){
            contentEl.innerHTML = htmlOrElement ;
        }else{
            contentEl.appendChild(htmlOrElement) ;
        }
        $modal.modal(options) ;
        if(options.draggable){
            makeDraggable($modal) ;
        }
        $modal.on('shown.bs.modal', function () {
            callback() ;
        }) ;
    } ;

     /**
     * @typedef VeloxDialogConfirmOptions
     * @type {object}
     * @property {boolean} focusToNoButton Focus to the NO button instead of YES button
     * @property {string} yesLabel Yes button label
     * @property {string} noLabel No button label
     */

     /**
     * This callback is called when user answer confirm dialong
     * @callback VeloxDialogConfirmCallback
     * @param {boolean} yes is user answer YES ?
     */

    /**
     * Display confirm box
     *
     * @param {string} message the message to display
     * @param {VeloxDialogConfirmOptions} [options] display options
     * @param {VeloxDialogConfirmCallback} callback called when user answer dialog
     */
    extension.extendsProto.confirm = function (message, options, callback) {
        if(typeof(options) === "function"){
            callback = options;
            options = {} ;
        }
        if(!options){ options = {} ; }

        var labelYes = options.yesLabel||confirmYesLabel() ;
        if(!labelYes){
            labelYes = VeloxWebView.tr?VeloxWebView.tr("global.yes"):"Yes";
        }
        var labelNo = options.noLabel||confirmNoLabel() ;
        if(!labelNo){
            labelNo = VeloxWebView.tr?VeloxWebView.tr("global.no"):"No";
        }

        var modalHtml = '
'+
        '
'+
        '
'+
        '
'+
        '
'+(options.title||' ')+'
'+
        '        <button type="button" class="close" data-dismiss="modal" aria-label="Close">'+
        '          ×'+
        '        </button>'+
        '
'+
        '
'+
        message.replace(/\n/g, "
")+
        '
'+
        '
'+
        '        <button type="button" class="btn btn-secondary btn-no">'+labelNo+'</button>'+
        '        <button type="button" class="btn btn-primary btn-yes">'+labelYes+'</button>'+
        '
'+
        '
'+
        '
'+
        '
' ;
        var $modal = window.jQuery(modalHtml);
        $modal.modal() ;
        var $btnYes = $modal.find(".btn-yes");
        var $btnNo = $modal.find(".btn-no");
        makeDraggable($modal) ;
        $modal.on('shown.bs.modal', function () {
            options.focusToNoButton?$btnNo.focus():$btnYes.focus() ;
        });
        $btnYes.on("click", function(){
            $modal.modal("hide");
            callback(true) ;
        });
        $btnNo.on("click", function(){
            $modal.modal("hide");
            callback(false) ;
        });
    } ;

    extension.extendsGlobal = {} ;

    extension.extendsGlobal.dialogs = {} ;

     /**
     * @typedef VeloxDialogGlobalOptions
     * @type {object}
     * @property {string} [confirmYesLabel] the default YES label in confirm (tr("global.yes") if not given and i18n activate, "Yes" if no i18n)
     * @property {string} [confirmNoLabel] the default NO label in confirm (tr("global.no") if not given and i18n activate, "No" if no i18n)
     */

    /**
     * Configure the dialogs
     *
     * options are :
     * {
     *       custombox :
     *        :
     *       customHTMLWaiter :
     *        : use your custom HTML for info dialog
     *        : use your custom HTML for error dialog
     *        :
     *   }
     *
     * @param {object} options - Init option of dialog
     */
    extension.extendsGlobal.dialogs.configure = function(options){
        if(options.confirmYesLabel){ confirmYesLabel = options.confirmYesLabel ; }
        if(options.confirmNoLabel){ confirmNoLabel = options.confirmNoLabel ; }
    } ;

    extension.extendsGlobal.info = extension.extendsProto.info ;
    extension.extendsGlobal.error = extension.extendsProto.error ;
    extension.extendsGlobal.confirm = extension.extendsProto.confirm ;
    extension.extendsGlobal.popup = extension.extendsProto.popup ;

    return extension ;

})));<|endoftext|>


<commit_before>/**
 * Tooltip.js is a lightweight javascript plugin for responsive and
 * easy to use tooltips. Requires jQuery for the moment.
 *
 * @version   1.0
 * @author    Jakob Ploens <jakob@2helden.com>
 * @copyright Jakob Ploens 2016
 * @license   The MIT License
 */

var Tooltip = function(element, settings){

    var self = this;

    /**
     * Define default settings we go for.
     *
     * @param integer Animation's speed – if needed.
     * @param integer Delay between mouse leaving target and removing tooltip
     * @param integer Difference between tooltip and cursor
     *
     * @param string  CSS class for tooltip
     * @param string  CSS class for fixed tooltip
     * @param string  CSS class for left aligned tooltip
     * @param string  CSS class for right aligned tooltip
     * @param string  CSS class for top aligned tooltip
     * @param string  CSS class for bottom aligned tooltip
     * @param string  CSS class for shown tooltip
     *
     * @param string  Tooltip's position. Can be either auto or left/right/top/bottom
     * @param string  Data attribute for the content
     * @param string  Event to trigger the tooltip. If false, tooltip has to be triggered manually by calling tooltip.construct();
     * @param string  Event to trigger the remove of the tooltip.
     *
     * @param boolean If true, adds fixed class, no matter what inFixedPosition() returns
     * @param boolean If true, follows mouse instead of one defined position
     * @param boolean If true, injects html insted of plain text
     */
    this.defaults = {
        speed:       100,
        delay:       0,
        offset:      5,

        class:       'tooltip',
        classFixed:  'tooltip--fixed',
        classLeft:   'tooltip--left',
        classRight:  'tooltip--right',
        classTop:    'tooltip--top',
        classBottom: 'tooltip--bottom',
        classShown:  'tooltip--is-shown',

        position:    'auto',
        dataAttr:    'tooltip',
        trigger:     'mouseenter',
        triggerOff:  'mouseleave',

        fixed:       false,
        follow:      false,
        html:        false,
    };

    /**
     * Get options.
     * Settings is the combination of defaults and custom JS options,
     * Options has additionally the data-attributes included
     */
    this.settings = merge(this.defaults, settings);
    this.options  = this.settings;

    /**
     * Setup local store variables
     */
    this.bound = false;

    /**
     * Save target as object
     */
    this.target  = element;
    this.content = this.target.getAttribute('data-' + this.options.dataAttr);

    /**
     * Add tooltip and attributes
     */
    this.tooltip = document.createElement('div');
    this.tooltip.addClass(this.options.class);
    this.tooltip.setAttribute('role', 'tooltip');

    /**
     * Look for data-attributes at target and append if neccessary.
     */
    for(var key in this.defaults){
        var data = this.target.getAttribute('data-' + key);
        if(data && data != ""){
            if(data === "false"){
                data = false;
            } else if(data === "true"){
                data = true;
            }
            this.options[key] = data;
        }
    }

    /**
     * Init function which builds our tooltip
     *
     * @return prototype
     */
    this.init = function(){
        /**
         * Bind events to tooltip's event trigger
         */
        if(self.options.trigger){
            self.target.addEventListener(self.options.trigger, self.construct);
        }

        /**
         * Return this
         */
        return self;
    };

    /**
     * Construct the tooltip
     */
    this.construct = function(){

        /**
         * If no content is given on this element, do nothing.
         */
        if(!self.content || self.content === ''){
            return;
        }

        /**
         * Add content and append object to body
         */
        if(self.options.html){
            self.tooltip.innerHTML = self.content;
        } else {
            self.tooltip.textContent = self.content;
        }
        document.body.appendChild(self.tooltip);

        /**
         * If target is in a fixed container, apply fixed class to tooltip.
         */
        if(self.inFixedPosition(self.$target)){
            self.tooltip.addClass(self.options.classFixed);
        }

        /**
         * Place tooltip at dedicated position.
         */
        self.position();

        /**
         * If trigger off was not bound yet, bind it and set bound to true.
         */
        if(!self.bound){
            self.target.addEventListener(self.options.triggerOff, function(){
                setTimeout(self.remove, self.options.delay);
            });
            self.bound = true;
        }

        return self;
    };

    /**
     * Calculate tooltip at right position
     *
     * @return object left and top values as object
     */
    this.position = function(){

        /**
         * Get often used settings in easier and shorter variable names.
         */
        var position  = self.options.position;
        var offset    = self.options.offset * 2;
        var direction = 'down';

        /**
         * Get dimensions and position of target and tooltip
         */
        var windowWidth    = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);

        var targetWidth    = self.target.offsetWidth;
        var targetHeight   = self.target.offsetHeight;

        var rect = self.target.getBoundingClientRect();
        var targetPosition = {
            top: rect.top + document.body.scrollTop,
            left: rect.left + document.body.scrollLeft
        };

        var tooltipWidth   = self.tooltip.offsetWidth;
        var tooltipHeight  = self.tooltip.offsetHeight;

        var left = targetPosition.left + (targetWidth / 2) - (tooltipWidth / 2);
        var top  = targetPosition.top - tooltipHeight - offset;

        /**
         * If window is smaller than tooltip, add a new max width to tooltip
         */
        if(windowWidth < (tooltipWidth * 1.5)){
            self.tooltip.style.maxWidth = (windowWidth / 2);
        }

        /**
         * Now we start checking for positions.
         */
        if(position === 'left' || left < 0){
            left = targetPosition.left + (targetWidth / 2) - offset;
            self.tooltip.addClass(self.options.classLeft);
        } else {
            self.tooltip.removeClass(self.options.classLeft);
        }

        if(position === 'right' || (left + tooltipWidth) > windowWidth){
            left = targetPosition.left - tooltipWidth + (targetWidth / 2) + offset;
            self.tooltip.addClass(self.options.classRight);
        } else {
            self.tooltip.removeClass(self.options.classRight);
        }

        if(position === 'bottom' || top < 0){
            direction = 'up';
            top = targetPosition.top + targetHeight + offset;
            self.tooltip.addClass(self.options.classBottom);
        } else {
            self.tooltip.removeClass(self.options.classBottom);
        }

        if(position === 'top'){
            top = targetPosition.top - tooltipHeight - offset;
            self.tooltip.removeClass(self.options.classBottom);
        }

        /**
         * If follow is true, recalculate the left and top values again.
         */
        if(self.options.follow){
            self.target.addEventListener('mousemove', function(event){
                left = event.pageX - (tooltipWidth / 2);
                top  = event.pageY - tooltipHeight - offset;

                self.tooltip.style.top  = parseInt(top) + 'px';
                self.tooltip.style.left = parseInt(left) + 'px';
                self.tooltip.addClass(self.options.classShown);
            });
        } else {
            self.tooltip.style.top  = parseInt(top) + 'px';
            self.tooltip.style.left = parseInt(left) + 'px';
            self.tooltip.addClass(self.options.classShown);
        }
    };

    /**
     * Returns true if one of parents is in fixed position
     *
     * @param  object
     * @return boolean
     */
    this.inFixedPosition = function(element){
        if(self.options.fixed) return true;

        return false;
    };

    /**
     * Remove a named tooltip or all.
     *
     * @param  boolean
     * @return boolean
     */
    this.remove = function(){
        var all = false;

        if(all){
            document.querySelector(self.options.class).removeClass(self.options.classShown);
            setTimeout(function(){
                document.querySelector(self.options.class).parentNode.removeChild(document.querySelector(self.options.class));
            }, self.speed);

        } else {
            self.tooltip.removeClass(self.options.classShown);
            setTimeout(function(){
                self.tooltip.parentNode.removeChild(self.tooltip);
            }, self.speed);
        }

        return;
    };

    /**
     * Destroy plugin and unbind events.
     */
    this.destroy = function(){
        self = null;
        delete self;
        return;
    };

    /**
     * Return init function
     */
    return this.init();
};

/**
 * Class to trigger more than one tooltip without using a loop manually.
 */
var Tooltips = function(selector, options){
    var self      = this;
    this.elements = document.querySelectorAll(selector);
    this.tooltips = [];
    this.options  = options;

    Array.prototype.forEach.call(self.elements, function(item, i){
        self.tooltips.push(new Tooltip(item, self.options));
    });

    return this;
};

/**
 * Short function to merge two objects.
 *
 * @param  object (gets overwritten)
 * @param  object (overwrites)
 * @return object (merged)
 */
function merge(objectA, objectB){
    var result = {};
    for(var attr in objectA){
        result[attr] = objectA[attr];
    }
    for(var attr in objectB){
        result[attr] = objectB[attr];
    }
    return result;
}

/**
 * addClass
 *
 * @param  string class name
 * @return object
 * @since  1.0
 */
Object.prototype.addClass = function(className){
    if(this.classList){
        this.classList.add(className);
    } else {
        this.className += ' ' + className;
    }
    return this;
};

/**
 * removeClass
 *
 * @param  string class name
 * @return object
 * @since  1.0
 */
Object.prototype.removeClass = function(className){
    if (this.classList){
        this.classList.remove(className);
    } else {
        this.className = this.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
    }
};

/**
 * jQuery Plugin
 */
if(window.jQuery){
    (function($){
        'use strict';

        /**
         * Initialize jQuery plugin
         */
        $.fn.tooltip = function(options){
            $(this).each(function(){
                var tooltip = new Tooltip(this, options);
            });
            return;
        };
    })(jQuery);
}<commit_msg>fixed #1<commit_after>/**
 * Tooltip.js is a lightweight javascript plugin for responsive and
 * easy to use tooltips. Requires jQuery for the moment.
 *
 * @version   1.0
 * @author    Jakob Ploens <jakob@2helden.com>
 * @copyright Jakob Ploens 2016
 * @license   The MIT License
 */

var Tooltip = function(element, settings){

    var self = this;

    /**
     * Define default settings we go for.
     *
     * @param integer Animation's speed – if needed.
     * @param integer Delay between mouse leaving target and removing tooltip
     * @param integer Difference between tooltip and cursor
     *
     * @param string  CSS class for tooltip
     * @param string  CSS class for fixed tooltip
     * @param string  CSS class for left aligned tooltip
     * @param string  CSS class for right aligned tooltip
     * @param string  CSS class for top aligned tooltip
     * @param string  CSS class for bottom aligned tooltip
     * @param string  CSS class for shown tooltip
     *
     * @param string  Tooltip's position. Can be either auto or left/right/top/bottom
     * @param string  Data attribute for the content
     * @param string  Event to trigger the tooltip. If false, tooltip has to be triggered manually by calling tooltip.construct();
     * @param string  Event to trigger the remove of the tooltip.
     *
     * @param boolean If true, adds fixed class, no matter what inFixedPosition() returns
     * @param boolean If true, follows mouse instead of one defined position
     * @param boolean If true, injects html insted of plain text
     */
    this.defaults = {
        speed:       100,
        delay:       0,
        offset:      5,

        class:       'tooltip',
        classFixed:  'tooltip--fixed',
        classLeft:   'tooltip--left',
        classRight:  'tooltip--right',
        classTop:    'tooltip--top',
        classBottom: 'tooltip--bottom',
        classShown:  'tooltip--is-shown',

        position:    'auto',
        dataAttr:    'tooltip',
        trigger:     'mouseenter',
        triggerOff:  'mouseleave',

        fixed:       false,
        follow:      false,
        html:        false,
    };

    /**
     * Get options.
     * Settings is the combination of defaults and custom JS options,
     * Options has additionally the data-attributes included
     */
    this.settings = merge(this.defaults, settings);
    this.options  = this.settings;

    /**
     * Setup local store variables
     */
    this.bound = false;

    /**
     * Save target as object
     */
    this.target  = element;
    this.content = this.target.getAttribute('data-' + this.options.dataAttr);

    /**
     * Add tooltip and attributes
     */
    this.tooltip = document.createElement('div');
    this.tooltip.addClass(this.options.class);
    this.tooltip.setAttribute('role', 'tooltip');

    /**
     * Look for data-attributes at target and append if neccessary.
     */
    for(var key in this.defaults){
        var data = this.target.getAttribute('data-' + key);
        if(data && data != ""){
            if(data === "false"){
                data = false;
            } else if(data === "true"){
                data = true;
            }
            this.options[key] = data;
        }
    }

    /**
     * Init function which builds our tooltip
     *
     * @return prototype
     */
    this.init = function(){
        /**
         * Bind events to tooltip's event trigger
         */
        if(self.options.trigger){
            self.target.addEventListener(self.options.trigger, self.construct);
        }

        /**
         * Return this
         */
        return self;
    };

    /**
     * Construct the tooltip
     */
    this.construct = function(){

        /**
         * If no content is given on this element, do nothing.
         */
        if(!self.content || self.content === ''){
            return;
        }

        /**
         * Add content and append object to body
         */
        if(self.options.html){
            self.tooltip.innerHTML = self.content;
        } else {
            self.tooltip.textContent = self.content;
        }
        document.body.appendChild(self.tooltip);

        /**
         * If target is in a fixed container, apply fixed class to tooltip.
         */
        if(self.inFixedPosition(self.$target)){
            self.tooltip.addClass(self.options.classFixed);
        }

        /**
         * Place tooltip at dedicated position.
         */
        self.position();

        /**
         * If trigger off was not bound yet, bind it and set bound to true.
         */
        if(!self.bound){
            self.target.addEventListener(self.options.triggerOff, function(){
                setTimeout(self.remove, self.options.delay);
            });
            self.bound = true;
        }

        return self;
    };

    /**
     * Calculate tooltip at right position
     *
     * @return object left and top values as object
     */
    this.position = function(){

        /**
         * Get often used settings in easier and shorter variable names.
         */
        var position  = self.options.position;
        var offset    = self.options.offset * 2;
        var direction = 'down';

        /**
         * Get dimensions and position of target and tooltip
         */
        var windowWidth    = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);

        var targetWidth    = self.target.offsetWidth;
        var targetHeight   = self.target.offsetHeight;

        var rect = self.target.getBoundingClientRect();
        var targetPosition = {
            top: rect.top + document.body.scrollTop,
            left: rect.left + document.body.scrollLeft
        };

        var tooltipWidth   = self.tooltip.offsetWidth;
        var tooltipHeight  = self.tooltip.offsetHeight;

        var left = targetPosition.left + (targetWidth / 2) - (tooltipWidth / 2);
        var top  = targetPosition.top - tooltipHeight - offset;

        /**
         * If window is smaller than tooltip, add a new max width to tooltip
         */
        if(windowWidth < (tooltipWidth * 1.5)){
            self.tooltip.style.maxWidth = (windowWidth / 2);
        }

        /**
         * Now we start checking for positions.
         */
        if(position === 'left' || left < 0){
            left = targetPosition.left + (targetWidth / 2) - offset;
            self.tooltip.addClass(self.options.classLeft);
        } else {
            self.tooltip.removeClass(self.options.classLeft);
        }

        if(position === 'right' || (left + tooltipWidth) > windowWidth){
            left = targetPosition.left - tooltipWidth + (targetWidth / 2) + offset;
            self.tooltip.addClass(self.options.classRight);
        } else {
            self.tooltip.removeClass(self.options.classRight);
        }

        if(position === 'bottom' || top < 0){
            direction = 'up';
            top = targetPosition.top + targetHeight + offset;
            self.tooltip.addClass(self.options.classBottom);
        } else {
            self.tooltip.removeClass(self.options.classBottom);
        }

        if(position === 'top'){
            top = targetPosition.top - tooltipHeight - offset;
            self.tooltip.removeClass(self.options.classBottom);
        }

        /**
         * If follow is true, recalculate the left and top values again.
         */
        if(self.options.follow){
            self.target.addEventListener('mousemove', function(event){
                left = event.pageX - (tooltipWidth / 2);
                top  = event.pageY - tooltipHeight - offset;

                self.tooltip.style.top  = parseInt(top) + 'px';
                self.tooltip.style.left = parseInt(left) + 'px';
                self.tooltip.addClass(self.options.classShown);
            });
        } else {
            self.tooltip.style.top  = parseInt(top) + 'px';
            self.tooltip.style.left = parseInt(left) + 'px';
            self.tooltip.addClass(self.options.classShown);
        }
    };

    /**
     * Returns true if one of parents is in fixed position
     *
     * @param  object
     * @return boolean
     */
    this.inFixedPosition = function(element){
        if(self.options.fixed) return true;

        return false;
    };

    /**
     * Remove a named tooltip or all.
     *
     * @param  boolean
     * @return boolean
     */
    this.remove = function(){
        var all = false;

        if(all){
            document.querySelector(self.options.class).removeClass(self.options.classShown);
            setTimeout(function(){
                document.querySelector(self.options.class).parentNode.removeChild(document.querySelector(self.options.class));
            }, self.speed);

        } else {
            self.tooltip.removeClass(self.options.classShown);
            setTimeout(function(){
                self.tooltip.parentNode.removeChild(self.tooltip);
            }, self.speed);
        }

        return;
    };

    /**
     * Destroy plugin and unbind events.
     */
    this.destroy = function(){
        self = null;
        delete self;
        return;
    };

    /**
     * Return init function
     */
    return this.init();
};

/**
 * Class to trigger more than one tooltip without using a loop manually.
 */
var Tooltips = function(selector, options){
    var self      = this;
    this.elements = document.querySelectorAll(selector);
    this.tooltips = [];
    this.options  = options;

    Array.prototype.forEach.call(self.elements, function(item, i){
        self.tooltips.push(new Tooltip(item, self.options));
    });

    return this;
};

/**
 * Short function to merge two objects.
 *
 * @param  object (gets overwritten)
 * @param  object (overwrites)
 * @return object (merged)
 */
function merge(objectA, objectB){
    var result = {};
    for(var attr in objectA){
        result[attr] = objectA[attr];
    }
    for(var attr in objectB){
        result[attr] = objectB[attr];
    }
    return result;
}

/**
 * addClass
 *
 * @param  string class name
 * @return object
 * @since  1.0
 */
Object.prototype.addClass = function(className){
    if(this.classList){
        this.classList.add(className);
    } else {
        this.className += ' ' + className;
    }
    return this;
};

/**
 * removeClass
 *
 * @param  string class name
 * @return object
 * @since  1.0
 */
Object.prototype.removeClass = function(className){
    if (this.classList){
        this.classList.remove(className);
    } else {
        this.className = this.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
    }
};

/**
 * jQuery Plugin
 */
if(window.jQuery){
    (function($){
        'use strict';

        /**
         * Initialize jQuery plugin
         */
        $.fn.tooltip = function(options){
            $(this).each(function(){
                var tooltip = new Tooltip(this, options);
            });
            return;
        };
    })(jQuery);
}<|endoftext|>


<commit_before>// ==========================================================================
//
//  Author:   jsandoe
//
//  Copyright (c) 2017 by The Concord Consortium, Inc. All rights reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// ==========================================================================
/*global require:true */
var Rsvg = require('librsvg').Rsvg;
var btoa = btoa || require('btoa');
var fs = require('fs');
var merc = require('mercator-projection');
var console = require('console');
/*global process:true, Buffer:true */
var progName = process.argv[1];

/**
 * print usage and exit
 */
function usage () {
  console.error('usage: node ' + progName + ' codap-geojson-file.codap');
  process.exit(1);
}

/**
 * Amend a CODAP file with geojson to add a thumbnail representation of each
 * geojson element. The thumbnail is a data uri encoding of a png file 200x200 pixels.
 * The thumbnail should be accurately proportioned for a Mercator projection
 * along the mid-line of the figure.
 */

(function () {
  var kGeometryTypes = {
    Point:true,
    MultiPoint:true,
    LineString:true,
    MultiLineString:true,
    Polygon:true,
    MultiPolygon:true,
    GeometryCollection:true
  };

  /**
   * Attempts to read the CODAP file, parse it, and verify that it has the expected
   * structure.
   *
   * @param {string} fn filename
   * @return {Object | null}
   *
   * To determine if the file is a CODAP file, we verify "appName"=="DG" and that
   * there is "appVersion", "appBuild", contexts, and components at the top level.
   * In addition the CODAP files for this purpose are expected to have exactly
   * one data context with two collections,
   * boundaries and index. The collection "boundaries" has one attribute, "boundary"
   * with Geojson content.
   */
  function readAndVerifyCODAPFile(fn) {
    var objString = fs.readFileSync(fn, {encoding: 'utf8'});
    var obj;
    try {
      obj = JSON.parse(objString);
    } catch (ex) {console.error(ex);}
    var hasCODAPFields = obj &&
        obj.appName === 'DG' &&
        obj.appVersion &&
        obj.appBuildNum &&
        obj.contexts &&
        obj.components
    ;
    var context = hasCODAPFields && obj.contexts[0];
    var collection = context && context.collections.find(function(c) { return c.name === 'boundaries';});
    var attr = collection && collection.attrs.find(function(attr) {return attr.name === 'boundary';});
    if (hasCODAPFields && attr) {
      return obj;
    } else {
      console.error('File is not properly formatted CODAP boundary file: ' + fn + ' ' + [!!obj, !!hasCODAPFields, !!context, !!collection, !!attr].join());
    }
  }

  function renderGeoJSONToSVG(geojson) {
    var bBox = {
      xMin: Number.MAX_VALUE,
      yMin: Number.MAX_VALUE,
      xMax: -Number.MAX_VALUE,
      yMax: -Number.MAX_VALUE
    };

    function adjustBBox(pt) {
      bBox.xMin = Math.min(pt.x, bBox.xMin);
      bBox.yMin = Math.min(pt.y, bBox.yMin);
      bBox.xMax = Math.max(pt.x, bBox.xMax);
      bBox.yMax = Math.max(pt.y, bBox.yMax);
    }

    function latLongToXY(coord) {
      return merc.fromLatLngToPoint({lat: coord[1], lng: coord[0]});
    }

    function renderPoint(coord) {
      if (coord) {
        return '<circle r="3" cx="' + coord[0] + '" cy="' + coord[1] + '" />';
      }
    }
    function renderLine(coords) {
      var pathDef = coords.map(function (pt, ix){
        if (ix === 0) {
          return 'M'+ pt.x + ',' + pt.y + ' L';
        } else {
          return pt.x + ' ' + pt.y + ' ';
        }
      }).join();
      return '<path stroke-width="1" stroke="blue" d="' + pathDef + '" />';
    }
    function renderPolygon(coords) {
      var pathDef = coords.map(function (linearRing) {
        return linearRing.map(function (pt, ix){
          if (ix === 0) {
            return 'M'+ pt.x + ',' + pt.y + ' L';
          } else {
            return pt.x + ' ' + pt.y + ' ';
          }
        }).join();
      }).join();
      var svg = '<path stroke-width="0" fill="blue" d="' + pathDef + '"/>';
      return svg;
    }
    var renderers = {
      Point: function (geojson) {
        var coord = latLongToXY(geojson.coordinates);
        adjustBBox(coord);
        return renderPoint(coord);
      },
      MultiPoint: function (geojson) {
        var coords = geojson.coordinates;
        var dots = coords.map(function (coord) {
          var xyCoord = latLongToXY(coord);
          adjustBBox(coord);
          return renderPoint(xyCoord);
        });
        return dots.join();
      },
      LineString: function (geojson) {
        var coords = geojson.coordinates;
        var xys = coords.map(function(coord) {
          var xy = latLongToXY(coord);
          adjustBBox(xy);
          return xy;
        });
        var svg = renderLine(xys);
        return svg;
      },
      MultiLineString: function (geojson) {
        var lines = geojson.coordinates;
        var svg = lines.map(function (line) {
          return renderers.LineString({coordinates: line });
        }).join();
      },
      Polygon: function (geojson) {
        var coords = geojson.coordinates;
        var xys = coords.map(function(lineString) {
          return lineString.map(function(coord) {
            var xy = latLongToXY(coord);
            adjustBBox(xy);
            return xy;
          });
        });
        var svg = renderPolygon(xys);
        return svg;
      },
      MultiPolygon: function(geojson) {
        var polygons = geojson.coordinates;
        var svg = polygons.map(function (polygon) {
          return renderers.Polygon({coordinates: polygon });
        }).join();
        return svg;
      },
      GeometryCollection: function (geojson) {
        var geometries = geojson.geometries;
        geometries.map(function(geometry) {
          var fn = renderers[geometry.type];
          if (!fn) {
            console.log("Unknown type: " + geometry.type);
          }
          return fn(geometry);
        }).join();
      },
      Feature: function (geojson) {
        var geometry = geojson.geometry;
        var fn = renderers[geometry.type];
        if (!fn) {
          console.log("Unknown type: " + geometry.type);
        }
        return fn(geometry);
      },
      FeatureCollection: function (geojson) {
        var features = geojson.features;
        var svg = features.map(function (feature) {
          return renderers.Feature(feature);
        }).join();
      }
    };
    if (!geojson) {
      return;
    }
    var fn = renderers[geojson.type];
    if (!fn) {
      console.log("Unknown type: " + geojson.type);
      return;
    }
    var svg = fn(geojson);
    var imgW = bBox.xMax-bBox.xMin;
    var imgH = bBox.yMax-bBox.yMin;
    var delta = (imgW - imgH) / 2;
    var x,y,w,h;
    if (delta < 0) {
      x = bBox.xMin - delta;
      y = bBox.yMin;
      w = imgW + (delta * 2);
      h = imgH;
    } else {
      x = bBox.xMin;
      y = bBox.yMin - delta;
      w = imgW;
      h = imgH + (delta * 2);
    }
    return '<?xml version="1.0" encoding="UTF-8" ?>' +
        '<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" ' +
        'viewBox="' + [x, y, w, h].join(' ') + '">' + svg + "</svg>";

  }

  function convertSVGtoPNG(svgText) {
    var svg = new Rsvg(svgText);
    var rendering = new Buffer(svg.render({
      format: 'png',
      width: svg.width,
      height: svg.height
    }).data);
    return rendering;
  }

  function formatAsDataURI(png, mime) {
    var out = 'data:' + mime + ';base64,' + btoa(png);
    return out;
  }

  var codapFile = process.argv[2];
  if (!codapFile) { usage(); }
  var codapDoc = readAndVerifyCODAPFile(codapFile);
  if (codapDoc) {
    var collection = codapDoc.contexts[0].collections.find(function(c) { return c.name === 'boundaries';});
    collection.cases.forEach(function (iCase) {
      try {
        var boundary = iCase.values.boundary;
        var isParsed = true;
        if (typeof boundary === 'string' && boundary.startsWith('{')) {
          boundary = JSON.parse(boundary);
          isParsed = false;
        }
        if (!boundary) {
          return;
        }
        // GeoJSON does not require any particular top-level type, but we need
        // to add a property so, we wrap 'naked' geoJSON in a Feature type.
        if (kGeometryTypes[boundary.type]) {
          boundary = {
            type: 'Feature',
            properties: {},
            geometry: boundary
          };
        }
        var svg = renderGeoJSONToSVG(boundary);
        var png = svg && convertSVGtoPNG(svg);
        var dataURI = png && formatAsDataURI(png, 'image/png');
        if (dataURI) {
          if (!boundary.properties) { boundary.properties = {}; }
          boundary.properties.THUMB = dataURI;
          if (!isParsed) {
            boundary = JSON.stringify(boundary);
          }
          iCase.values.boundary = boundary;
        }
      } catch (ex) {
        console.error(ex);
      }
    });
  }
  console.log(JSON.stringify(codapDoc));

}());
<commit_msg>[#148080855] Fix thumbnails for boundaries that cross antimeridian.<commit_after>// ==========================================================================
//
//  Author:   jsandoe
//
//  Copyright (c) 2017 by The Concord Consortium, Inc. All rights reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// ==========================================================================
/*global require:true */
var Rsvg = require('librsvg').Rsvg;
var btoa = btoa || require('btoa');
var fs = require('fs');
var merc = require('mercator-projection');
// var st = require('geojson-bounds');
var console = require('console');
/*global process:true, Buffer:true */
var progName = process.argv[1];

/**
 * print usage and exit
 */
function usage () {
  console.error('usage: node ' + progName + ' codap-geojson-file.codap');
  process.exit(1);
}

/**
 * Amend a CODAP file with geojson to add a thumbnail representation of each
 * geojson element. The thumbnail is a data uri encoding of a png file 200x200 pixels.
 * The thumbnail should be accurately proportioned for a Mercator projection
 * along the mid-line of the figure.
 */

(function () {
  var kGeometryTypes = {
    Point:true,
    MultiPoint:true,
    LineString:true,
    MultiLineString:true,
    Polygon:true,
    MultiPolygon:true,
    GeometryCollection:true
  };

  /**
   * Attempts to read the CODAP file, parse it, and verify that it has the expected
   * structure.
   *
   * @param {string} fn filename
   * @return {Object | null}
   *
   * To determine if the file is a CODAP file, we verify "appName"=="DG" and that
   * there is "appVersion", "appBuild", contexts, and components at the top level.
   * In addition the CODAP files for this purpose are expected to have exactly
   * one data context with two collections,
   * boundaries and index. The collection "boundaries" has one attribute, "boundary"
   * with Geojson content.
   */
  function readAndVerifyCODAPFile(fn) {
    var objString = fs.readFileSync(fn, {encoding: 'utf8'});
    var obj;
    try {
      obj = JSON.parse(objString);
    } catch (ex) {console.error(ex);}
    var hasCODAPFields = obj &&
        obj.appName === 'DG' &&
        obj.appVersion &&
        obj.appBuildNum &&
        obj.contexts &&
        obj.components
    ;
    var context = hasCODAPFields && obj.contexts[0];
    var collection = context && context.collections.find(function(c) { return c.name === 'boundaries';});
    var attr = collection && collection.attrs.find(function(attr) {return attr.name === 'boundary';});
    if (hasCODAPFields && attr) {
      return obj;
    } else {
      console.error('File is not properly formatted CODAP boundary file: ' + fn + ' ' + [!!obj, !!hasCODAPFields, !!context, !!collection, !!attr].join());
    }
  }

  function renderGeoJSONToSVG(geojson) {
    var refLng = null;
    var bBox = {
      xMin: Number.MAX_VALUE,
      yMin: Number.MAX_VALUE,
      xMax: -Number.MAX_VALUE,
      yMax: -Number.MAX_VALUE
    };

    function adjustBBox(pt) {
      bBox.xMin = Math.min(pt.x, bBox.xMin);
      bBox.yMin = Math.min(pt.y, bBox.yMin);
      bBox.xMax = Math.max(pt.x, bBox.xMax);
      bBox.yMax = Math.max(pt.y, bBox.yMax);
    }

    function latLongToXY(coord) {
      return merc.fromLatLngToPoint(adjustForAntimeridian({lat: coord[1], lng: coord[0]}));
    }

    function renderPoint(coord) {
      if (coord) {
        return '<circle r="3" cx="' + coord[0] + '" cy="' + coord[1] + '" />';
      }
    }
    function renderLine(coords) {
      var pathDef = coords.map(function (pt, ix){
        if (ix === 0) {
          return 'M'+ pt.x + ',' + pt.y + ' L';
        } else {
          return pt.x + ' ' + pt.y + ' ';
        }
      }).join();
      return '<path stroke-width="1" stroke="blue" d="' + pathDef + '" />';
    }
    function renderPolygon(coords) {
      var pathDef = coords.map(function (linearRing) {
        return linearRing.map(function (pt, ix){
          if (ix === 0) {
            return 'M'+ pt.x + ',' + pt.y + ' L';
          } else {
            return pt.x + ' ' + pt.y + ' ';
          }
        }).join();
      }).join();
      var svg = '<path stroke-width="0" fill="blue" d="' + pathDef + '"/>';
      return svg;
    }
    function adjustForAntimeridian(coord) {
      if (!coord) {
        return;
      }
      if (refLng === null) {
        refLng = coord.lng;
      } else if (Math.abs(coord.lng - refLng)>180) {
        coord.lng += (refLng>=0?360:-360);
      }
      return coord;
    }
    var renderers = {
      Point: function (geojson) {
        var coord = latLongToXY(geojson.coordinates);
        adjustBBox(coord);
        return renderPoint(coord);
      },
      MultiPoint: function (geojson) {
        var coords = geojson.coordinates;
        var dots = coords.map(function (coord) {
          var xyCoord = latLongToXY(coord);
          adjustBBox(coord);
          return renderPoint(xyCoord);
        });
        return dots.join('');
      },
      LineString: function (geojson) {
        var coords = geojson.coordinates;
        var xys = coords.map(function(coord) {
          var xy = latLongToXY(coord);
          adjustBBox(xy);
          return xy;
        });
        var svg = renderLine(xys);
        return svg;
      },
      MultiLineString: function (geojson) {
        var lines = geojson.coordinates;
        var svg = lines.map(function (line) {
          return renderers.LineString({coordinates: line });
        }).join('');
        return svg;
      },
      Polygon: function (geojson) {
        var coords = geojson.coordinates;
        var xys = coords.map(function(lineString) {
          return lineString.map(function(coord) {
            var xy = latLongToXY(coord);
            adjustBBox(xy);
            return xy;
          });
        });
        var svg = renderPolygon(xys);
        return svg;
      },
      MultiPolygon: function(geojson) {
        var polygons = geojson.coordinates;
        var svg = polygons.map(function (polygon) {
          return renderers.Polygon({coordinates: polygon });
        }).join('');
        return svg;
      },
      GeometryCollection: function (geojson) {
        var geometries = geojson.geometries;
        geometries.map(function(geometry) {
          var fn = renderers[geometry.type];
          if (!fn) {
            console.log("Unknown type: " + geometry.type);
          }
          return fn(geometry);
        }).join('');
      },
      Feature: function (geojson) {
        var geometry = geojson.geometry;
        var fn = renderers[geometry.type];
        if (!fn) {
          console.log("Unknown type: " + geometry.type);
        }
        return fn(geometry);
      },
      FeatureCollection: function (geojson) {
        var features = geojson.features;
        var svg = features.map(function (feature) {
          return renderers.Feature(feature);
        }).join();
        return svg.join('');
      }
    };
    if (!geojson) {
      return;
    }
    var fn = renderers[geojson.type];
    if (!fn) {
      console.log("Unknown type: " + geojson.type);
      return;
    }
    var svg = fn(geojson);
    var imgW = bBox.xMax-bBox.xMin;
    var imgH = bBox.yMax-bBox.yMin;
    var delta = (imgW - imgH) / 2;
    var x,y,w,h;
    if (delta < 0) {
      x = bBox.xMin - delta;
      y = bBox.yMin;
      w = imgW + (delta * 2);
      h = imgH;
    } else {
      x = bBox.xMin;
      y = bBox.yMin - delta;
      w = imgW;
      h = imgH + (delta * 2);
    }
    return '<?xml version="1.0" encoding="UTF-8" ?>' +
        '<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" ' +
        'viewBox="' + [x, y, w, h].join(' ') + '">' + svg + "</svg>";

  }

  function convertSVGtoPNG(svgText) {
    var svg = new Rsvg(svgText);
    var rendering = new Buffer(svg.render({
      format: 'png',
      width: svg.width,
      height: svg.height
    }).data);
    return rendering;
  }

  function formatAsDataURI(png, mime) {
    var out = 'data:' + mime + ';base64,' + btoa(png);
    return out;
  }

  var codapFile = process.argv[2];
  if (!codapFile) { usage(); }
  var codapDoc = readAndVerifyCODAPFile(codapFile);
  if (codapDoc) {
    var collection = codapDoc.contexts[0].collections.find(function(c) { return c.name === 'boundaries';});
    collection.cases.forEach(function (iCase) {
      try {
        var boundary = iCase.values.boundary;
        var isParsed = true;
        if (typeof boundary === 'string' && boundary.startsWith('{')) {
          boundary = JSON.parse(boundary);
          isParsed = false;
        }
        if (!boundary) {
          return;
        }
        // GeoJSON does not require any particular top-level type, but we need
        // to add a property so, we wrap 'naked' geoJSON in a Feature type.
        if (kGeometryTypes[boundary.type]) {
          boundary = {
            type: 'Feature',
            properties: {},
            geometry: boundary
          };
        }
        var svg = renderGeoJSONToSVG(boundary);
        var png = svg && convertSVGtoPNG(svg);
        var dataURI = png && formatAsDataURI(png, 'image/png');
        if (dataURI) {
          if (!boundary.properties) { boundary.properties = {}; }
          boundary.properties.THUMB = dataURI;
          if (!isParsed) {
            boundary = JSON.stringify(boundary);
          }
          iCase.values.boundary = boundary;
        }
      } catch (ex) {
        console.error(ex);
      }
    });
  }
  console.log(JSON.stringify(codapDoc));

}());
<|endoftext|>


<commit_before>/**
 * zrender
 *
 * @author Kener (@Kener-林峰, linzhifeng@baidu.com)
 *
 * shape类：标线
 */
define(function (require) {
    var Base = require('zrender/shape/Base');
    var IconShape = require('./Icon');
    var LineShape = require('zrender/shape/Line');
    var lineInstance = new LineShape({});
    var BrokenLineShape = require('zrender/shape/BrokenLine');
    var brokenLineInstance = new BrokenLineShape({});

    var matrix = require('zrender/tool/matrix');
    var area = require('zrender/tool/area');
    var dashedLineTo = require('zrender/shape/util/dashedLineTo');
    var smoothSpline = require('zrender/shape/util/smoothSpline');
    var zrUtil = require('zrender/tool/util');

    function MarkLine(options) {
        Base.call(this, options);
    }

    MarkLine.prototype =  {
        type : 'mark-line',
        /**
         * 画刷
         * @param ctx       画布句柄
         * @param e         形状实体
         * @param isHighlight   是否为高亮状态
         * @param updateCallback 需要异步加载资源的shape可以通过这个callback(e)
         *                       让painter更新视图，base.brush没用，需要的话重载brush
         */
        brush : function (ctx, isHighlight) {
            var style = this.style;

            if (isHighlight) {
                // 根据style扩展默认高亮样式
                style = this.getHighlightStyle(
                    style,
                    this.highlightStyle || {}
                );
            }

            ctx.save();
            this.setContext(ctx, style);

            // 设置transform
            this.updateTransform(ctx);

            ctx.beginPath();
            this.buildLinePath(ctx, style);
            ctx.stroke();

            this.brushSymbol(ctx, style, 0);
            this.brushSymbol(ctx, style, 1);

            if (style.text) {
                this.drawText(ctx, style, this.style);
            }

            ctx.restore();

            return;
        },

        /**
         * 创建线条路径
         * @param {Context2D} ctx Canvas 2D上下文
         * @param {Object} style 样式
         */
        buildLinePath : function (ctx, style) {
            var pointList = style.pointList || this.getPointList(style);
            style.pointList = pointList;

            var len = Math.min(
                    style.pointList.length,
                    Math.round(style.pointListLength || style.pointList.length)
                );

            if (!style.lineType || style.lineType == 'solid') {
                //默认为实线
                ctx.moveTo(pointList[0][0],pointList[0][1]);
                for (var i = 1; i < len; i++) {
                    ctx.lineTo(pointList[i][0],pointList[i][1]);
                }
            }
            else if (style.lineType == 'dashed'
                    || style.lineType == 'dotted'
            ) {
                if (style.smooth !== 'spline') {
                    // 直线
                    var dashLength = (style.lineWidth || 1)
                                 * (style.lineType == 'dashed' ? 5 : 1);
                    ctx.moveTo(pointList[0][0],pointList[0][1]);
                    for (var i = 1; i < len; i++) {
                        dashedLineTo(
                            ctx,
                            pointList[i - 1][0], pointList[i - 1][1],
                            pointList[i][0], pointList[i][1],
                            dashLength
                        );
                    }
                }
                else {
                    // 曲线
                    for (var i = 1; i < len; i += 2) {
                        ctx.moveTo(pointList[i - 1][0],pointList[i - 1][1]);
                        ctx.lineTo(pointList[i][0],pointList[i][1]);
                    }
                }
            }
        },

        /**
         * 标线始末标注
         */
        brushSymbol : function (ctx, style, idx) {
            if (style.symbol[idx] == 'none') {
                return;
            }
            ctx.save();
            ctx.beginPath();

            ctx.lineWidth = style.symbolBorder;
            ctx.strokeStyle = style.symbolBorderColor;
            // symbol
            style.iconType = style.symbol[idx].replace('empty', '')
                                              .toLowerCase();
            if (style.symbol[idx].match('empty')) {
                ctx.fillStyle = '#fff'; //'rgba(0, 0, 0, 0)';
            }

            // symbolRotate
            var len = Math.min(
                    style.pointList.length,
                    Math.round(style.pointListLength || style.pointList.length)
                );
            var x = idx === 0 ? style.pointList[0][0] : style.pointList[len - 1][0];
            var y = idx === 0 ? style.pointList[0][1] : style.pointList[len - 1][1];
            var rotate = typeof style.symbolRotate[idx] != 'undefined'
                         ? (style.symbolRotate[idx] - 0) : 0;
            var transform;
            if (rotate !== 0) {
                transform = matrix.create();
                matrix.identity(transform);
                if (x || y ) {
                    matrix.translate(transform, transform, [-x, -y]);
                }
                matrix.rotate(
                    transform, transform,
                    rotate * Math.PI / 180
                );
                if (x || y ) {
                    matrix.translate(transform, transform, [x, y]);
                }
                ctx.transform.apply(ctx, transform);
            }

            if (style.iconType == 'arrow' && rotate === 0) {
                // 箭头自动旋转，手动画
                this.buildArrawPath(ctx, style, idx);
            }
            else {
                // symbolSize
                var symbolSize = style.symbolSize[idx];
                style.x = x - symbolSize;
                style.y = y - symbolSize,
                style.width = symbolSize * 2;
                style.height = symbolSize * 2;
                IconShape.prototype.buildPath(ctx, style);
            }

            ctx.closePath();
            ctx.fill();
            ctx.stroke();
            ctx.restore();
        },

        buildArrawPath : function (ctx, style, idx) {
            var len = Math.min(
                    style.pointList.length,
                    Math.round(style.pointListLength || style.pointList.length)
                );
            var symbolSize = style.symbolSize[idx] * 2;
            var xStart = style.pointList[0][0];
            var xEnd = style.pointList[len - 1][0];
            var yStart = style.pointList[0][1];
            var yEnd = style.pointList[len - 1][1];
            var delta = 0;
            if (style.smooth === 'spline') {
                delta = 0.2; // 偏移0.2弧度
            }
            // 原谅我吧，这三角函数实在没想明白，只能这么笨了
            var rotate = Math.atan(
                    Math.abs((yEnd - yStart) / (xStart - xEnd)
                ));
            if (idx === 0) {
                if (xEnd > xStart) {
                    if (yEnd > yStart) {
                        rotate =  Math.PI * 2 - rotate + delta;
                    }
                    else {
                        rotate += delta;
                    }
                }
                else {
                    if (yEnd > yStart) {
                        rotate += Math.PI - delta;
                    }
                    else {
                        rotate = Math.PI - rotate - delta;
                    }
                }
            }
            else {
                if (xStart > xEnd) {
                    if (yStart > yEnd) {
                        rotate =  Math.PI * 2 - rotate + delta;
                    }
                    else {
                        rotate += delta;
                    }
                }
                else {
                    if (yStart > yEnd) {
                        rotate += Math.PI - delta;
                    }
                    else {
                        rotate = Math.PI - rotate - delta;
                    }
                }
            }

            var halfRotate = Math.PI / 8; // 夹角
            var x = idx === 0 ? xStart : xEnd;
            var y = idx === 0 ? yStart : yEnd;
            var point= [
                [
                    x + symbolSize * Math.cos(rotate - halfRotate),
                    y - symbolSize * Math.sin(rotate - halfRotate),
                ],
                [
                    x + symbolSize * 0.6 * Math.cos(rotate),
                    y - symbolSize * 0.6 * Math.sin(rotate),
                ],
                [
                    x + symbolSize * Math.cos(rotate + halfRotate),
                    y - symbolSize * Math.sin(rotate + halfRotate),
                ]
            ];
            ctx.moveTo(x, y);
            for (var i = 0, l = point.length; i <l; i++) {
                ctx.lineTo(point[i][0], point[i][1]);
            }
            ctx.lineTo(x, y);
        },

        getPointList : function (style) {
            var pointList = [
                [style.xStart, style.yStart],
                [style.xEnd, style.yEnd]
            ];
            if (style.smooth === 'spline') {
                var lastPointX = pointList[1][0];
                var lastPointY = pointList[1][1];
                pointList[3] = [lastPointX, lastPointY];
                pointList[1] = this.getOffetPoint(pointList[0], pointList[3]);
                pointList[2] = this.getOffetPoint(pointList[3], pointList[0]);
                pointList = smoothSpline(pointList, false);
                // 修正最后一点在插值产生的偏移
                pointList[pointList.length - 1] = [lastPointX, lastPointY];
            }
            return pointList;
        },

        /**
         * {Array} start point
         * {Array} end point
         */
        getOffetPoint : function (sp, ep) {
            var distance = Math.sqrt(Math.round(
                    (sp[0] - ep[0]) * (sp[0] - ep[0]) + (sp[1] - ep[1]) * (sp[1] - ep[1])
                )) / 3;
            //console.log(delta);
            var mp = [sp[0], sp[1]];
            var angle;
            var deltaAngle = 0.2; // 偏移0.2弧度
            if (sp[0] != ep[0] && sp[1] != ep[1]) {
                // 斜率存在
                var k = (ep[1] - sp[1]) / (ep[0] - sp[0]);
                angle = Math.atan(k);
            }
            else if (sp[0] == ep[0]){
                // 垂直线
                angle = (sp[1] <= ep[1] ? 1 : -1) * Math.PI / 2;
            }
            else {
                // 水平线
                angle = 0;
            }
            var dX;
            var dY;
            if (sp[0] <= ep[0]) {
                angle -= deltaAngle;
                dX = Math.round(Math.cos(angle) * distance);
                dY = Math.round(Math.sin(angle) * distance);
                mp[0] += dX;
                mp[1] += dY;
            }
            else {
                angle += deltaAngle;
                dX = Math.round(Math.cos(angle) * distance);
                dY = Math.round(Math.sin(angle) * distance);
                mp[0] -= dX;
                mp[1] -= dY;
            }
            return mp;
        },

        /**
         * 返回矩形区域，用于局部刷新和文字定位
         * @param {Object} style
         */
        getRect : function (style) {
            if (style.__rect) {
                return style.__rect;
            }

            var lineWidth = style.lineWidth || 1;
            style.__rect = {
                x : Math.min(style.xStart, style.xEnd) - lineWidth,
                y : Math.min(style.yStart, style.yEnd) - lineWidth,
                width : Math.abs(style.xStart - style.xEnd)
                        + lineWidth,
                height : Math.abs(style.yStart - style.yEnd)
                         + lineWidth
            };

            return style.__rect;
        },

        isCover : function (x, y) {
            var originPos = this.getTansform(x, y);
            x = originPos[0];
            y = originPos[1];

            // 快速预判并保留判断矩形
            var rect = this.style.__rect;
            if (!rect) {
                rect = this.style.__rect = this.getRect(this.style);
            }
            if (x >= rect.x
                && x <= (rect.x + rect.width)
                && y >= rect.y
                && y <= (rect.y + rect.height)
            ) {
                // 矩形内
                return this.style.smooth !== 'spline'
                       ? area.isInside(lineInstance, this.style, x, y)
                       : area.isInside(brokenLineInstance, this.style, x, y);
            }

            return false;
        }
    };

    zrUtil.inherits(MarkLine, Base);

    return MarkLine;
});<commit_msg>little optimize<commit_after>/**
 * zrender
 *
 * @author Kener (@Kener-林峰, linzhifeng@baidu.com)
 *
 * shape类：标线
 */
define(function (require) {
    var Base = require('zrender/shape/Base');
    var IconShape = require('./Icon');
    var LineShape = require('zrender/shape/Line');
    var lineInstance = new LineShape({});
    var BrokenLineShape = require('zrender/shape/BrokenLine');
    var brokenLineInstance = new BrokenLineShape({});

    var matrix = require('zrender/tool/matrix');
    var area = require('zrender/tool/area');
    var dashedLineTo = require('zrender/shape/util/dashedLineTo');
    var smoothSpline = require('zrender/shape/util/smoothSpline');
    var zrUtil = require('zrender/tool/util');

    function MarkLine(options) {
        Base.call(this, options);
    }

    MarkLine.prototype =  {
        type : 'mark-line',
        /**
         * 画刷
         * @param ctx       画布句柄
         * @param e         形状实体
         * @param isHighlight   是否为高亮状态
         * @param updateCallback 需要异步加载资源的shape可以通过这个callback(e)
         *                       让painter更新视图，base.brush没用，需要的话重载brush
         */
        brush : function (ctx, isHighlight) {
            var style = this.style;

            if (isHighlight) {
                // 根据style扩展默认高亮样式
                style = this.getHighlightStyle(
                    style,
                    this.highlightStyle || {}
                );
            }

            ctx.save();
            this.setContext(ctx, style);

            // 设置transform
            this.updateTransform(ctx);

            ctx.beginPath();
            this.buildLinePath(ctx, style);
            ctx.stroke();

            this.brushSymbol(ctx, style, 0);
            this.brushSymbol(ctx, style, 1);

            if (style.text) {
                this.drawText(ctx, style, this.style);
            }

            ctx.restore();
        },

        /**
         * 创建线条路径
         * @param {Context2D} ctx Canvas 2D上下文
         * @param {Object} style 样式
         */
        buildLinePath : function (ctx, style) {
            var pointList = style.pointList || this.getPointList(style);
            style.pointList = pointList;

            var len = Math.min(
                    style.pointList.length,
                    Math.round(style.pointListLength || style.pointList.length)
                );

            if (!style.lineType || style.lineType == 'solid') {
                //默认为实线
                ctx.moveTo(pointList[0][0],pointList[0][1]);
                for (var i = 1; i < len; i++) {
                    ctx.lineTo(pointList[i][0],pointList[i][1]);
                }
            }
            else if (style.lineType == 'dashed'
                    || style.lineType == 'dotted'
            ) {
                if (style.smooth !== 'spline') {
                    // 直线
                    var dashLength = (style.lineWidth || 1)
                                 * (style.lineType == 'dashed' ? 5 : 1);
                    ctx.moveTo(pointList[0][0],pointList[0][1]);
                    for (var i = 1; i < len; i++) {
                        dashedLineTo(
                            ctx,
                            pointList[i - 1][0], pointList[i - 1][1],
                            pointList[i][0], pointList[i][1],
                            dashLength
                        );
                    }
                }
                else {
                    // 曲线
                    for (var i = 1; i < len; i += 2) {
                        ctx.moveTo(pointList[i - 1][0],pointList[i - 1][1]);
                        ctx.lineTo(pointList[i][0],pointList[i][1]);
                    }
                }
            }
        },

        /**
         * 标线始末标注
         */
        brushSymbol : function (ctx, style, idx) {
            if (style.symbol[idx] == 'none') {
                return;
            }
            ctx.save();
            ctx.beginPath();

            ctx.lineWidth = style.symbolBorder;
            ctx.strokeStyle = style.symbolBorderColor;
            // symbol
            style.iconType = style.symbol[idx].replace('empty', '')
                                              .toLowerCase();
            if (style.symbol[idx].match('empty')) {
                ctx.fillStyle = '#fff'; //'rgba(0, 0, 0, 0)';
            }

            // symbolRotate
            var len = Math.min(
                    style.pointList.length,
                    Math.round(style.pointListLength || style.pointList.length)
                );
            var x = idx === 0 ? style.pointList[0][0] : style.pointList[len - 1][0];
            var y = idx === 0 ? style.pointList[0][1] : style.pointList[len - 1][1];
            var rotate = typeof style.symbolRotate[idx] != 'undefined'
                         ? (style.symbolRotate[idx] - 0) : 0;
            var transform;
            if (rotate !== 0) {
                transform = matrix.create();
                matrix.identity(transform);
                if (x || y ) {
                    matrix.translate(transform, transform, [-x, -y]);
                }
                matrix.rotate(
                    transform, transform,
                    rotate * Math.PI / 180
                );
                if (x || y ) {
                    matrix.translate(transform, transform, [x, y]);
                }
                ctx.transform.apply(ctx, transform);
            }

            if (style.iconType == 'arrow' && rotate === 0) {
                // 箭头自动旋转，手动画
                this.buildArrawPath(ctx, style, idx);
            }
            else {
                // symbolSize
                var symbolSize = style.symbolSize[idx];
                style.x = x - symbolSize;
                style.y = y - symbolSize,
                style.width = symbolSize * 2;
                style.height = symbolSize * 2;
                IconShape.prototype.buildPath(ctx, style);
            }

            ctx.closePath();
            ctx.fill();
            ctx.stroke();
            ctx.restore();
        },

        buildArrawPath : function (ctx, style, idx) {
            var len = Math.min(
                    style.pointList.length,
                    Math.round(style.pointListLength || style.pointList.length)
                );
            var symbolSize = style.symbolSize[idx] * 2;
            var xStart = style.pointList[0][0];
            var xEnd = style.pointList[len - 1][0];
            var yStart = style.pointList[0][1];
            var yEnd = style.pointList[len - 1][1];
            var delta = 0;
            if (style.smooth === 'spline') {
                delta = 0.2; // 偏移0.2弧度
            }
            // 原谅我吧，这三角函数实在没想明白，只能这么笨了
            var rotate = Math.atan(
                    Math.abs((yEnd - yStart) / (xStart - xEnd)
                ));
            if (idx === 0) {
                if (xEnd > xStart) {
                    if (yEnd > yStart) {
                        rotate =  Math.PI * 2 - rotate + delta;
                    }
                    else {
                        rotate += delta;
                    }
                }
                else {
                    if (yEnd > yStart) {
                        rotate += Math.PI - delta;
                    }
                    else {
                        rotate = Math.PI - rotate - delta;
                    }
                }
            }
            else {
                if (xStart > xEnd) {
                    if (yStart > yEnd) {
                        rotate =  Math.PI * 2 - rotate + delta;
                    }
                    else {
                        rotate += delta;
                    }
                }
                else {
                    if (yStart > yEnd) {
                        rotate += Math.PI - delta;
                    }
                    else {
                        rotate = Math.PI - rotate - delta;
                    }
                }
            }

            var halfRotate = Math.PI / 8; // 夹角
            var x = idx === 0 ? xStart : xEnd;
            var y = idx === 0 ? yStart : yEnd;
            var point= [
                [
                    x + symbolSize * Math.cos(rotate - halfRotate),
                    y - symbolSize * Math.sin(rotate - halfRotate),
                ],
                [
                    x + symbolSize * 0.6 * Math.cos(rotate),
                    y - symbolSize * 0.6 * Math.sin(rotate),
                ],
                [
                    x + symbolSize * Math.cos(rotate + halfRotate),
                    y - symbolSize * Math.sin(rotate + halfRotate),
                ]
            ];
            ctx.moveTo(x, y);
            for (var i = 0, l = point.length; i <l; i++) {
                ctx.lineTo(point[i][0], point[i][1]);
            }
            ctx.lineTo(x, y);
        },

        getPointList : function (style) {
            var pointList = [
                [style.xStart, style.yStart],
                [style.xEnd, style.yEnd]
            ];
            if (style.smooth === 'spline') {
                var lastPointX = pointList[1][0];
                var lastPointY = pointList[1][1];
                pointList[3] = [lastPointX, lastPointY];
                pointList[1] = this.getOffetPoint(pointList[0], pointList[3]);
                pointList[2] = this.getOffetPoint(pointList[3], pointList[0]);
                pointList = smoothSpline(pointList, false);
                // 修正最后一点在插值产生的偏移
                pointList[pointList.length - 1] = [lastPointX, lastPointY];
            }
            return pointList;
        },

        /**
         * {Array} start point
         * {Array} end point
         */
        getOffetPoint : function (sp, ep) {
            var distance = Math.sqrt(Math.round(
                    (sp[0] - ep[0]) * (sp[0] - ep[0]) + (sp[1] - ep[1]) * (sp[1] - ep[1])
                )) / 3;
            //console.log(delta);
            var mp = [sp[0], sp[1]];
            var angle;
            var deltaAngle = 0.2; // 偏移0.2弧度
            if (sp[0] != ep[0] && sp[1] != ep[1]) {
                // 斜率存在
                var k = (ep[1] - sp[1]) / (ep[0] - sp[0]);
                angle = Math.atan(k);
            }
            else if (sp[0] == ep[0]){
                // 垂直线
                angle = (sp[1] <= ep[1] ? 1 : -1) * Math.PI / 2;
            }
            else {
                // 水平线
                angle = 0;
            }
            var dX;
            var dY;
            if (sp[0] <= ep[0]) {
                angle -= deltaAngle;
                dX = Math.round(Math.cos(angle) * distance);
                dY = Math.round(Math.sin(angle) * distance);
                mp[0] += dX;
                mp[1] += dY;
            }
            else {
                angle += deltaAngle;
                dX = Math.round(Math.cos(angle) * distance);
                dY = Math.round(Math.sin(angle) * distance);
                mp[0] -= dX;
                mp[1] -= dY;
            }
            return mp;
        },

        /**
         * 返回矩形区域，用于局部刷新和文字定位
         * @param {Object} style
         */
        getRect : function (style) {
            if (style.__rect) {
                return style.__rect;
            }

            var lineWidth = style.lineWidth || 1;
            style.__rect = {
                x : Math.min(style.xStart, style.xEnd) - lineWidth,
                y : Math.min(style.yStart, style.yEnd) - lineWidth,
                width : Math.abs(style.xStart - style.xEnd)
                        + lineWidth,
                height : Math.abs(style.yStart - style.yEnd)
                         + lineWidth
            };

            return style.__rect;
        },

        isCover : function (x, y) {
            var originPos = this.getTansform(x, y);
            x = originPos[0];
            y = originPos[1];

            // 快速预判并保留判断矩形
            var rect = this.style.__rect;
            if (!rect) {
                rect = this.style.__rect = this.getRect(this.style);
            }
            if (x >= rect.x
                && x <= (rect.x + rect.width)
                && y >= rect.y
                && y <= (rect.y + rect.height)
            ) {
                // 矩形内
                return this.style.smooth !== 'spline'
                       ? area.isInside(lineInstance, this.style, x, y)
                       : area.isInside(brokenLineInstance, this.style, x, y);
            }

            return false;
        }
    };

    zrUtil.inherits(MarkLine, Base);

    return MarkLine;
});
<|endoftext|>