<commit_before>/*
 * @requires plugins/Tool.js
 * @requires OpenLayers/Request.js
 * @include OpenLayers/Layer/Vector.js
 * @include OpenLayers/Strategy/BBOX.js
 * @include OpenLayers/Protocol/WFS/v1_0_0.js
 * @include OpenLayers/Protocol/WFS.js
 * @include OpenLayers/Protocol/HTTP.js
 * @include OpenLayers/Format/JSON.js
 * @include OpenLayers/Control/SelectFeature.js
 * @include GeoExt/widgets/tree/LayerNode.js
 * @include c2corg/config/styles.js
 */

Ext.namespace("c2corg.plugins");

c2corg.plugins.LayerTree = Ext.extend(gxp.plugins.Tool, {

    ptype: "c2corg_layertree",

    init: function() {
        c2corg.plugins.LayerTree.superclass.init.apply(this, arguments);
        this.target.on("ready", this.viewerReady, this);
    },

    viewerReady: function() {
        var w = this.tree.findParentByType("window");
        w.el.show(); // we don't use w.show() since it would grab focus

        this.tree.delayedApplyState();
        this.tree.loadInitialThemes();
        this.tree.makeThemesInteractive();

        // listen on window resize to be sure that the
        // c2c layers window won't go out of the map
        Ext.EventManager.onWindowResize(function() {
            var m = this.target.mapPanel.getEl();
            var xy = w.el.getAlignToXY(m, "tr-tr", [-5, 0]);
            var pos = w.getPosition();

            if (xy[0] <= pos[0]) {
                w.alignTo(m, "tr-tr", [-20, pos[1]-xy[1]]);
            }
        }, this);
    },

    addOutput: function(config) {

        config = Ext.apply({
            xtype: "c2corg_layertree",
            mapPanel: this.target.mapPanel,
            url: this.url,
            initialThemes: this.initialThemes || []
        }, config || {});

        this.tree = c2corg.plugins.LayerTree.superclass.addOutput.call(this, config);
        this.tree.findParentByType("window").hide().alignTo(this.target.mapPanel.getEl(), "tr-tr", [-20, 45]);
        return this.tree;
    }
});

Ext.preg(c2corg.plugins.LayerTree.prototype.ptype, c2corg.plugins.LayerTree);

Ext.namespace("c2corg.tree");

c2corg.tree.LayerTree = Ext.extend(Ext.tree.TreePanel, {

    baseCls: "layertree",
    enableDD: false,
    rootVisible: false,
    useArrows: true,

    mapPanel: null,
    initialState: null,
    initialThemes: null,

    stateEvents: ["layervisibilitychange"],
    stateId: "tree",

    url: null,
    layers: {},
    popups: [],
    styleMap: null,

    initComponent: function() {
        this.addLayers();
        this.root = {
            children: this.getThemes()
        };
        c2corg.tree.LayerTree.superclass.initComponent.call(this, arguments);

        this.addEvents(
            /** private: event[layervisibilitychange]
             *  Fires after a checkbox state changes
             */
            "layervisibilitychange"
        );
        this.on("checkchange", function(node, checked) {
            this.fireEvent("layervisibilitychange");
        }, this);
    },

    getStyleMap: function() {
        if (!this.styleMap) {
            this.styleMap = c2corg.styleMap('c2clayer');
        }
        return this.styleMap;
    },

    createVectorLayer: function(options) {
        return new OpenLayers.Layer.Vector(options.name, {
            strategies: [new OpenLayers.Strategy.BBOX({resFactor: 1, ratio: 1})],
            protocol: new OpenLayers.Protocol.WFS({
                featureType: options.featureType,
                url: this.url,
                maxFeatures: options.maxFeatures || 200,
                geometryName: "geom",
                srsName: "EPSG:900913"
            }),
            isBaseLayer: false,
            visibility: false,
            styleMap: this.getStyleMap()
        });
    },

    addLayers: function() {
        this.layers = {
            "summits": this.createVectorLayer({name: "summits", featureType: "summits"}),
            "access": this.createVectorLayer({name: "access", featureType: "access"}),
            "public_transportations": this.createVectorLayer({name: "public_transportations", featureType: "public_transportations"}),
            "huts": this.createVectorLayer({name: "huts", featureType: "huts"}),
            "sites": this.createVectorLayer({name: "sites", featureType: "sites"}),
            "users": this.createVectorLayer({name: "users", featureType: "users"}),
            "images": this.createVectorLayer({name: "images", featureType: "images"}),
            "products": this.createVectorLayer({name: "products", featureType: "products"}),
            "routes": this.createVectorLayer({name: "routes", featureType: "routes", maxFeatures: 50}),
            "outings": this.createVectorLayer({name: "outings", featureType: "outings", maxFeatures: 50}),
            "maps": this.createVectorLayer({name: "maps", featureType: "maps"}),
            "ranges": this.createVectorLayer({name: "ranges", featureType: "ranges"}),
            "admin_limits": this.createVectorLayer({name: "admin_limits", featureType: "admin_limits"}),
            "countries": this.createVectorLayer({name: "countries", featureType: "countries"})
        };
        for (var i in this.layers) {
            this.mapPanel.map.addLayer(this.layers[i]);
        }
    },

    getThemes: function() {
        return [{
            text: OpenLayers.i18n("summits"),
            nodeType: "gx_layer",
            layer: this.layers["summits"],
            iconCls: "picto_summits",
            expanded: false,
            children: [{
                text: OpenLayers.i18n("pass"),
                iconCls: "picto_pass",
                leaf: true
            },{
                text: OpenLayers.i18n("lake"),
                iconCls: "picto_lake",
                leaf: true
            },{
                text: OpenLayers.i18n("valley"),
                iconCls: "picto_crag",
                leaf: true
            }]
        }, {
            text: OpenLayers.i18n("parkings"),
            nodeType: "gx_layer",
            layer: this.layers["access"],
            iconCls: "picto_parkings",
            expanded: false,
            children: [{
                text: OpenLayers.i18n("public_transportations"),
                iconCls: "picto_parking_green",
                leaf: true
            }]
        }, {
            text: OpenLayers.i18n("huts"),
            nodeType: "gx_layer",
            layer: this.layers["huts"],
            iconCls: "picto_huts",
            expanded: false,
            children: [{
                text: OpenLayers.i18n("gite"),
                iconCls: "picto_gite",
                leaf: true
            }, {
                text: OpenLayers.i18n("camping area"),
                iconCls: "picto_camp",
                leaf: true
            }]
        }, {
            text: OpenLayers.i18n("sites"),
            nodeType: "gx_layer",
            layer: this.layers["sites"],
            iconCls: "picto_sites",
            leaf: true
        }, {
            text: OpenLayers.i18n("routes"),
            nodeType: "gx_layer",
            layer: this.layers["routes"],
            iconCls: "picto_routes",
            leaf: true
        }, {
            text: OpenLayers.i18n("More..."),
            expanded: false,
            children: [{
                text: OpenLayers.i18n("users"),
                nodeType: "gx_layer",
                layer: this.layers["users"],
                iconCls: "picto_users",
                leaf: true
            }, {
                text: OpenLayers.i18n("images"),
                nodeType: "gx_layer",
                layer: this.layers["images"],
                iconCls: "picto_images",
                leaf: true
            }, {
                text: OpenLayers.i18n("products"),
                nodeType: "gx_layer",
                layer: this.layers["products"],
                iconCls: "picto_products",
                leaf: true
            }, {
                text: OpenLayers.i18n("outings"),
                nodeType: "gx_layer",
                layer: this.layers["outings"],
                iconCls: "picto_outings",
                leaf: true
            }, {
                text: OpenLayers.i18n("public_transportations"),
                nodeType: "gx_layer",
                layer: this.layers["public_transportations"],
                iconCls: "picto_parking_green",
                leaf: true
            }, {
                text: OpenLayers.i18n("maps"),
                nodeType: "gx_layer",
                layer: this.layers["maps"],
                iconCls: "picto_maps",
                leaf: true
            }, {
                text: OpenLayers.i18n("areas"),
                expanded: false,
                iconCls: "picto_areas",
                children: [{
                    text: OpenLayers.i18n("ranges"),
                    nodeType: "gx_layer",
                    layer: this.layers["ranges"],
                    iconCls: "picto_blank",
                    leaf: true
                }, {
                    text: OpenLayers.i18n("admin boundaries"),
                    nodeType: "gx_layer",
                    layer: this.layers["admin_limits"],
                    iconCls: "picto_blank",
                    leaf: true
                }, {
                    text: OpenLayers.i18n("countries"),
                    nodeType: "gx_layer",
                    layer: this.layers["countries"],
                    iconCls: "picto_blank",
                    leaf: true
                }]
            }]
        }];
    },

    /** api: method[applyState]
     *  :arg state: ``Object``
     */
    applyState: function(state) {
        // actual state is loaded later in delayedApplyState to prevent
        // the layer from being displayed under the baselayers
        this.initialState = state;
    },

    /** private: method[delayedApplyState]
     */
    delayedApplyState: function() {
        if (this.initialState && this.initialState.layers) {
            this.initialThemes = Ext.isArray(this.initialState.layers)
                                 ? this.initialState.layers
                                 : [this.initialState.layers];
        }
    },

    getState: function() {
        var layers = [], state = {};
        for (var i in this.layers) {
            if (this.layers[i].getVisibility()) {
                layers.push(i);
            }
        }
        if (layers) {
            state["layers"] = layers.join(",");
        }
        return state;
    },

    loadInitialThemes: function() {
        var layers, map = this.mapPanel.map;
        for (var i = 0, len = this.initialThemes.length; i < len; i++) {
            layers = map.getLayersByName(this.initialThemes[i]);
            if (layers.length == 1) {
                layers[0].setVisibility(true);
            }
        }
    },

    makeThemesInteractive: function() {
        var layers = [];
        for (var name in this.layers) {
            layers.push(this.layers[name]);
        }

        var clickControl = new OpenLayers.Control.SelectFeature(
            layers, {
                clickout: true,
                onSelect: function(feature) {

                    // close existing pinned popups
                    for (var i = 0; i < this.popups.length; i++) {
                      if (!this.popups[i].body.dom) { // clean closed popups
                        this.popups.splice(i, 1);
                        i--;
                      } else if (!this.popups[i].draggable) { // draggable popups are unpinned
                        this.popups[i].close();
                        this.popups.splice(i, 1);
                        i--;
                      }
                    }

                    var popup = new GeoExt.Popup({
                        width: 440,
                        height: 200,
                        autoScroll: true,
                        resizable: false,
                        cls: "popup_content",
                        location: feature
                    });
                    popup.show();
                    var popupUrl = "/" + feature.data.module + "/popup/" + feature.data.id + "/raw/true";
                    popup.load({
                        url: popupUrl,
                        timeout: 60,
                        text: OpenLayers.i18n("Please wait..."),
                        scripts: true
                    });

                    this.popups.push(popup);
                },
                scope: this
            });

        var hoverControl = new c2corg.control.hoverFeature(layers);

        this.mapPanel.map.addControl(hoverControl);
        this.mapPanel.map.addControl(clickControl);

        // FIXME we only activate the controls when at least on layer is active
        // This beacause we want a hoverControl for features displayed on the embedded map (see ShowFeatures.js)
        // Unfortunately we cannot add controls here and there on different sets of layers, only the last defined one
        // will get the events. (that's also why ShowFeatures plugin is called first in embedded.js)
        // This is the easiest solution for now, the drawback being that you cannot get the hover effect on features if you
        // have activated on of the c2c objetcs layers.
        // An other solution could be to make this plugin aware of the feature layer from ShowFeature plugin, have only one other control
        // on all those layers and distinguish afterwards, or even merge the two plugins together, but this is more changes and quite dirty
        this.on("layervisibilitychange", function() {
            if (this.getChecked().length) {
                hoverControl.activate();
                clickControl.activate();
            } else {
                hoverControl.deactivate();
                clickControl.deactivate();
            }
        }, this);
    }
});

Ext.reg("c2corg_layertree", c2corg.tree.LayerTree);

Ext.namespace("c2corg.control");

c2corg.control.hoverFeature = OpenLayers.Class(OpenLayers.Control.SelectFeature, {
    hover: true,
    highlightOnly: true,
    renderIntent: "temporary",
    protocol: null,
    listening: true,

    initialize: function(layers, options) {
        OpenLayers.Control.SelectFeature.prototype.initialize.apply(this, [layers, options]);

        this.protocol = new OpenLayers.Protocol.HTTP({
            url: "/documents/tooltipPreview",
            format: new OpenLayers.Format.JSON(),
            params: {}
        });

        this.events.on({
            //beforefeaturehighlighted: this.report,
            featurehighlighted: this.showPreview,
            featureunhighlighted: this.hidePreview,
            scope: this
        });
    },

    showPreview: function(e) {
        if (!this.listening) {
            return;
        }
        this.listening = false;
        var feature = e.feature;
        this.currentFeature = feature;
        this.protocol.read({
            params: {
                module: feature.data.module,
                id: feature.data.id
            },
            callback: function(result) {
                if (result.success()) {
                    if (this.currentFeature.geometry instanceof OpenLayers.Geometry.Point) {
                        var lonlat = new OpenLayers.LonLat(this.currentFeature.geometry.x,
                                                           this.currentFeature.geometry.y);
                    } else {
                        // FIXME: would be better to use the cursor position
                        var lonlat = this.currentFeature.bounds.getCenterLonLat();
                    }
                    var px = this.map.getViewPortPxFromLonLat(lonlat);
                    this.div.innerHTML = OpenLayers.i18n("${item}. Click to show info", {
                        item: result.features.name
                    });
                    this.div.style.top = (px.y + 10) + "px";
                    this.div.style.left = (px.x + 10) + "px";
                    this.div.style.display = "block";
                }
            },
            scope: this
        });
    },

    hidePreview: function(e) {
        this.div.innerHTML = "";
        this.div.style.display = "none";
        this.listening = true;
    },

    draw: function() {
        OpenLayers.Control.prototype.draw.apply(this, arguments);
        this.div.className = "tooltip_tooltip";
        this.div.style.display = "none";
        return this.div;
    }
});
<commit_msg>fixes #886<commit_after>/*
 * @requires plugins/Tool.js
 * @requires OpenLayers/Request.js
 * @include OpenLayers/Layer/Vector.js
 * @include OpenLayers/Strategy/BBOX.js
 * @include OpenLayers/Protocol/WFS/v1_0_0.js
 * @include OpenLayers/Protocol/WFS.js
 * @include OpenLayers/Protocol/HTTP.js
 * @include OpenLayers/Format/JSON.js
 * @include OpenLayers/Control/SelectFeature.js
 * @include GeoExt/widgets/tree/LayerNode.js
 * @include c2corg/config/styles.js
 */

Ext.namespace("c2corg.plugins");

c2corg.plugins.LayerTree = Ext.extend(gxp.plugins.Tool, {

    ptype: "c2corg_layertree",

    init: function() {
        c2corg.plugins.LayerTree.superclass.init.apply(this, arguments);
        this.target.on("ready", this.viewerReady, this);
    },

    viewerReady: function() {
        var w = this.tree.findParentByType("window");
        w.el.show(); // we don't use w.show() since it would grab focus

        this.tree.delayedApplyState();
        this.tree.loadInitialThemes();
        this.tree.makeThemesInteractive();

        // listen on window resize to be sure that the
        // c2c layers window won't go out of the map
        Ext.EventManager.onWindowResize(function() {
            var m = this.target.mapPanel.getEl();
            var xy = w.el.getAlignToXY(m, "tr-tr", [-5, 0]);
            var pos = w.getPosition();

            if (xy[0] <= pos[0]) {
                w.alignTo(m, "tr-tr", [-20, pos[1]-xy[1]]);
            }
        }, this);
    },

    addOutput: function(config) {

        config = Ext.apply({
            xtype: "c2corg_layertree",
            mapPanel: this.target.mapPanel,
            url: this.url,
            initialThemes: this.initialThemes || []
        }, config || {});

        this.tree = c2corg.plugins.LayerTree.superclass.addOutput.call(this, config);
        this.tree.findParentByType("window").hide().alignTo(this.target.mapPanel.getEl(), "tr-tr", [-20, 45]);
        return this.tree;
    }
});

Ext.preg(c2corg.plugins.LayerTree.prototype.ptype, c2corg.plugins.LayerTree);

Ext.namespace("c2corg.tree");

c2corg.tree.LayerTree = Ext.extend(Ext.tree.TreePanel, {

    baseCls: "layertree",
    enableDD: false,
    rootVisible: false,
    useArrows: true,

    mapPanel: null,
    initialState: null,
    initialThemes: null,

    stateEvents: ["layervisibilitychange"],
    stateId: "tree",

    url: null,
    layers: {},
    popups: [],
    styleMap: null,

    initComponent: function() {
        this.addLayers();
        this.root = {
            children: this.getThemes()
        };
        c2corg.tree.LayerTree.superclass.initComponent.call(this, arguments);

        this.addEvents(
            /** private: event[layervisibilitychange]
             *  Fires after a checkbox state changes
             */
            "layervisibilitychange"
        );
        this.on("checkchange", function(node, checked) {
            this.fireEvent("layervisibilitychange");
        }, this);
    },

    getStyleMap: function() {
        if (!this.styleMap) {
            this.styleMap = c2corg.styleMap('c2clayer');
        }
        return this.styleMap;
    },

    createVectorLayer: function(options) {
        return new OpenLayers.Layer.Vector(options.name, {
            strategies: [new OpenLayers.Strategy.BBOX({resFactor: 1, ratio: 1})],
            protocol: new OpenLayers.Protocol.WFS({
                featureType: options.featureType,
                url: this.url,
                maxFeatures: options.maxFeatures || 200,
                geometryName: "geom",
                srsName: "EPSG:900913"
            }),
            isBaseLayer: false,
            visibility: false,
            styleMap: this.getStyleMap()
        });
    },

    addLayers: function() {
        this.layers = {
            "summits": this.createVectorLayer({name: "summits", featureType: "summits"}),
            "access": this.createVectorLayer({name: "access", featureType: "access"}),
            "public_transportations": this.createVectorLayer({name: "public_transportations", featureType: "public_transportations"}),
            "huts": this.createVectorLayer({name: "huts", featureType: "huts"}),
            "sites": this.createVectorLayer({name: "sites", featureType: "sites"}),
            "users": this.createVectorLayer({name: "users", featureType: "users"}),
            "images": this.createVectorLayer({name: "images", featureType: "images"}),
            "products": this.createVectorLayer({name: "products", featureType: "products"}),
            "routes": this.createVectorLayer({name: "routes", featureType: "routes", maxFeatures: 50}),
            "outings": this.createVectorLayer({name: "outings", featureType: "outings", maxFeatures: 50}),
            "maps": this.createVectorLayer({name: "maps", featureType: "maps"}),
            "ranges": this.createVectorLayer({name: "ranges", featureType: "ranges"}),
            "admin_limits": this.createVectorLayer({name: "admin_limits", featureType: "admin_limits"}),
            "countries": this.createVectorLayer({name: "countries", featureType: "countries"})
        };
        for (var i in this.layers) {
            this.mapPanel.map.addLayer(this.layers[i]);
        }
    },

    getThemes: function() {
        return [{
            text: OpenLayers.i18n("summits"),
            nodeType: "gx_layer",
            layer: this.layers["summits"],
            iconCls: "picto_summits",
            expanded: false,
            children: [{
                text: OpenLayers.i18n("pass"),
                iconCls: "picto_pass",
                leaf: true
            },{
                text: OpenLayers.i18n("lake"),
                iconCls: "picto_lake",
                leaf: true
            },{
                text: OpenLayers.i18n("valley"),
                iconCls: "picto_crag",
                leaf: true
            }]
        }, {
            text: OpenLayers.i18n("parkings"),
            nodeType: "gx_layer",
            layer: this.layers["access"],
            iconCls: "picto_parkings",
            expanded: false,
            children: [{
                text: OpenLayers.i18n("public_transportations"),
                iconCls: "picto_parking_green",
                leaf: true
            }]
        }, {
            text: OpenLayers.i18n("huts"),
            nodeType: "gx_layer",
            layer: this.layers["huts"],
            iconCls: "picto_huts",
            expanded: false,
            children: [{
                text: OpenLayers.i18n("gite"),
                iconCls: "picto_gite",
                leaf: true
            }, {
                text: OpenLayers.i18n("camping area"),
                iconCls: "picto_camp",
                leaf: true
            }]
        }, {
            text: OpenLayers.i18n("sites"),
            nodeType: "gx_layer",
            layer: this.layers["sites"],
            iconCls: "picto_sites",
            leaf: true
        }, {
            text: OpenLayers.i18n("routes"),
            nodeType: "gx_layer",
            layer: this.layers["routes"],
            iconCls: "picto_routes",
            leaf: true
        }, {
            text: OpenLayers.i18n("More..."),
            expanded: false,
            children: [{
                text: OpenLayers.i18n("users"),
                nodeType: "gx_layer",
                layer: this.layers["users"],
                iconCls: "picto_users",
                leaf: true
            }, {
                text: OpenLayers.i18n("images"),
                nodeType: "gx_layer",
                layer: this.layers["images"],
                iconCls: "picto_images",
                leaf: true
            }, {
                text: OpenLayers.i18n("products"),
                nodeType: "gx_layer",
                layer: this.layers["products"],
                iconCls: "picto_products",
                leaf: true
            }, {
                text: OpenLayers.i18n("outings"),
                nodeType: "gx_layer",
                layer: this.layers["outings"],
                iconCls: "picto_outings",
                leaf: true
            }, {
                text: OpenLayers.i18n("public_transportations"),
                nodeType: "gx_layer",
                layer: this.layers["public_transportations"],
                iconCls: "picto_parking_green",
                leaf: true
            }, {
                text: OpenLayers.i18n("maps"),
                nodeType: "gx_layer",
                layer: this.layers["maps"],
                iconCls: "picto_maps",
                leaf: true
            }, {
                text: OpenLayers.i18n("areas"),
                expanded: false,
                iconCls: "picto_areas",
                children: [{
                    text: OpenLayers.i18n("ranges"),
                    nodeType: "gx_layer",
                    layer: this.layers["ranges"],
                    iconCls: "picto_blank",
                    leaf: true
                }, {
                    text: OpenLayers.i18n("admin boundaries"),
                    nodeType: "gx_layer",
                    layer: this.layers["admin_limits"],
                    iconCls: "picto_blank",
                    leaf: true
                }, {
                    text: OpenLayers.i18n("countries"),
                    nodeType: "gx_layer",
                    layer: this.layers["countries"],
                    iconCls: "picto_blank",
                    leaf: true
                }]
            }]
        }];
    },

    /** api: method[applyState]
     *  :arg state: ``Object``
     */
    applyState: function(state) {
        // actual state is loaded later in delayedApplyState to prevent
        // the layer from being displayed under the baselayers
        this.initialState = state;
    },

    /** private: method[delayedApplyState]
     */
    delayedApplyState: function() {
        if (this.initialState && this.initialState.layers) {
            this.initialThemes = Ext.isArray(this.initialState.layers)
                                 ? this.initialState.layers
                                 : [this.initialState.layers];
        }
    },

    getState: function() {
        var layers = [], state = {};
        for (var i in this.layers) {
            if (this.layers[i].getVisibility()) {
                layers.push(i);
            }
        }
        if (layers) {
            state["layers"] = layers.join(",");
        }
        return state;
    },

    loadInitialThemes: function() {
        var layers, map = this.mapPanel.map;
        for (var i = 0, len = this.initialThemes.length; i < len; i++) {
            layers = map.getLayersByName(this.initialThemes[i]);
            if (layers.length == 1) {
                layers[0].setVisibility(true);
            }
        }
    },

    makeThemesInteractive: function() {
        var layers = [];
        for (var name in this.layers) {
            layers.push(this.layers[name]);
        }

        var clickControl = new OpenLayers.Control.SelectFeature(
            layers, {
                clickout: true,
                onSelect: function(feature) {

                    // close existing pinned popups
                    for (var i = 0; i < this.popups.length; i++) {
                      if (!this.popups[i].body.dom) { // clean closed popups
                        this.popups.splice(i, 1);
                        i--;
                      } else if (!this.popups[i].draggable) { // draggable popups are unpinned
                        this.popups[i].close();
                        this.popups.splice(i, 1);
                        i--;
                      }
                    }

                    var popup = new GeoExt.Popup({
                        width: 440,
                        height: 200,
                        autoScroll: true,
                        resizable: false,
                        cls: "popup_content",
                        location: feature
                    });
                    popup.show();
                    var popupUrl = "/" + feature.data.module + "/popup/" + feature.data.id + "/raw/true";
                    popup.load({
                        url: popupUrl,
                        timeout: 60,
                        text: OpenLayers.i18n("Please wait..."),
                        scripts: true
                    });

                    this.popups.push(popup);
                },
                scope: this
            });

        var hoverControl = new c2corg.control.hoverFeature(layers);

        this.mapPanel.map.addControl(hoverControl);
        this.mapPanel.map.addControl(clickControl);

        // FIXME we only activate the controls when at least on layer is active
        // This beacause we want a hoverControl for features displayed on the embedded map (see ShowFeatures.js)
        // Unfortunately we cannot add controls here and there on different sets of layers, only the last defined one
        // will get the events. (that's also why ShowFeatures plugin is called first in embedded.js)
        // This is the easiest solution for now, the drawback being that you cannot get the hover effect on features if you
        // have activated on of the c2c objetcs layers.
        // An other solution could be to make this plugin aware of the feature layer from ShowFeature plugin, have only one other control
        // on all those layers and distinguish afterwards, or even merge the two plugins together, but this is more changes and quite dirty
        if (this.getChecked().length) {
          hoverControl.activate();
          clickControl.activate();
        }

        this.on("layervisibilitychange", function() {
            if (this.getChecked().length) {
                hoverControl.activate();
                clickControl.activate();
            } else {
                hoverControl.deactivate();
                clickControl.deactivate();
            }
        }, this);
    }
});

Ext.reg("c2corg_layertree", c2corg.tree.LayerTree);

Ext.namespace("c2corg.control");

c2corg.control.hoverFeature = OpenLayers.Class(OpenLayers.Control.SelectFeature, {
    hover: true,
    highlightOnly: true,
    renderIntent: "temporary",
    protocol: null,
    listening: true,

    initialize: function(layers, options) {
        OpenLayers.Control.SelectFeature.prototype.initialize.apply(this, [layers, options]);

        this.protocol = new OpenLayers.Protocol.HTTP({
            url: "/documents/tooltipPreview",
            format: new OpenLayers.Format.JSON(),
            params: {}
        });

        this.events.on({
            //beforefeaturehighlighted: this.report,
            featurehighlighted: this.showPreview,
            featureunhighlighted: this.hidePreview,
            scope: this
        });
    },

    showPreview: function(e) {
        if (!this.listening) {
            return;
        }
        this.listening = false;
        var feature = e.feature;
        this.currentFeature = feature;
        this.protocol.read({
            params: {
                module: feature.data.module,
                id: feature.data.id
            },
            callback: function(result) {
                if (result.success()) {
                    if (this.currentFeature.geometry instanceof OpenLayers.Geometry.Point) {
                        var lonlat = new OpenLayers.LonLat(this.currentFeature.geometry.x,
                                                           this.currentFeature.geometry.y);
                    } else {
                        // FIXME: would be better to use the cursor position
                        var lonlat = this.currentFeature.bounds.getCenterLonLat();
                    }
                    var px = this.map.getViewPortPxFromLonLat(lonlat);
                    this.div.innerHTML = OpenLayers.i18n("${item}. Click to show info", {
                        item: result.features.name
                    });
                    this.div.style.top = (px.y + 10) + "px";
                    this.div.style.left = (px.x + 10) + "px";
                    this.div.style.display = "block";
                }
            },
            scope: this
        });
    },

    hidePreview: function(e) {
        this.div.innerHTML = "";
        this.div.style.display = "none";
        this.listening = true;
    },

    draw: function() {
        OpenLayers.Control.prototype.draw.apply(this, arguments);
        this.div.className = "tooltip_tooltip";
        this.div.style.display = "none";
        return this.div;
    }
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 Google 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.
 */

CLASS({
  name: 'Server',
  package: 'com.google.ow',

  requires: [
    'MDAO',
    'com.google.ow.IdGenerator',
    'com.google.ow.SubstreamSink',
    'com.google.ow.content.Stream',
    'com.google.ow.content.UpdateStream',
    'com.google.ow.content.Video',
    'com.google.ow.model.Ad',
    'com.google.ow.model.CustomerOrder',
    'com.google.ow.model.Envelope',
    'com.google.ow.model.MerchantOrder',
    'com.google.ow.model.ProductAd',
    'com.google.ow.stream.NewConnectionListener',
    'com.google.plus.Person',
    'com.google.plus.ShareSink',
    'foam.dao.AuthorizedDAO',
    'foam.dao.DebugAuthDAO',
    'foam.dao.EasyDAO',
    'foam.dao.LoggingDAO',
    'foam.dao.PrivateOwnerAuthorizer',
    'foam.dao.ProxyDAO',
    'foam.mlang.PropertySequence',
  ],
  imports: [
    'console',
    'idGenerator',
    'exportToContext',
    'exportDAO',
    'exportDirectory',
  ],
  exports: [
    'personDAO',
    // TODO(markdittmer): This bypasses authorization for server components.
    // We should do better.
    'streamDAO_ as streamDAO',
    'createStreamItem',
  ],

  constants: {
    DATA_PATHS: [
      global.FOAM_BOOT_DIR + '/../js/com/google/ow/local/',
      global.FOAM_BOOT_DIR + '/../js/com/google/ow/examples/',
    ],
  },

  properties: [
    {
      name: 'fs',
      factory: function() { return require('fs'); }
    },
    {
      name: 'util',
      lazyFactory: function() { return require('util'); },
    },
    {
      name: 'idGenerator',
      lazyFactory: function() {
        return this.IdGenerator.create(null, this.Y);
      },
    },
    {
      name: 'personDAO_',
      lazyFactory: function() {
        return this.EasyDAO.create({
          model: this.Person,
          name: 'people',
          daoType: this.MDAO,
          guid: true,
          isServer: true,
          // logging: true,
        });
      },
    },
    {
      name: 'personDAO',
      lazyFactory: function() {
        // TODO(markdittmer): Authorize access to people.
        return this.personDAO_;
        // return this.authorizeFactory(this.Person, this.personDAO_);
      },
    },
    {
      name: 'streamDAO_',
      lazyFactory: function() {
        return this.EasyDAO.create({
          model: this.Envelope,
          name: 'streams',
          daoType: this.MDAO,
          guid: true,
          isServer: true,
          // autoIndex: true,
          // logging: true,
        }, this.Y).orderBy(this.Envelope.TIMESTAMP);
      },
    },
    {
      name: 'streamDAO',
      lazyFactory: function() {
        return this.authorizeFactory(
            this.Envelope,
            this.ShareSink.create({ delegate: this.streamDAO_ }, this.Y));
      },
    },
    {
      name: 'videoDAO_',
      lazyFactory: function() {
        return this.EasyDAO.create({
          model: this.Video,
          name: 'videos',
          daoType: this.MDAO,
          // isServer: true,
          // logging: true,
        }, this.Y);
      },
    },
    {
      name: 'videoDAO',
      lazyFactory: function() {
        // TODO(markdittmer): Authorize access to people.
        return this.videoDAO_;
        //return this.authorizeFactory(this.Video, this.videoDAO_);
      },
    },
    {
      name: 'streamData',
      help: 'Test data',
      factory: function() { return this.dataFactory('streams', this.Stream); },
    },
    {
      name: 'personData',
      help: 'Test data',
      factory: function() { return this.dataFactory('people', this.Person); },
    },
    {
      name: 'adData',
      help: 'Test data',
      factory: function() { return this.dataFactory('ads', this.ProductAd); },
    },
    {
      name: 'videoData',
      help: 'Test data',
      factory: function() { return this.dataFactory('videos', this.Video); },
    },
    {
      model_: 'FunctionProperty',
      name: 'createStreamItem',
      hidden: true,
      factory: function() {
        return function(source, target, data, opt_sid) {
          var srcId = source.id || source;
          var tgtId = target.id || target;
          // share handler, if some sort of setup is needed
          if ( data.onShare ) data.onShare(srcId, tgtId, opt_sid);
          return this.Envelope.create({
            owner: tgtId,
            source: srcId,
            data: data,
            sid: opt_sid || data.sid || '',
            substreams: data.substreams || [],
          });
        }.bind(this);
      },
    },
  ],

  methods: [
    function init() {
      this.SUPER();
      // Done in loadData at the moment
      //this.streamDAO_.listen(this.SubstreamSink.create(null, this.Y));
    },
    function authorizeFactory(model, delegate) {
      return this.DebugAuthDAO.create({
        delegate: this.AuthorizedDAO.create({
          model: model,
          delegate: delegate,
          authorizer: this.PrivateOwnerAuthorizer.create({
            ownerProp: this.Envelope.OWNER,
          }, this.Y),
        }, this.Y),
      }, this.Y);
    },
    function dataFactory(name, model) {
      var dao = this.EasyDAO.create({
        name: name,
        model: model,
        daoType: this.MDAO,
        guid: true,
      }, this.Y);

      var dataPaths = this.DATA_PATHS.map(function(p) {
        return p + name + '.json';
      });
      for ( var i = 0; i < dataPaths.length; ++i ) {
        try {
          var result = this.fs.readFileSync(dataPaths[i]);
          if ( result ) {
            JSONUtil.arrayToObjArray(this.Y, eval('(' + result + ')'), model).select(dao);
            break;
          }
        } catch (e) {}
      }

      return dao;
    },
    function execute() {
      this.exportDAO(this.streamDAO);
      this.exportDAO(this.personDAO);
      this.exportToContext({
        streamDAO: this.streamDAO_,
      });
      this.exportDirectory('/static', 'static');
      this.loadData();
    },
    function loadData() {
      // Bootstrap streams.
      var self = this, videoStreamEnv;
      self.adData.select(GROUP_BY(self.Ad.AD_STREAM))(function(data) {
        var sids = data.groupKeys;
        for ( var i = 0; i < sids.length; ++i ) {
          self.NewConnectionListener.create({
            streamDAO: self.streamDAO_,
            substreams: [ sids[i] ],
            getPeople: function(env) {
              return [ env.data.merchant, env.data.customer ];
            },
            streamClientFactory: function(inputEnv, pid) {
              return self.UpdateStream.create({
                substreams: this.getConnectionSubstreams(inputEnv),
              });
            },
            streamDataFactory: function(data, pid) {
              // TODO(markdittmer): This special case should be better abstracted.
              if ( data.status === 'PENDING' ) data.status = 'SUBMITTED';
              if ( pid === data.merchant ) return self.MerchantOrder.create(data);
              else                         return self.CustomerOrder.create(data);
            },
          });
        }
      });
      self.streamData.select({
        put: function(o) {
          // HACK(markdittmer): Manual setup tasks for various test streams.
          if ( o.data.name === 'Test Videos' ) {
            videoStreamEnv = o;
            self.personDAO_.pipe({
              put: function(person) {
                //console.log("Person vid list create", person.id, videoStreamEnv.substreams);
                self.streamDAO_.put(self.Envelope.create({
                  owner: person.id,
                  source: '0',
                  substreams: videoStreamEnv.substreams,
                  sid: videoStreamEnv.sid,
                  data: videoStreamEnv.data.clone(),
                }));
              }
            });
          } else if ( o.data.name === 'User Data' ) {
            self.personDAO_.pipe({
              put: function(person) {
                console.log("Person *** data create", o.data.name_, person.id);
                self.streamDAO_.put(self.Envelope.create({
                  owner: person.id,
                  source: '0',
                  substreams: o.substreams,
                  sid: o.sid,
                  data: o.data.clone(),
                }));
              }
            });
          } else {
            self.streamDAO_.put(o);
          }
        },
      });
      self.loadData_(videoStreamEnv);
    },
    function loadData_(videoStreamEnv) {
      // Give everyone the ads and videos.
      this.personDAO_.pipe({
        put: function(person) {
          console.log("Person put!", person.id);

          var incr = 0;
          this.adData.select({
            put: function(ad) {
              this.streamDAO_.put(this.Envelope.create({
                owner: person.id,
                source: ad.merchant,
                data: ad,
              }));
            }.bind(this),
          });
          if ( videoStreamEnv ) {
            this.videoDAO_.select({
              put: function(video) {
                //console.log("Create vid:",person.id, video.id, videoStreamEnv.substreams[0]);
                this.streamDAO_.put(this.Envelope.create({
  //                id: 'fakeIDVid'+incr++,
                  owner: person.id,
                  source: incr,
                  data: video,
                  sid: videoStreamEnv.substreams[0] + '/fakeIDVid'+incr,
                  substreams: [videoStreamEnv.substreams[0] + '/fakeIDVid'+incr,
                    video.id+'/commentThreads'],
                }, this.Y));
                //console.log('vid sid:',videoStreamEnv.substreams[0] + '/fakeIDVid'+incr);
              }.bind(this),
            });
          }
        }.bind(this),
      });
      // Bootstrap video data.
      this.videoData.select(this.videoDAO_)(function() {
        // Bootstrap people.
        this.personData.select(this.personDAO_)(function() {
           this.streamDAO_.listen(this.SubstreamSink.create(null, this.Y));
        }.bind(this));
      }.bind(this));

    },
  ],
});
<commit_msg>OW: Remove ShareSink from server.<commit_after>/**
 * @license
 * Copyright 2015 Google 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.
 */

CLASS({
  name: 'Server',
  package: 'com.google.ow',

  requires: [
    'MDAO',
    'com.google.ow.IdGenerator',
    'com.google.ow.SubstreamSink',
    'com.google.ow.content.Stream',
    'com.google.ow.content.UpdateStream',
    'com.google.ow.content.Video',
    'com.google.ow.model.Ad',
    'com.google.ow.model.CustomerOrder',
    'com.google.ow.model.Envelope',
    'com.google.ow.model.MerchantOrder',
    'com.google.ow.model.ProductAd',
    'com.google.ow.stream.NewConnectionListener',
    'com.google.plus.Person',
    'foam.dao.AuthorizedDAO',
    'foam.dao.DebugAuthDAO',
    'foam.dao.EasyDAO',
    'foam.dao.LoggingDAO',
    'foam.dao.PrivateOwnerAuthorizer',
    'foam.dao.ProxyDAO',
    'foam.mlang.PropertySequence',
  ],
  imports: [
    'console',
    'idGenerator',
    'exportToContext',
    'exportDAO',
    'exportDirectory',
  ],
  exports: [
    'personDAO',
    // TODO(markdittmer): This bypasses authorization for server components.
    // We should do better.
    'streamDAO_ as streamDAO',
    'createStreamItem',
  ],

  constants: {
    DATA_PATHS: [
      global.FOAM_BOOT_DIR + '/../js/com/google/ow/local/',
      global.FOAM_BOOT_DIR + '/../js/com/google/ow/examples/',
    ],
  },

  properties: [
    {
      name: 'fs',
      factory: function() { return require('fs'); }
    },
    {
      name: 'util',
      lazyFactory: function() { return require('util'); },
    },
    {
      name: 'idGenerator',
      lazyFactory: function() {
        return this.IdGenerator.create(null, this.Y);
      },
    },
    {
      name: 'personDAO_',
      lazyFactory: function() {
        return this.EasyDAO.create({
          model: this.Person,
          name: 'people',
          daoType: this.MDAO,
          guid: true,
          isServer: true,
          // logging: true,
        });
      },
    },
    {
      name: 'personDAO',
      lazyFactory: function() {
        // TODO(markdittmer): Authorize access to people.
        return this.personDAO_;
        // return this.authorizeFactory(this.Person, this.personDAO_);
      },
    },
    {
      name: 'streamDAO_',
      lazyFactory: function() {
        return this.EasyDAO.create({
          model: this.Envelope,
          name: 'streams',
          daoType: this.MDAO,
          guid: true,
          isServer: true,
          // autoIndex: true,
          // logging: true,
        }, this.Y).orderBy(this.Envelope.TIMESTAMP);
      },
    },
    {
      name: 'streamDAO',
      lazyFactory: function() {
        return this.authorizeFactory(this.Envelope, this.streamDAO_);
      },
    },
    {
      name: 'videoDAO_',
      lazyFactory: function() {
        return this.EasyDAO.create({
          model: this.Video,
          name: 'videos',
          daoType: this.MDAO,
          // isServer: true,
          // logging: true,
        }, this.Y);
      },
    },
    {
      name: 'videoDAO',
      lazyFactory: function() {
        // TODO(markdittmer): Authorize access to people.
        return this.videoDAO_;
        //return this.authorizeFactory(this.Video, this.videoDAO_);
      },
    },
    {
      name: 'streamData',
      help: 'Test data',
      factory: function() { return this.dataFactory('streams', this.Stream); },
    },
    {
      name: 'personData',
      help: 'Test data',
      factory: function() { return this.dataFactory('people', this.Person); },
    },
    {
      name: 'adData',
      help: 'Test data',
      factory: function() { return this.dataFactory('ads', this.ProductAd); },
    },
    {
      name: 'videoData',
      help: 'Test data',
      factory: function() { return this.dataFactory('videos', this.Video); },
    },
    {
      model_: 'FunctionProperty',
      name: 'createStreamItem',
      hidden: true,
      factory: function() {
        return function(source, target, data, opt_sid) {
          var srcId = source.id || source;
          var tgtId = target.id || target;
          // share handler, if some sort of setup is needed
          if ( data.onShare ) data.onShare(srcId, tgtId, opt_sid);
          return this.Envelope.create({
            owner: tgtId,
            source: srcId,
            data: data,
            sid: opt_sid || data.sid || '',
            substreams: data.substreams || [],
          });
        }.bind(this);
      },
    },
  ],

  methods: [
    function init() {
      this.SUPER();
      // Done in loadData at the moment
      //this.streamDAO_.listen(this.SubstreamSink.create(null, this.Y));
    },
    function authorizeFactory(model, delegate) {
      return this.DebugAuthDAO.create({
        delegate: this.AuthorizedDAO.create({
          model: model,
          delegate: delegate,
          authorizer: this.PrivateOwnerAuthorizer.create({
            ownerProp: this.Envelope.OWNER,
          }, this.Y),
        }, this.Y),
      }, this.Y);
    },
    function dataFactory(name, model) {
      var dao = this.EasyDAO.create({
        name: name,
        model: model,
        daoType: this.MDAO,
        guid: true,
      }, this.Y);

      var dataPaths = this.DATA_PATHS.map(function(p) {
        return p + name + '.json';
      });
      for ( var i = 0; i < dataPaths.length; ++i ) {
        try {
          var result = this.fs.readFileSync(dataPaths[i]);
          if ( result ) {
            JSONUtil.arrayToObjArray(this.Y, eval('(' + result + ')'), model).select(dao);
            break;
          }
        } catch (e) {}
      }

      return dao;
    },
    function execute() {
      this.exportDAO(this.streamDAO);
      this.exportDAO(this.personDAO);
      this.exportToContext({
        streamDAO: this.streamDAO_,
      });
      this.exportDirectory('/static', 'static');
      this.loadData();
    },
    function loadData() {
      // Bootstrap streams.
      var self = this, videoStreamEnv;
      self.adData.select(GROUP_BY(self.Ad.AD_STREAM))(function(data) {
        var sids = data.groupKeys;
        for ( var i = 0; i < sids.length; ++i ) {
          self.NewConnectionListener.create({
            streamDAO: self.streamDAO_,
            substreams: [ sids[i] ],
            getPeople: function(env) {
              return [ env.data.merchant, env.data.customer ];
            },
            streamClientFactory: function(inputEnv, pid) {
              return self.UpdateStream.create({
                substreams: this.getConnectionSubstreams(inputEnv),
              });
            },
            streamDataFactory: function(data, pid) {
              // TODO(markdittmer): This special case should be better abstracted.
              if ( data.status === 'PENDING' ) data.status = 'SUBMITTED';
              if ( pid === data.merchant ) return self.MerchantOrder.create(data);
              else                         return self.CustomerOrder.create(data);
            },
          });
        }
      });
      self.streamData.select({
        put: function(o) {
          // HACK(markdittmer): Manual setup tasks for various test streams.
          if ( o.data.name === 'Test Videos' ) {
            videoStreamEnv = o;
            self.personDAO_.pipe({
              put: function(person) {
                //console.log("Person vid list create", person.id, videoStreamEnv.substreams);
                self.streamDAO_.put(self.Envelope.create({
                  owner: person.id,
                  source: '0',
                  substreams: videoStreamEnv.substreams,
                  sid: videoStreamEnv.sid,
                  data: videoStreamEnv.data.clone(),
                }));
              }
            });
          } else if ( o.data.name === 'User Data' ) {
            self.personDAO_.pipe({
              put: function(person) {
                console.log("Person *** data create", o.data.name_, person.id);
                self.streamDAO_.put(self.Envelope.create({
                  owner: person.id,
                  source: '0',
                  substreams: o.substreams,
                  sid: o.sid,
                  data: o.data.clone(),
                }));
              }
            });
          } else {
            self.streamDAO_.put(o);
          }
        },
      });
      self.loadData_(videoStreamEnv);
    },
    function loadData_(videoStreamEnv) {
      // Give everyone the ads and videos.
      this.personDAO_.pipe({
        put: function(person) {
          console.log("Person put!", person.id);

          var incr = 0;
          this.adData.select({
            put: function(ad) {
              this.streamDAO_.put(this.Envelope.create({
                owner: person.id,
                source: ad.merchant,
                data: ad,
              }));
            }.bind(this),
          });
          if ( videoStreamEnv ) {
            this.videoDAO_.select({
              put: function(video) {
                //console.log("Create vid:",person.id, video.id, videoStreamEnv.substreams[0]);
                this.streamDAO_.put(this.Envelope.create({
  //                id: 'fakeIDVid'+incr++,
                  owner: person.id,
                  source: incr,
                  data: video,
                  sid: videoStreamEnv.substreams[0] + '/fakeIDVid'+incr,
                  substreams: [videoStreamEnv.substreams[0] + '/fakeIDVid'+incr,
                    video.id+'/commentThreads'],
                }, this.Y));
                //console.log('vid sid:',videoStreamEnv.substreams[0] + '/fakeIDVid'+incr);
              }.bind(this),
            });
          }
        }.bind(this),
      });
      // Bootstrap video data.
      this.videoData.select(this.videoDAO_)(function() {
        // Bootstrap people.
        this.personData.select(this.personDAO_)(function() {
           this.streamDAO_.listen(this.SubstreamSink.create(null, this.Y));
        }.bind(this));
      }.bind(this));

    },
  ],
});
<|endoftext|>


<commit_before>import Ember from 'ember';
import config from 'dummy/config/environment';

export default Ember.Controller.extend({

  // Grab the current version to show what
  // version the documentation was generated for
  addonVersion: Ember.computed({
    get() {
      let version = config.APP.version;

      // remove the sha (if there is one)
      version = version.split('+')[0];

      // ensure there is a "v" prefix
      // if ( !version.startsWith('v') ) // ES2015 (requires polyfill)
      if ( version.indexOf('v') !== 0 ) {
        version = `v${version}`;
      }

      return version;
    }
  })

});
<commit_msg>Manually define version for docs<commit_after>import Ember from 'ember';
import config from 'dummy/config/environment';

export default Ember.Controller.extend({

  // Grab the current version to show what
  // version the documentation was generated for
  addonVersion: Ember.computed({
    get() {
      let version = config.APP.version || '2.6.2';

      // remove the sha (if there is one)
      version = version.split('+')[0];

      // ensure there is a "v" prefix
      // if ( !version.startsWith('v') ) // ES2015 (requires polyfill)
      if ( version.indexOf('v') !== 0 ) {
        version = `v${version}`;
      }

      return version;
    }
  })

});
<|endoftext|>


<commit_before>// Copyright 2014-2018, University of Colorado Boulder

/**
 * Abstract base type for scenes.
 *
 * @author Andrea Lin
 */
define( require => {
  'use strict';

  // modules
  const Bounds2 = require( 'DOT/Bounds2' );
  const GQColors = require( 'GRAPHING_QUADRATICS/common/GQColors' );
  const GQConstants = require( 'GRAPHING_QUADRATICS/common/GQConstants' );
  const Graph = require( 'GRAPHING_LINES/common/model/Graph' );
  const graphingQuadratics = require( 'GRAPHING_QUADRATICS/graphingQuadratics' );
  const ModelViewTransform2 = require( 'PHETCOMMON/view/ModelViewTransform2' );
  const ObservableArray = require( 'AXON/ObservableArray' );
  const PointTool = require( 'GRAPHING_LINES/common/model/PointTool' );
  const Property = require( 'AXON/Property' );
  const Quadratic = require( 'GRAPHING_QUADRATICS/common/model/Quadratic' );
  const Vector2 = require( 'DOT/Vector2' );

  // constants
  const GRID_VIEW_UNITS = 530; // max dimension (width or height) of the grid in view coordinates
  const ORIGIN_OFFSET = new Vector2( 315, 330 ); // offset of the graph's origin in view coordinates

  class GQScene {

    /**
     * @param {Object} [options]
     * @abstract
     */
    constructor( options ) {

      options = _.extend( {
        quadratic: new Quadratic( 1, 0, 0 ), // initial interactive quadratic
        icon: null // used on radio button to select the scene
      }, options );

      // @public (read-only) icon that represents this scene
      this.icon = options.icon;

      // @public (read-only) graph
      this.graph = new Graph( GQConstants.X_AXIS_RANGE, GQConstants.Y_AXIS_RANGE );

      // @public the interactive quadratic
      this.quadraticProperty = new Property( options.quadratic, {
        valueType: Quadratic,
        reentrant: true
      } );

      // @public saved quadratics
      this.savedQuadratics = new ObservableArray( [] );

      // @public (read-only) interactive and saved quadratics
      this.lines = new ObservableArray();
      Property.multilink( [ this.quadraticProperty, this.savedQuadratics.lengthProperty ], ( quadratic ) => {
        this.lines.clear();
        this.lines.add( quadratic );
        this.lines.addAll( this.savedQuadratics.getArray() );
      } );

      // transform between model and view coordinate frames
      const modelViewTransformScale = GRID_VIEW_UNITS / Math.max(
        this.graph.xRange.getLength(),
        this.graph.yRange.getLength()
      );

      // @public (read-only) model-view transform, created in the model because it's dependent on graph axes ranges.
      this.modelViewTransform = ModelViewTransform2.createOffsetXYScaleMapping(
        ORIGIN_OFFSET,
        modelViewTransformScale,
        -modelViewTransformScale // y is inverted
      );

      // @public (read-only) point tools, drag bounds determined empirically
      this.pointTools = [
        new PointTool( new Vector2( -2, -12 ), 'right', this.lines,
          new Bounds2(
            this.graph.xRange.min - 1,
            this.graph.yRange.min - 1,
            this.graph.xRange.max + 3,
            this.graph.yRange.max + 3
          ) ),
        new PointTool( new Vector2( 2, -12 ), 'left', this.lines,
          new Bounds2(
            this.graph.xRange.min - 1,
            this.graph.yRange.min - 3,
            this.graph.xRange.max + 3,
            this.graph.yRange.max + 1
          ) )
      ];
    }

    // @public
    reset() {
      this.quadraticProperty.reset();
      this.eraseQuadratics();
      this.pointTools.forEach( pointTool => { pointTool.reset(); } );
    }

    /**
     * Saves the current quadratic.
     * @public
     */
    saveQuadratic() {

      // The implementation supports N saved quadratics, but the requirements specify that N=1.
      // So before saving the quadratic, perform an erase.
      this.eraseQuadratics();
      this.savedQuadratics.add( this.quadraticProperty.value.withColor( GQColors.SAVED_CURVE ) );
    }

    /**
     * Erases the saved quadratics.
     * @public
     */
    eraseQuadratics() {
      this.savedQuadratics.clear();
    }
  }

  return graphingQuadratics.register( 'GQScene', GQScene );
} );
<commit_msg>add logging to quadraticProperty<commit_after>// Copyright 2014-2018, University of Colorado Boulder

/**
 * Abstract base type for scenes.
 *
 * @author Andrea Lin
 */
define( require => {
  'use strict';

  // modules
  const Bounds2 = require( 'DOT/Bounds2' );
  const GQColors = require( 'GRAPHING_QUADRATICS/common/GQColors' );
  const GQConstants = require( 'GRAPHING_QUADRATICS/common/GQConstants' );
  const Graph = require( 'GRAPHING_LINES/common/model/Graph' );
  const graphingQuadratics = require( 'GRAPHING_QUADRATICS/graphingQuadratics' );
  const ModelViewTransform2 = require( 'PHETCOMMON/view/ModelViewTransform2' );
  const ObservableArray = require( 'AXON/ObservableArray' );
  const PointTool = require( 'GRAPHING_LINES/common/model/PointTool' );
  const Property = require( 'AXON/Property' );
  const Quadratic = require( 'GRAPHING_QUADRATICS/common/model/Quadratic' );
  const Vector2 = require( 'DOT/Vector2' );

  // constants
  const GRID_VIEW_UNITS = 530; // max dimension (width or height) of the grid in view coordinates
  const ORIGIN_OFFSET = new Vector2( 315, 330 ); // offset of the graph's origin in view coordinates

  class GQScene {

    /**
     * @param {Object} [options]
     * @abstract
     */
    constructor( options ) {

      options = _.extend( {
        quadratic: new Quadratic( 1, 0, 0 ), // initial interactive quadratic
        icon: null // used on radio button to select the scene
      }, options );

      // @public (read-only) icon that represents this scene
      this.icon = options.icon;

      // @public (read-only) graph
      this.graph = new Graph( GQConstants.X_AXIS_RANGE, GQConstants.Y_AXIS_RANGE );

      // @public the interactive quadratic
      this.quadraticProperty = new Property( options.quadratic, {
        valueType: Quadratic,
        reentrant: true
      } );
      this.quadraticProperty.link( quadratic => {
        phet.log && phet.log( 'quadratic=' + quadratic.a + ' x^2 + ' + quadratic.b + ' x + ' + quadratic.c );
      } );

      // @public saved quadratics
      this.savedQuadratics = new ObservableArray( [] );

      // @public (read-only) interactive and saved quadratics
      this.lines = new ObservableArray();
      Property.multilink( [ this.quadraticProperty, this.savedQuadratics.lengthProperty ], ( quadratic ) => {
        this.lines.clear();
        this.lines.add( quadratic );
        this.lines.addAll( this.savedQuadratics.getArray() );
      } );

      // transform between model and view coordinate frames
      const modelViewTransformScale = GRID_VIEW_UNITS / Math.max(
        this.graph.xRange.getLength(),
        this.graph.yRange.getLength()
      );

      // @public (read-only) model-view transform, created in the model because it's dependent on graph axes ranges.
      this.modelViewTransform = ModelViewTransform2.createOffsetXYScaleMapping(
        ORIGIN_OFFSET,
        modelViewTransformScale,
        -modelViewTransformScale // y is inverted
      );

      // @public (read-only) point tools, drag bounds determined empirically
      this.pointTools = [
        new PointTool( new Vector2( -2, -12 ), 'right', this.lines,
          new Bounds2(
            this.graph.xRange.min - 1,
            this.graph.yRange.min - 1,
            this.graph.xRange.max + 3,
            this.graph.yRange.max + 3
          ) ),
        new PointTool( new Vector2( 2, -12 ), 'left', this.lines,
          new Bounds2(
            this.graph.xRange.min - 1,
            this.graph.yRange.min - 3,
            this.graph.xRange.max + 3,
            this.graph.yRange.max + 1
          ) )
      ];
    }

    // @public
    reset() {
      this.quadraticProperty.reset();
      this.eraseQuadratics();
      this.pointTools.forEach( pointTool => { pointTool.reset(); } );
    }

    /**
     * Saves the current quadratic.
     * @public
     */
    saveQuadratic() {

      // The implementation supports N saved quadratics, but the requirements specify that N=1.
      // So before saving the quadratic, perform an erase.
      this.eraseQuadratics();
      this.savedQuadratics.add( this.quadraticProperty.value.withColor( GQColors.SAVED_CURVE ) );
    }

    /**
     * Erases the saved quadratics.
     * @public
     */
    eraseQuadratics() {
      this.savedQuadratics.clear();
    }
  }

  return graphingQuadratics.register( 'GQScene', GQScene );
} );
<|endoftext|>


<commit_before>/* eslint-env mocha */
/* eslint-disable func-names, prefer-arrow-callback */

import sideNav from '../../pageobjects/side-nav.page';
import mainContent from '../../pageobjects/main-content.page';
import {publicChannelName, privateChannelName} from '../../data/channel.js';
import {targetUser} from '../../data/interactions.js';

//test data imports
import {checkIfUserIsValid, setPublicChannelCreated, setPrivateChannelCreated, setDirectMessageCreated} from '../../data/checks';
import {username, email, password} from '../../data/user.js';
//Basic usage test start
describe('[Channel creation]', function() {
	before(()=>{
		checkIfUserIsValid(username, email, password);
	});

	afterEach(function() {
		if (this.currentTest.state !== 'passed') {
			setPublicChannelCreated(false);
			switch (this.currentTest.title) {
				case 'create a public channel':
					setPublicChannelCreated(false);
					console.log('Public channel Not Created!');
					break;
				case 'create a private channel':
					setPrivateChannelCreated(false);
					console.log('Private channel Not Created!');
					break;
				case 'start a direct message with rocket.cat':
					setDirectMessageCreated(false);
					console.log('Direct Message Not Created!');
					break;
			}
		}
	});

	describe('public channel:', function() {
		it('it should create a public channel', function() {
			sideNav.createChannel(publicChannelName, false, false);
			setPublicChannelCreated(true);
		});
	});

	describe('private channel:', function() {
		it('it should create a private channel', function() {
			sideNav.createChannel(privateChannelName, true, false);
			setPrivateChannelCreated(true);
		});
	});

	describe('direct message:', function() {
		it('it should start a direct message with rocket.cat', function() {
			sideNav.spotlightSearchIcon.click();
			sideNav.spotlightSearch.waitForVisible(10000);
			sideNav.searchChannel(targetUser);
			setDirectMessageCreated(true);
		});
	});
});
<commit_msg>fix tests<commit_after>/* eslint-env mocha */
/* eslint-disable func-names, prefer-arrow-callback */

import sideNav from '../../pageobjects/side-nav.page';
import {publicChannelName, privateChannelName} from '../../data/channel.js';
import {targetUser} from '../../data/interactions.js';

//test data imports
import {checkIfUserIsValid, setPublicChannelCreated, setPrivateChannelCreated, setDirectMessageCreated} from '../../data/checks';
import {username, email, password} from '../../data/user.js';
//Basic usage test start
describe('[Channel creation]', function() {
	before(()=>{
		checkIfUserIsValid(username, email, password);
	});

	afterEach(function() {
		if (this.currentTest.state !== 'passed') {
			setPublicChannelCreated(false);
			switch (this.currentTest.title) {
				case 'create a public channel':
					setPublicChannelCreated(false);
					console.log('Public channel Not Created!');
					break;
				case 'create a private channel':
					setPrivateChannelCreated(false);
					console.log('Private channel Not Created!');
					break;
				case 'start a direct message with rocket.cat':
					setDirectMessageCreated(false);
					console.log('Direct Message Not Created!');
					break;
			}
		}
	});

	describe('public channel:', function() {
		it('it should create a public channel', function() {
			sideNav.createChannel(publicChannelName, false, false);
			setPublicChannelCreated(true);
		});
	});

	describe('private channel:', function() {
		it('it should create a private channel', function() {
			sideNav.createChannel(privateChannelName, true, false);
			setPrivateChannelCreated(true);
		});
	});

	describe('direct message:', function() {
		it('it should start a direct message with rocket.cat', function() {
			sideNav.spotlightSearchIcon.click();
			sideNav.spotlightSearch.waitForVisible(10000);
			sideNav.searchChannel(targetUser);
			setDirectMessageCreated(true);
		});
	});
});
<|endoftext|>


<commit_before>// Copyright 2017, University of Colorado Boulder

/**
 * The lattice is a 2D grid with a value in each cell that represents the wave amplitude at that point.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var Bounds2 = require( 'DOT/Bounds2' );
  var Emitter = require( 'AXON/Emitter' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Matrix = require( 'DOT/Matrix' );
  var waveInterference = require( 'WAVE_INTERFERENCE/waveInterference' );

  // constants

  // The wave speed in the coordinate frame of the lattice, see http://www.mtnmath.com/whatth/node47.html. We tried
  // different values, but they do not have the properer emergent behavior.  WAVE_SPEED=1 propagates out as a diamond
  // rather than a circle, and WAVE_SPEED=0.1 is too slow and throws off the frequency of light.
  var WAVE_SPEED = 0.5;
  var WAVE_SPEED_SQUARED = WAVE_SPEED * WAVE_SPEED; // precompute to avoid work in the inner loop
  var NUMBER_OF_MATRICES = 3; // The algorithm we use for the discretized wave equation requires current value + 2 history points

  // This is the threshold for the wave value that determines if the light has visited.  If the value is higher,
  // it will track the wavefront of the light more accurately (and hence could be used for more accurate computation of
  // the speed of light), but will generate more artifacts in the initial wave.  If the value is lower, it will generate
  // fewer artifacts in the initial propagation, but will lead the initial wavefront by too far and make it seem like
  // light is faster than it should be measured (based on the propagation of wavefronts).
  var LIGHT_VISIT_THRESHOLD = 0.06;

  /**
   * @param {number} width - width of the lattice
   * @param {number} height - height of the lattice
   * @param {number} dampX - number of cells on the left and again on the right to use for damping
   * @param {number} dampY - number of cells on the top and again on the bottom to use for damping
   * @constructor
   */
  function Lattice( width, height, dampX, dampY ) {

    // @public (read-only) {number} - number of cells on the left and again on the right to use for damping
    this.dampX = dampX;

    // @public (read-only) {number} - number of cells on the top and again on the bottom to use for damping
    this.dampY = dampY;

    // @private {Matrix[]} - matrices for current value, previous value and value before previous
    this.matrices = [];
    for ( var i = 0; i < NUMBER_OF_MATRICES; i++ ) {
      this.matrices.push( new Matrix( width, height ) );
    }

    // @private {Matrix} - keeps track of which cells have been visited by the wave
    this.visitedMatrix = new Matrix( width, height );

    // @private {number} - indicates the current matrix. Previous matrix is one higher (with correct modulus)
    this.currentMatrixIndex = 0;

    // @public {Emitter} - sends a notification each time the lattice updates.
    this.changedEmitter = new Emitter();

    // @public (read-only) {number} - width of the lattice (includes damping regions)
    this.width = width;

    // @public (read-only) {number} - height of the lattice (includes damping regions)
    this.height = height;

    // @public {number} - Determines how far we have animated between the "last" and "current" matrices, so that we
    // can use getInterpolatedValue to update the view at 60fps even though the model is running at a slower rate
    this.interpolationRatio = 0;
  }

  waveInterference.register( 'Lattice', Lattice );

  return inherit( Object, Lattice, {

    /**
     * Gets a Bounds2 representing the visible (non-damping) region of the lattice.
     * @returns {Bounds2}
     */
    getVisibleBounds: function() {
      return new Bounds2( this.dampX, this.dampY, this.width - this.dampX, this.height - this.dampY );
    },

    /**
     * Gets a Bounds2 representing the full region of the lattice, including damping regions.
     * @returns {Bounds2}
     */
    getBounds: function() {
      return new Bounds2( 0, 0, this.width, this.height );
    },

    /**
     * Read the values on the center line of the lattice (omits the out-of-bounds damping regions), for display in the
     * WaveAreaGraphNode
     * @param {number[]} array - array to fill with the values for performance/memory, will be resized if necessary
     * @public
     */
    getCenterLineValues: function( array ) {
      var samplingWidth = this.width - this.dampX * 2;

      // Resize array if necessary
      if ( array.length !== samplingWidth ) {
        array.length = 0;
      }
      var samplingVerticalLocation = Math.round( this.height / 2 );
      for ( var i = 0; i < this.width - this.dampX * 2; i++ ) {
        array[ i ] = this.getCurrentValue( i + this.dampX, samplingVerticalLocation );
      }
      return array;
    },

    /**
     * Returns the current value in the given cell
     * @param {number} i - horizontal integer coordinate
     * @param {number} j - vertical integer coordinate
     * @returns {number}
     * @public
     */
    getCurrentValue: function( i, j ) {
      return this.matrices[ this.currentMatrixIndex ].get( i, j );
    },

    /**
     * Returns the interpolated value of the given cell
     * @param {number} i - horizontal integer coordinate
     * @param {number} j - vertical integer coordinate
     * @returns {number}
     * @public
     */
    getInterpolatedValue: function( i, j ) {
      return this.getCurrentValue( i, j ) * this.interpolationRatio + this.getLastValue( i, j ) * ( 1 - this.interpolationRatio );
    },

    /**
     * Sets the current value in the given cell
     * @param {number} i - horizontal integer coordinate
     * @param {number} j - vertical integer coordinate
     * @param {number} value
     * @public
     */
    setCurrentValue: function( i, j, value ) {
      this.matrices[ this.currentMatrixIndex ].set( i, j, value );
    },

    /**
     * Returns the previous value in the given cell
     * @param {number} i - horizontal integer coordinate
     * @param {number} j - vertical integer coordinate
     * @private
     */
    getLastValue: function( i, j ) {
      return this.matrices[ ( this.currentMatrixIndex + 1 ) % this.matrices.length ].get( i, j );
    },

    /**
     * Sets the previous value in the given cell
     * @param {number} i - horizontal integer coordinate
     * @param {number} j - vertical integer coordinate
     * @param {number} value
     * @private
     */
    setLastValue: function( i, j, value ) {
      this.matrices[ ( this.currentMatrixIndex + 1 ) % this.matrices.length ].set( i, j, value );
    },

    /**
     * Across a horizontal line, dampen the waveform based on past adjacent values.  Developed in the Java version as
     * one step in preventing artifacts near the edge of the visible part of the lattice.
     * @param {number} j - y coordinate for the lattice
     * @param {number} dj - distance above or below to look
     * @param {Matrix} matrix0 - current matrix
     * @param {Matrix} matrix2 - two time steps ago
     * @private
     */
    dampHorizontalTemporal: function( j, dj, matrix0, matrix2 ) {
      for ( var i = 0; i < this.width; i++ ) {
        matrix0.set( i, j, matrix2.get( i, j + dj ) );
      }
    },

    /**
     * Across a vertical line, dampen the waveform based on past adjacent values.  Developed in the Java version as
     * one step in preventing artifacts near the edge of the visible part of the lattice.
     * @param {number} i - x coordinate for the lattice
     * @param {number} di - distance left or right to look
     * @param {Matrix} matrix0 - current matrix
     * @param {Matrix} matrix2 - two time steps ago
     * @private
     */
    dampVerticalTemporal: function( i, di, matrix0, matrix2 ) {
      for ( var j = 0; j < this.height; j++ ) {
        matrix0.set( i, j, matrix2.get( i + di, j ) );
      }
    },

    /**
     * Exponential decay in a vertical band to prevent reflections or artifacts from the sides of the lattice.
     * @param {number} i0 - x coordinate of the damping region
     * @param {number} sign - +1 or -1 depending on which way the damping moves
     * @param {number} width - width of the damping region
     * @private
     */
    decayVertical: function( i0, sign, width ) {
      for ( var j = 0; j < this.height; j++ ) {
        for ( var step = 0; step < width; step++ ) {
          var distFromDampBoundary = width - step;
          var damp = this.getDamp( distFromDampBoundary );
          var i = i0 + step * sign;
          this.setCurrentValue( i, j, this.getCurrentValue( i, j ) * damp );
          this.setLastValue( i, j, this.getLastValue( i, j ) * damp );
        }
      }
    },

    /**
     * Exponential decay in a horizontal band to prevent reflections or artifacts from the sides of the lattice.
     * @param {number} j0 - y coordinate of the damping region
     * @param {number} sign - +1 or -1 depending on which way the damping moves
     * @param {number} height - height of the damping region
     * @private
     */
    decayHorizontal: function( j0, sign, height ) {
      for ( var i = 0; i < this.width; i++ ) {
        for ( var step = 0; step < height; step++ ) {
          var distFromDampBoundary = height - step;
          var damp = this.getDamp( distFromDampBoundary );
          var j = j0 + step * sign;
          this.setCurrentValue( i, j, this.getCurrentValue( i, j ) * damp );
          this.setLastValue( i, j, this.getLastValue( i, j ) * damp );
        }
      }
    },

    /**
     * Determines whether the incoming wave has reached the cell.
     * @param {number} i - horizontal coordinate to check
     * @param {number} j - vertical coordinate to check
     * @returns {boolean}
     */
    hasCellBeenVisited: function( i, j ) {
      return this.visitedMatrix.get( i, j ) === 1;
    },

    /**
     * Damp more aggressively further from the edge of the visible lattice
     * @param {number} depthInDampRegion - number of cells into the damping region
     * @returns {number} scale factor for dampening the wave
     * @private
     */
    getDamp: function( depthInDampRegion ) {
      return ( 1 - depthInDampRegion * 0.0001 );
    },

    /**
     * Resets all of the wave values to 0
     * @public
     */
    clear: function() {
      for ( var i = 0; i < this.matrices.length; i++ ) {
        this.matrices[ i ].timesEquals( 0 );
      }

      this.visitedMatrix.timesEquals( 0 );
    },

    /**
     * Gets the values on the right hand side of the wave (before the damping region), for determining intensity.
     * @returns {number[]}
     * @public
     */
    getOutputColumn: function() {

      // TODO(performance): garbage-free form?  Would require preallocating the entire intensitySample matrix
      var column = [];
      for ( var j = this.dampY; j < this.height - this.dampY; j++ ) {
        column.push( this.getCurrentValue( this.width - this.dampX - 1, j ) );
      }
      return column;
    },

    /**
     * Propagates the wave by one step.  This is a discrete algorithm and cannot use dt.
     * @param {function} forcingFunction - sets values before and after the wave equation
     * @public
     */
    step: function( forcingFunction ) {

      // Apply values before lattice step so the values will be used to propagate
      forcingFunction( this );

      this.currentMatrixIndex = ( this.currentMatrixIndex - 1 + this.matrices.length ) % this.matrices.length;

      var matrix0 = this.matrices[ ( this.currentMatrixIndex + 0 ) % this.matrices.length ];
      var matrix1 = this.matrices[ ( this.currentMatrixIndex + 1 ) % this.matrices.length ];
      var matrix2 = this.matrices[ ( this.currentMatrixIndex + 2 ) % this.matrices.length ];
      var width = matrix0.getRowDimension();
      var height = matrix0.getColumnDimension();

      // Main loop, doesn't update cells on the edges
      for ( var i = 1; i < width - 1; i++ ) {
        for ( var j = 1; j < height - 1; j++ ) {
          var neighborSum = matrix1.get( i + 1, j ) + matrix1.get( i - 1, j ) + matrix1.get( i, j + 1 ) + matrix1.get( i, j - 1 );
          var m1ij = matrix1.get( i, j );
          var value = m1ij * 2 - matrix2.get( i, j ) + WAVE_SPEED_SQUARED * ( neighborSum + m1ij * -4 );
          matrix0.set( i, j, value );

          if ( Math.abs( value ) > LIGHT_VISIT_THRESHOLD ) {
            this.visitedMatrix.set( i, j, 1 );
          }
        }
      }

      // temporal dampen on the visible region
      this.dampHorizontalTemporal( 0, 1, matrix0, matrix2 );
      this.dampHorizontalTemporal( this.height - 1, -1, matrix0, matrix2 );
      this.dampVerticalTemporal( 0, +1, matrix0, matrix2 );
      this.dampVerticalTemporal( this.width - 1, -1, matrix0, matrix2 );

      // decay the wave outside of the visible part
      this.decayVertical( 0, +1, this.dampY / 2 );
      this.decayVertical( this.width - 1, -1, this.dampY / 2 );
      this.decayHorizontal( 0, +1, this.dampX / 2 );
      this.decayHorizontal( this.height - 1, -1, this.dampX / 2 );

      // Apply values on top of the computed lattice values so there is no noise at the point sources
      forcingFunction( this );
    }
  } );
} );<commit_msg>Removed args from forcing function<commit_after>// Copyright 2017, University of Colorado Boulder

/**
 * The lattice is a 2D grid with a value in each cell that represents the wave amplitude at that point.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var Bounds2 = require( 'DOT/Bounds2' );
  var Emitter = require( 'AXON/Emitter' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Matrix = require( 'DOT/Matrix' );
  var waveInterference = require( 'WAVE_INTERFERENCE/waveInterference' );

  // constants

  // The wave speed in the coordinate frame of the lattice, see http://www.mtnmath.com/whatth/node47.html. We tried
  // different values, but they do not have the properer emergent behavior.  WAVE_SPEED=1 propagates out as a diamond
  // rather than a circle, and WAVE_SPEED=0.1 is too slow and throws off the frequency of light.
  var WAVE_SPEED = 0.5;
  var WAVE_SPEED_SQUARED = WAVE_SPEED * WAVE_SPEED; // precompute to avoid work in the inner loop
  var NUMBER_OF_MATRICES = 3; // The algorithm we use for the discretized wave equation requires current value + 2 history points

  // This is the threshold for the wave value that determines if the light has visited.  If the value is higher,
  // it will track the wavefront of the light more accurately (and hence could be used for more accurate computation of
  // the speed of light), but will generate more artifacts in the initial wave.  If the value is lower, it will generate
  // fewer artifacts in the initial propagation, but will lead the initial wavefront by too far and make it seem like
  // light is faster than it should be measured (based on the propagation of wavefronts).
  var LIGHT_VISIT_THRESHOLD = 0.06;

  /**
   * @param {number} width - width of the lattice
   * @param {number} height - height of the lattice
   * @param {number} dampX - number of cells on the left and again on the right to use for damping
   * @param {number} dampY - number of cells on the top and again on the bottom to use for damping
   * @constructor
   */
  function Lattice( width, height, dampX, dampY ) {

    // @public (read-only) {number} - number of cells on the left and again on the right to use for damping
    this.dampX = dampX;

    // @public (read-only) {number} - number of cells on the top and again on the bottom to use for damping
    this.dampY = dampY;

    // @private {Matrix[]} - matrices for current value, previous value and value before previous
    this.matrices = [];
    for ( var i = 0; i < NUMBER_OF_MATRICES; i++ ) {
      this.matrices.push( new Matrix( width, height ) );
    }

    // @private {Matrix} - keeps track of which cells have been visited by the wave
    this.visitedMatrix = new Matrix( width, height );

    // @private {number} - indicates the current matrix. Previous matrix is one higher (with correct modulus)
    this.currentMatrixIndex = 0;

    // @public {Emitter} - sends a notification each time the lattice updates.
    this.changedEmitter = new Emitter();

    // @public (read-only) {number} - width of the lattice (includes damping regions)
    this.width = width;

    // @public (read-only) {number} - height of the lattice (includes damping regions)
    this.height = height;

    // @public {number} - Determines how far we have animated between the "last" and "current" matrices, so that we
    // can use getInterpolatedValue to update the view at 60fps even though the model is running at a slower rate
    this.interpolationRatio = 0;
  }

  waveInterference.register( 'Lattice', Lattice );

  return inherit( Object, Lattice, {

    /**
     * Gets a Bounds2 representing the visible (non-damping) region of the lattice.
     * @returns {Bounds2}
     */
    getVisibleBounds: function() {
      return new Bounds2( this.dampX, this.dampY, this.width - this.dampX, this.height - this.dampY );
    },

    /**
     * Gets a Bounds2 representing the full region of the lattice, including damping regions.
     * @returns {Bounds2}
     */
    getBounds: function() {
      return new Bounds2( 0, 0, this.width, this.height );
    },

    /**
     * Read the values on the center line of the lattice (omits the out-of-bounds damping regions), for display in the
     * WaveAreaGraphNode
     * @param {number[]} array - array to fill with the values for performance/memory, will be resized if necessary
     * @public
     */
    getCenterLineValues: function( array ) {
      var samplingWidth = this.width - this.dampX * 2;

      // Resize array if necessary
      if ( array.length !== samplingWidth ) {
        array.length = 0;
      }
      var samplingVerticalLocation = Math.round( this.height / 2 );
      for ( var i = 0; i < this.width - this.dampX * 2; i++ ) {
        array[ i ] = this.getCurrentValue( i + this.dampX, samplingVerticalLocation );
      }
      return array;
    },

    /**
     * Returns the current value in the given cell
     * @param {number} i - horizontal integer coordinate
     * @param {number} j - vertical integer coordinate
     * @returns {number}
     * @public
     */
    getCurrentValue: function( i, j ) {
      return this.matrices[ this.currentMatrixIndex ].get( i, j );
    },

    /**
     * Returns the interpolated value of the given cell
     * @param {number} i - horizontal integer coordinate
     * @param {number} j - vertical integer coordinate
     * @returns {number}
     * @public
     */
    getInterpolatedValue: function( i, j ) {
      return this.getCurrentValue( i, j ) * this.interpolationRatio + this.getLastValue( i, j ) * ( 1 - this.interpolationRatio );
    },

    /**
     * Sets the current value in the given cell
     * @param {number} i - horizontal integer coordinate
     * @param {number} j - vertical integer coordinate
     * @param {number} value
     * @public
     */
    setCurrentValue: function( i, j, value ) {
      this.matrices[ this.currentMatrixIndex ].set( i, j, value );
    },

    /**
     * Returns the previous value in the given cell
     * @param {number} i - horizontal integer coordinate
     * @param {number} j - vertical integer coordinate
     * @private
     */
    getLastValue: function( i, j ) {
      return this.matrices[ ( this.currentMatrixIndex + 1 ) % this.matrices.length ].get( i, j );
    },

    /**
     * Sets the previous value in the given cell
     * @param {number} i - horizontal integer coordinate
     * @param {number} j - vertical integer coordinate
     * @param {number} value
     * @private
     */
    setLastValue: function( i, j, value ) {
      this.matrices[ ( this.currentMatrixIndex + 1 ) % this.matrices.length ].set( i, j, value );
    },

    /**
     * Across a horizontal line, dampen the waveform based on past adjacent values.  Developed in the Java version as
     * one step in preventing artifacts near the edge of the visible part of the lattice.
     * @param {number} j - y coordinate for the lattice
     * @param {number} dj - distance above or below to look
     * @param {Matrix} matrix0 - current matrix
     * @param {Matrix} matrix2 - two time steps ago
     * @private
     */
    dampHorizontalTemporal: function( j, dj, matrix0, matrix2 ) {
      for ( var i = 0; i < this.width; i++ ) {
        matrix0.set( i, j, matrix2.get( i, j + dj ) );
      }
    },

    /**
     * Across a vertical line, dampen the waveform based on past adjacent values.  Developed in the Java version as
     * one step in preventing artifacts near the edge of the visible part of the lattice.
     * @param {number} i - x coordinate for the lattice
     * @param {number} di - distance left or right to look
     * @param {Matrix} matrix0 - current matrix
     * @param {Matrix} matrix2 - two time steps ago
     * @private
     */
    dampVerticalTemporal: function( i, di, matrix0, matrix2 ) {
      for ( var j = 0; j < this.height; j++ ) {
        matrix0.set( i, j, matrix2.get( i + di, j ) );
      }
    },

    /**
     * Exponential decay in a vertical band to prevent reflections or artifacts from the sides of the lattice.
     * @param {number} i0 - x coordinate of the damping region
     * @param {number} sign - +1 or -1 depending on which way the damping moves
     * @param {number} width - width of the damping region
     * @private
     */
    decayVertical: function( i0, sign, width ) {
      for ( var j = 0; j < this.height; j++ ) {
        for ( var step = 0; step < width; step++ ) {
          var distFromDampBoundary = width - step;
          var damp = this.getDamp( distFromDampBoundary );
          var i = i0 + step * sign;
          this.setCurrentValue( i, j, this.getCurrentValue( i, j ) * damp );
          this.setLastValue( i, j, this.getLastValue( i, j ) * damp );
        }
      }
    },

    /**
     * Exponential decay in a horizontal band to prevent reflections or artifacts from the sides of the lattice.
     * @param {number} j0 - y coordinate of the damping region
     * @param {number} sign - +1 or -1 depending on which way the damping moves
     * @param {number} height - height of the damping region
     * @private
     */
    decayHorizontal: function( j0, sign, height ) {
      for ( var i = 0; i < this.width; i++ ) {
        for ( var step = 0; step < height; step++ ) {
          var distFromDampBoundary = height - step;
          var damp = this.getDamp( distFromDampBoundary );
          var j = j0 + step * sign;
          this.setCurrentValue( i, j, this.getCurrentValue( i, j ) * damp );
          this.setLastValue( i, j, this.getLastValue( i, j ) * damp );
        }
      }
    },

    /**
     * Determines whether the incoming wave has reached the cell.
     * @param {number} i - horizontal coordinate to check
     * @param {number} j - vertical coordinate to check
     * @returns {boolean}
     */
    hasCellBeenVisited: function( i, j ) {
      return this.visitedMatrix.get( i, j ) === 1;
    },

    /**
     * Damp more aggressively further from the edge of the visible lattice
     * @param {number} depthInDampRegion - number of cells into the damping region
     * @returns {number} scale factor for dampening the wave
     * @private
     */
    getDamp: function( depthInDampRegion ) {
      return ( 1 - depthInDampRegion * 0.0001 );
    },

    /**
     * Resets all of the wave values to 0
     * @public
     */
    clear: function() {
      for ( var i = 0; i < this.matrices.length; i++ ) {
        this.matrices[ i ].timesEquals( 0 );
      }

      this.visitedMatrix.timesEquals( 0 );
    },

    /**
     * Gets the values on the right hand side of the wave (before the damping region), for determining intensity.
     * @returns {number[]}
     * @public
     */
    getOutputColumn: function() {

      // TODO(performance): garbage-free form?  Would require preallocating the entire intensitySample matrix
      var column = [];
      for ( var j = this.dampY; j < this.height - this.dampY; j++ ) {
        column.push( this.getCurrentValue( this.width - this.dampX - 1, j ) );
      }
      return column;
    },

    /**
     * Propagates the wave by one step.  This is a discrete algorithm and cannot use dt.
     * @param {function} forcingFunction - sets values before and after the wave equation
     * @public
     */
    step: function( forcingFunction ) {

      // Apply values before lattice step so the values will be used to propagate
      forcingFunction();

      this.currentMatrixIndex = ( this.currentMatrixIndex - 1 + this.matrices.length ) % this.matrices.length;

      var matrix0 = this.matrices[ ( this.currentMatrixIndex + 0 ) % this.matrices.length ];
      var matrix1 = this.matrices[ ( this.currentMatrixIndex + 1 ) % this.matrices.length ];
      var matrix2 = this.matrices[ ( this.currentMatrixIndex + 2 ) % this.matrices.length ];
      var width = matrix0.getRowDimension();
      var height = matrix0.getColumnDimension();

      // Main loop, doesn't update cells on the edges
      for ( var i = 1; i < width - 1; i++ ) {
        for ( var j = 1; j < height - 1; j++ ) {
          var neighborSum = matrix1.get( i + 1, j ) + matrix1.get( i - 1, j ) + matrix1.get( i, j + 1 ) + matrix1.get( i, j - 1 );
          var m1ij = matrix1.get( i, j );
          var value = m1ij * 2 - matrix2.get( i, j ) + WAVE_SPEED_SQUARED * ( neighborSum + m1ij * -4 );
          matrix0.set( i, j, value );

          if ( Math.abs( value ) > LIGHT_VISIT_THRESHOLD ) {
            this.visitedMatrix.set( i, j, 1 );
          }
        }
      }

      // temporal dampen on the visible region
      this.dampHorizontalTemporal( 0, 1, matrix0, matrix2 );
      this.dampHorizontalTemporal( this.height - 1, -1, matrix0, matrix2 );
      this.dampVerticalTemporal( 0, +1, matrix0, matrix2 );
      this.dampVerticalTemporal( this.width - 1, -1, matrix0, matrix2 );

      // decay the wave outside of the visible part
      this.decayVertical( 0, +1, this.dampY / 2 );
      this.decayVertical( this.width - 1, -1, this.dampY / 2 );
      this.decayHorizontal( 0, +1, this.dampX / 2 );
      this.decayHorizontal( this.height - 1, -1, this.dampX / 2 );

      // Apply values on top of the computed lattice values so there is no noise at the point sources
      forcingFunction();
    }
  } );
} );<|endoftext|>


<commit_before>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2017, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

var _ = require('lodash');

var Categorization = function(bgClasses){
  var classes = _.cloneDeep(bgClasses);
  var defaults = {
    'very-low': { boundary: 55 },
    low: { boundary: 70 },
    target: { boundary: 180 },
    high: { boundary: 300 },
  };
  _.defaults(classes, defaults);
  return function(d) {
    if (d.value < classes['very-low'].boundary) {
      return 'verylow';
    }
    else if (d.value >= classes['very-low'].boundary &&
      d.value < classes.low.boundary) {
      return 'low';
    }
    else if (d.value >= classes.low.boundary &&
      d.value <= classes.target.boundary) {
      return 'target';
    }
    else if (d.value > classes.target.boundary &&
      d.value <= classes.high.boundary) {
      return 'high';
    }
    else if (d.value > classes.high.boundary) {
      return 'veryhigh';
    }
  };
};

module.exports = Categorization;
<commit_msg>naming things<commit_after>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2017, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

var _ = require('lodash');

var Categorizer = function(bgClasses){
  var classes = _.cloneDeep(bgClasses);
  var defaults = {
    'very-low': { boundary: 55 },
    low: { boundary: 70 },
    target: { boundary: 180 },
    high: { boundary: 300 },
  };
  _.defaults(classes, defaults);
  return function(d) {
    if (d.value < classes['very-low'].boundary) {
      return 'verylow';
    }
    else if (d.value >= classes['very-low'].boundary &&
      d.value < classes.low.boundary) {
      return 'low';
    }
    else if (d.value >= classes.low.boundary &&
      d.value <= classes.target.boundary) {
      return 'target';
    }
    else if (d.value > classes.target.boundary &&
      d.value <= classes.high.boundary) {
      return 'high';
    }
    else if (d.value > classes.high.boundary) {
      return 'veryhigh';
    }
  };
};

module.exports = Categorizer;
<|endoftext|>