<commit_before>//----------------------------------------------------------------------------
//  Copyright (C) 2013 The IPython Development Team
//
//  Distributed under the terms of the BSD License.  The full license is in
//  the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------

//============================================================================
// Base Widget Model and View classes
//============================================================================

/**
 * @module IPython
 * @namespace IPython
 **/

define(["notebook/js/widgetmanager",
        "underscore",
        "backbone"],
function(WidgetManager, _, Backbone){

    var WidgetModel = Backbone.Model.extend({
        constructor: function (widget_manager, model_id, comm) {
            // Constructor
            //
            // Creates a WidgetModel instance.
            //
            // Parameters
            // ----------
            // widget_manager : WidgetManager instance
            // model_id : string
            //      An ID unique to this model.
            // comm : Comm instance (optional)
            this.widget_manager = widget_manager;
            this.pending_msgs = 0;
            this.msg_throttle = 3;
            this.msg_buffer = null;
            this.key_value_lock = null;
            this.id = model_id;
            this.views = [];

            if (comm !== undefined) {
                // Remember comm associated with the model.
                this.comm = comm;
                comm.model = this;

                // Hook comm messages up to model.
                comm.on_close($.proxy(this._handle_comm_closed, this));
                comm.on_msg($.proxy(this._handle_comm_msg, this));
            }
            return Backbone.Model.apply(this);
        },

        send: function (content, callbacks) {
            // Send a custom msg over the comm.
            if (this.comm !== undefined) {
                var data = {method: 'custom', content: content};
                this.comm.send(data, callbacks);
                this.pending_msgs++;
            }
        },

        _handle_comm_closed: function (msg) {
            // Handle when a widget is closed.
            this.trigger('comm:close');
            delete this.comm.model; // Delete ref so GC will collect widget model.
            delete this.comm;
            delete this.model_id; // Delete id from model so widget manager cleans up.
            _.each(this.views, function(view, i) {
                view.remove();
            });
        },

        _handle_comm_msg: function (msg) {
            // Handle incoming comm msg.
            var method = msg.content.data.method;
            switch (method) {
                case 'update':
                    this.apply_update(msg.content.data.state);
                    break;
                case 'custom':
                    this.trigger('msg:custom', msg.content.data.content);
                    break;
                case 'display':
                    this.widget_manager.display_view(msg, this);
                    break;
            }
        },

        apply_update: function (state) {
            // Handle when a widget is updated via the python side.
            var that = this;
            _.each(state, function(value, key) {
                that.key_value_lock = [key, value];
                try {
                    that.set(key, that._unpack_models(value));
                } finally {
                    that.key_value_lock = null;
                }
            });
        },

        _handle_status: function (msg, callbacks) {
            // Handle status msgs.

            // execution_state : ('busy', 'idle', 'starting')
            if (this.comm !== undefined) {
                if (msg.content.execution_state ==='idle') {
                    // Send buffer if this message caused another message to be
                    // throttled.
                    if (this.msg_buffer !== null &&
                        this.msg_throttle === this.pending_msgs) {
                        var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
                        this.comm.send(data, callbacks);
                        this.msg_buffer = null;
                    } else {
                        --this.pending_msgs;
                    }
                }
            }
        },

        callbacks: function(view) {
            // Create msg callbacks for a comm msg.
            var callbacks = this.widget_manager.callbacks(view);

            if (callbacks.iopub === undefined) {
                callbacks.iopub = {};
            }

            var that = this;
            callbacks.iopub.status = function (msg) {
                that._handle_status(msg, callbacks);
            };
            return callbacks;
        },

        sync: function (method, model, options) {
            // Handle sync to the back-end.  Called when a model.save() is called.

            // Make sure a comm exists.
            var error = options.error || function() {
                console.error('Backbone sync error:', arguments);
            };
            if (this.comm === undefined) {
                error();
                return false;
            }

            // Delete any key value pairs that the back-end already knows about.
            var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
            if (this.key_value_lock !== null) {
                var key = this.key_value_lock[0];
                var value = this.key_value_lock[1];
                if (attrs[key] === value) {
                    delete attrs[key];
                }
            }

            // Only sync if there are attributes to send to the back-end.
            if (_.size(attrs) > 0) {

                // If this message was sent via backbone itself, it will not
                // have any callbacks.  It's important that we create callbacks
                // so we can listen for status messages, etc...
                var callbacks = options.callbacks || this.callbacks();

                // Check throttle.
                if (this.pending_msgs >= this.msg_throttle) {
                    // The throttle has been exceeded, buffer the current msg so
                    // it can be sent once the kernel has finished processing
                    // some of the existing messages.

                    // Combine updates if it is a 'patch' sync, otherwise replace updates
                    switch (method) {
                        case 'patch':
                            this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
                            break;
                        case 'update':
                        case 'create':
                            this.msg_buffer = attrs;
                            break;
                        default:
                            error();
                            return false;
                    }
                    this.msg_buffer_callbacks = callbacks;

                } else {
                    // We haven't exceeded the throttle, send the message like
                    // normal.
                    var data = {method: 'backbone', sync_data: attrs};
                    this.comm.send(data, callbacks);
                    this.pending_msgs++;
                }
            }
            // Since the comm is a one-way communication, assume the message
            // arrived.  Don't call success since we don't have a model back from the server
            // this means we miss out on the 'sync' event.
        },

        save_changes: function(callbacks) {
            // Push this model's state to the back-end
            //
            // This invokes a Backbone.Sync.
            this.save(this.changedAttributes(), {patch: true, callbacks: callbacks});
        },

        _pack_models: function(value) {
            // Replace models with model ids recursively.
            if (value instanceof Backbone.Model) {
                return value.id;

            } else if ($.isArray(value)) {
                var packed = [];
                var that = this;
                _.each(value, function(sub_value, key) {
                    packed.push(that._pack_models(sub_value));
                });
                return packed;

            } else if (value instanceof Object) {
                var packed = {};
                var that = this;
                _.each(value, function(sub_value, key) {
                    packed[key] = that._pack_models(sub_value);
                });
                return packed;

            } else {
                return value;
            }
        },

        _unpack_models: function(value) {
            // Replace model ids with models recursively.
            if ($.isArray(value)) {
                var unpacked = [];
                var that = this;
                _.each(value, function(sub_value, key) {
                    unpacked.push(that._unpack_models(sub_value));
                });
                return unpacked;

            } else if (value instanceof Object) {
                var unpacked = {};
                var that = this;
                _.each(value, function(sub_value, key) {
                    unpacked[key] = that._unpack_models(sub_value);
                });
                return unpacked;

            } else {
                var model = this.widget_manager.get_model(value);
                if (model) {
                    return model;
                } else {
                    return value;
                }
            }
        },

    });
    WidgetManager.register_widget_model('WidgetModel', WidgetModel);

    var WidgetView = Backbone.View.extend({
        initialize: function(parameters) {
            // Public constructor.
            this.model.on('change',this.update,this);
            this.options = parameters.options;
            this.child_views = [];
            this.model.views.push(this);
        },

        update: function(){
            // Triggered on model change.
            //
            // Update view to be consistent with this.model
        },

        create_child_view: function(child_model, options) {
            // Create and return a child view.
            //
            // -given a model and (optionally) a view name if the view name is
            // not given, it defaults to the model's default view attribute.

            // TODO: this is hacky, and makes the view depend on this cell attribute and widget manager behavior
            // it would be great to have the widget manager add the cell metadata
            // to the subview without having to add it here.
            var child_view = this.model.widget_manager.create_view(child_model, options || {}, this);
            this.child_views[child_model.id] = child_view;
            return child_view;
        },

        delete_child_view: function(child_model, options) {
            // Delete a child view that was previously created using create_child_view.
            var view = this.child_views[child_model.id];
            if (view !== undefined) {
                delete this.child_views[child_model.id];
                view.remove();
            }
        },

        do_diff: function(old_list, new_list, removed_callback, added_callback) {
            // Difference a changed list and call remove and add callbacks for
            // each removed and added item in the new list.
            //
            // Parameters
            // ----------
            // old_list : array
            // new_list : array
            // removed_callback : Callback(item)
            //      Callback that is called for each item removed.
            // added_callback : Callback(item)
            //      Callback that is called for each item added.

            // removed items
            _.each(_.difference(old_list, new_list), function(item, index, list) {
                removed_callback(item);
            }, this);

            // added items
            _.each(_.difference(new_list, old_list), function(item, index, list) {
                added_callback(item);
            }, this);
        },

        callbacks: function(){
            // Create msg callbacks for a comm msg.
            return this.model.callbacks(this);
        },

        render: function(){
            // Render the view.
            //
            // By default, this is only called the first time the view is created
        },

        send: function (content) {
            // Send a custom msg associated with this view.
            this.model.send(content, this.callbacks());
        },

        touch: function () {
            this.model.save_changes(this.callbacks());
        },
    });

    var DOMWidgetView = WidgetView.extend({
        initialize: function (options) {
            // Public constructor

            // In the future we may want to make changes more granular
            // (e.g., trigger on visible:change).
            this.model.on('change', this.update, this);
            this.model.on('msg:custom', this.on_msg, this);
            DOMWidgetView.__super__.initialize.apply(this, arguments);
        },

        on_msg: function(msg) {
            // Handle DOM specific msgs.
            switch(msg.msg_type) {
                case 'add_class':
                    this.add_class(msg.selector, msg.class_list);
                    break;
                case 'remove_class':
                    this.remove_class(msg.selector, msg.class_list);
                    break;
            }
        },

        add_class: function (selector, class_list) {
            // Add a DOM class to an element.
            this._get_selector_element(selector).addClass(class_list);
        },

        remove_class: function (selector, class_list) {
            // Remove a DOM class from an element.
            this._get_selector_element(selector).removeClass(class_list);
        },

        update: function () {
            // Update the contents of this view
            //
            // Called when the model is changed.  The model may have been
            // changed by another view or by a state update from the back-end.
            //      The very first update seems to happen before the element is
            // finished rendering so we use setTimeout to give the element time
            // to render
            var e = this.$el;
            var visible = this.model.get('visible');
            setTimeout(function() {e.toggle(visible);},0);

            var css = this.model.get('_css');
            if (css === undefined) {return;}
            var that = this;
            _.each(css, function(css_traits, selector){
                // Apply the css traits to all elements that match the selector.
                var elements = that._get_selector_element(selector);
                if (elements.length > 0) {
                    _.each(css_traits, function(css_value, css_key){
                        elements.css(css_key, css_value);
                    });
                }
            });
        },

        _get_selector_element: function (selector) {
            // Get the elements via the css selector.

            // If the selector is blank, apply the style to the $el_to_style
            // element.  If the $el_to_style element is not defined, use apply
            // the style to the view's element.
            var elements;
            if (!selector) {
                if (this.$el_to_style === undefined) {
                    elements = this.$el;
                } else {
                    elements = this.$el_to_style;
                }
            } else {
                elements = this.$el.find(selector);
            }
            return elements;
        },
    });

    IPython.WidgetModel = WidgetModel;
    IPython.WidgetView = WidgetView;
    IPython.DOMWidgetView = DOMWidgetView;

    // Pass through WidgetManager namespace.
    return WidgetManager;
});
<commit_msg>Let backbone get changed attrs<commit_after>//----------------------------------------------------------------------------
//  Copyright (C) 2013 The IPython Development Team
//
//  Distributed under the terms of the BSD License.  The full license is in
//  the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------

//============================================================================
// Base Widget Model and View classes
//============================================================================

/**
 * @module IPython
 * @namespace IPython
 **/

define(["notebook/js/widgetmanager",
        "underscore",
        "backbone"],
function(WidgetManager, _, Backbone){

    var WidgetModel = Backbone.Model.extend({
        constructor: function (widget_manager, model_id, comm) {
            // Constructor
            //
            // Creates a WidgetModel instance.
            //
            // Parameters
            // ----------
            // widget_manager : WidgetManager instance
            // model_id : string
            //      An ID unique to this model.
            // comm : Comm instance (optional)
            this.widget_manager = widget_manager;
            this.pending_msgs = 0;
            this.msg_throttle = 3;
            this.msg_buffer = null;
            this.key_value_lock = null;
            this.id = model_id;
            this.views = [];

            if (comm !== undefined) {
                // Remember comm associated with the model.
                this.comm = comm;
                comm.model = this;

                // Hook comm messages up to model.
                comm.on_close($.proxy(this._handle_comm_closed, this));
                comm.on_msg($.proxy(this._handle_comm_msg, this));
            }
            return Backbone.Model.apply(this);
        },

        send: function (content, callbacks) {
            // Send a custom msg over the comm.
            if (this.comm !== undefined) {
                var data = {method: 'custom', content: content};
                this.comm.send(data, callbacks);
                this.pending_msgs++;
            }
        },

        _handle_comm_closed: function (msg) {
            // Handle when a widget is closed.
            this.trigger('comm:close');
            delete this.comm.model; // Delete ref so GC will collect widget model.
            delete this.comm;
            delete this.model_id; // Delete id from model so widget manager cleans up.
            _.each(this.views, function(view, i) {
                view.remove();
            });
        },

        _handle_comm_msg: function (msg) {
            // Handle incoming comm msg.
            var method = msg.content.data.method;
            switch (method) {
                case 'update':
                    this.apply_update(msg.content.data.state);
                    break;
                case 'custom':
                    this.trigger('msg:custom', msg.content.data.content);
                    break;
                case 'display':
                    this.widget_manager.display_view(msg, this);
                    break;
            }
        },

        apply_update: function (state) {
            // Handle when a widget is updated via the python side.
            var that = this;
            _.each(state, function(value, key) {
                that.key_value_lock = [key, value];
                try {
                    that.set(key, that._unpack_models(value));
                } finally {
                    that.key_value_lock = null;
                }
            });
        },

        _handle_status: function (msg, callbacks) {
            // Handle status msgs.

            // execution_state : ('busy', 'idle', 'starting')
            if (this.comm !== undefined) {
                if (msg.content.execution_state ==='idle') {
                    // Send buffer if this message caused another message to be
                    // throttled.
                    if (this.msg_buffer !== null &&
                        this.msg_throttle === this.pending_msgs) {
                        var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
                        this.comm.send(data, callbacks);
                        this.msg_buffer = null;
                    } else {
                        --this.pending_msgs;
                    }
                }
            }
        },

        callbacks: function(view) {
            // Create msg callbacks for a comm msg.
            var callbacks = this.widget_manager.callbacks(view);

            if (callbacks.iopub === undefined) {
                callbacks.iopub = {};
            }

            var that = this;
            callbacks.iopub.status = function (msg) {
                that._handle_status(msg, callbacks);
            };
            return callbacks;
        },

        sync: function (method, model, options) {
            // Handle sync to the back-end.  Called when a model.save() is called.

            // Make sure a comm exists.
            var error = options.error || function() {
                console.error('Backbone sync error:', arguments);
            };
            if (this.comm === undefined) {
                error();
                return false;
            }

            // Delete any key value pairs that the back-end already knows about.
            var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
            if (this.key_value_lock !== null) {
                var key = this.key_value_lock[0];
                var value = this.key_value_lock[1];
                if (attrs[key] === value) {
                    delete attrs[key];
                }
            }

            // Only sync if there are attributes to send to the back-end.
            if (_.size(attrs) > 0) {

                // If this message was sent via backbone itself, it will not
                // have any callbacks.  It's important that we create callbacks
                // so we can listen for status messages, etc...
                var callbacks = options.callbacks || this.callbacks();

                // Check throttle.
                if (this.pending_msgs >= this.msg_throttle) {
                    // The throttle has been exceeded, buffer the current msg so
                    // it can be sent once the kernel has finished processing
                    // some of the existing messages.

                    // Combine updates if it is a 'patch' sync, otherwise replace updates
                    switch (method) {
                        case 'patch':
                            this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
                            break;
                        case 'update':
                        case 'create':
                            this.msg_buffer = attrs;
                            break;
                        default:
                            error();
                            return false;
                    }
                    this.msg_buffer_callbacks = callbacks;

                } else {
                    // We haven't exceeded the throttle, send the message like
                    // normal.
                    var data = {method: 'backbone', sync_data: attrs};
                    this.comm.send(data, callbacks);
                    this.pending_msgs++;
                }
            }
            // Since the comm is a one-way communication, assume the message
            // arrived.  Don't call success since we don't have a model back from the server
            // this means we miss out on the 'sync' event.
        },

        save_changes: function(callbacks) {
            // Push this model's state to the back-end
            //
            // This invokes a Backbone.Sync.
            this.save({patch: true, callbacks: callbacks});
        },

        _pack_models: function(value) {
            // Replace models with model ids recursively.
            if (value instanceof Backbone.Model) {
                return value.id;

            } else if ($.isArray(value)) {
                var packed = [];
                var that = this;
                _.each(value, function(sub_value, key) {
                    packed.push(that._pack_models(sub_value));
                });
                return packed;

            } else if (value instanceof Object) {
                var packed = {};
                var that = this;
                _.each(value, function(sub_value, key) {
                    packed[key] = that._pack_models(sub_value);
                });
                return packed;

            } else {
                return value;
            }
        },

        _unpack_models: function(value) {
            // Replace model ids with models recursively.
            if ($.isArray(value)) {
                var unpacked = [];
                var that = this;
                _.each(value, function(sub_value, key) {
                    unpacked.push(that._unpack_models(sub_value));
                });
                return unpacked;

            } else if (value instanceof Object) {
                var unpacked = {};
                var that = this;
                _.each(value, function(sub_value, key) {
                    unpacked[key] = that._unpack_models(sub_value);
                });
                return unpacked;

            } else {
                var model = this.widget_manager.get_model(value);
                if (model) {
                    return model;
                } else {
                    return value;
                }
            }
        },

    });
    WidgetManager.register_widget_model('WidgetModel', WidgetModel);

    var WidgetView = Backbone.View.extend({
        initialize: function(parameters) {
            // Public constructor.
            this.model.on('change',this.update,this);
            this.options = parameters.options;
            this.child_views = [];
            this.model.views.push(this);
        },

        update: function(){
            // Triggered on model change.
            //
            // Update view to be consistent with this.model
        },

        create_child_view: function(child_model, options) {
            // Create and return a child view.
            //
            // -given a model and (optionally) a view name if the view name is
            // not given, it defaults to the model's default view attribute.

            // TODO: this is hacky, and makes the view depend on this cell attribute and widget manager behavior
            // it would be great to have the widget manager add the cell metadata
            // to the subview without having to add it here.
            var child_view = this.model.widget_manager.create_view(child_model, options || {}, this);
            this.child_views[child_model.id] = child_view;
            return child_view;
        },

        delete_child_view: function(child_model, options) {
            // Delete a child view that was previously created using create_child_view.
            var view = this.child_views[child_model.id];
            if (view !== undefined) {
                delete this.child_views[child_model.id];
                view.remove();
            }
        },

        do_diff: function(old_list, new_list, removed_callback, added_callback) {
            // Difference a changed list and call remove and add callbacks for
            // each removed and added item in the new list.
            //
            // Parameters
            // ----------
            // old_list : array
            // new_list : array
            // removed_callback : Callback(item)
            //      Callback that is called for each item removed.
            // added_callback : Callback(item)
            //      Callback that is called for each item added.

            // removed items
            _.each(_.difference(old_list, new_list), function(item, index, list) {
                removed_callback(item);
            }, this);

            // added items
            _.each(_.difference(new_list, old_list), function(item, index, list) {
                added_callback(item);
            }, this);
        },

        callbacks: function(){
            // Create msg callbacks for a comm msg.
            return this.model.callbacks(this);
        },

        render: function(){
            // Render the view.
            //
            // By default, this is only called the first time the view is created
        },

        send: function (content) {
            // Send a custom msg associated with this view.
            this.model.send(content, this.callbacks());
        },

        touch: function () {
            this.model.save_changes(this.callbacks());
        },
    });

    var DOMWidgetView = WidgetView.extend({
        initialize: function (options) {
            // Public constructor

            // In the future we may want to make changes more granular
            // (e.g., trigger on visible:change).
            this.model.on('change', this.update, this);
            this.model.on('msg:custom', this.on_msg, this);
            DOMWidgetView.__super__.initialize.apply(this, arguments);
        },

        on_msg: function(msg) {
            // Handle DOM specific msgs.
            switch(msg.msg_type) {
                case 'add_class':
                    this.add_class(msg.selector, msg.class_list);
                    break;
                case 'remove_class':
                    this.remove_class(msg.selector, msg.class_list);
                    break;
            }
        },

        add_class: function (selector, class_list) {
            // Add a DOM class to an element.
            this._get_selector_element(selector).addClass(class_list);
        },

        remove_class: function (selector, class_list) {
            // Remove a DOM class from an element.
            this._get_selector_element(selector).removeClass(class_list);
        },

        update: function () {
            // Update the contents of this view
            //
            // Called when the model is changed.  The model may have been
            // changed by another view or by a state update from the back-end.
            //      The very first update seems to happen before the element is
            // finished rendering so we use setTimeout to give the element time
            // to render
            var e = this.$el;
            var visible = this.model.get('visible');
            setTimeout(function() {e.toggle(visible);},0);

            var css = this.model.get('_css');
            if (css === undefined) {return;}
            var that = this;
            _.each(css, function(css_traits, selector){
                // Apply the css traits to all elements that match the selector.
                var elements = that._get_selector_element(selector);
                if (elements.length > 0) {
                    _.each(css_traits, function(css_value, css_key){
                        elements.css(css_key, css_value);
                    });
                }
            });
        },

        _get_selector_element: function (selector) {
            // Get the elements via the css selector.

            // If the selector is blank, apply the style to the $el_to_style
            // element.  If the $el_to_style element is not defined, use apply
            // the style to the view's element.
            var elements;
            if (!selector) {
                if (this.$el_to_style === undefined) {
                    elements = this.$el;
                } else {
                    elements = this.$el_to_style;
                }
            } else {
                elements = this.$el.find(selector);
            }
            return elements;
        },
    });

    IPython.WidgetModel = WidgetModel;
    IPython.WidgetView = WidgetView;
    IPython.DOMWidgetView = DOMWidgetView;

    // Pass through WidgetManager namespace.
    return WidgetManager;
});
<|endoftext|>


<commit_before>// STcommon.js : Show Timer common JavaScript
// This file should be included first, before module-specific JS.
//
// Example:
// <script type="text/javascript" src="STcommon.js"></script>
// <script type="text/javascript" src="edit_a_break.js"></script>
//
// This contains functions and variables used in all modules of
// the Show Timer.

var dbug = 3;

function get_show_start_hour_offset() {

    // This returns the number of hours difference between the
    // computer's configured local timezone and US/Eastern.  This
    // would depend on the browswer's implementatin of the Date
    // object's getTimezoneOffset() method.  US/Eastern is 5 hours
    // (300 minutes) behind UTC in January, and during daylight
    // saviing time (DST), 240 minutes.  So for example, if the
    // computer is configured for US/Pacific time during DST,
    // getTimezoneOffset() will return 420, therefore 240-420 = -180,
    // or 3 hours west of EDT.  For the example of "The Tech Guy,"
    // this would be added to 14 to produce 11, or 11 AM, which is the
    // hour of the beginning of the show in Petaluma.

    var now = new Date();
    var nowOff = now.getTimezoneOffset();
    now.setMonth(0);
    var JanOff = now.getTimezoneOffset();
    var EToff = 300;
    var localDiff;

    if ( nowOff !== JanOff ) {
	// We're under daylight saving time
	EToff = 240;
    }

    localDiff = EToff - nowOff;

    return localDiff / 60;
}

// symbolic states of the show
const BEFORE_SHOW = 0,
      BUMP_IN = 1, // technically ON_AIR, but while bumper music is playing
      ON_AIR = 2, // talent should be talking
      TIME_SHORT = 3, // break/end coming soon
      TIME_VERY_SHORT = 4, // break/end coming REALLY soon
      IN_BREAK = 5, // ad should be playing
      BUMP_SOON = 6, // ad still playing, but BUMP_IN approaching
      BUMP_VERY_SOON = 7, // ad still playing, but very near to BUMP_IN
      SHOW_DONE = 8;

// You could think of "after show" as "before show", as in, before
// the next show.  But SHOW_DONE marks the end of the precomputed
// events table.

function dbg(lvl, msg) {
    if ( lvl >= dbug ) {
	console.log(lvl+": "+msg);
    }
    return true;
}

function zeropad(num, places) {

    // This takes "num" and pads with zeroes so that the result ends
    // up being "places" digits.  For example, positive seconds less
    // than 10 are typically preceeded by ":0".

    var s = num.toString();
    var i;

    while ( s.length < places ) {
	s = "0" + s;
    }

    return s;
}

var brk;  // the list of breaks

function load_breaks(profname) {

    // Load the breaks list from the localStorage key named
    // "profname" and return it.  Historical note: this used
    // to append "_breaks" to the key name, but then that did
    // not allow for the idea of a profile backup name.

    var breaktab = try_load_breaks(profname);

    if ( breaktab !== false ) {
	return breaktab;
    }

    if ( Array.isArray(defaultbrk) ) {
	console.warn('Tried to load breaks "'+profname+'" but instead returning default array of breaks.');
	return defaultbrk;
    }

    return false;
}

function try_load_breaks(profname) {

    // This does the actual work of attempting to load the profile.
    // This was refactored because there are now two ways this can
    // fail to load the profile (return false), and I wanted an idea
    // of a built-in default breaks list.  So the "API" as it were
    // calls "load_breaks()" and if we return false here, it sees if
    // there exists a "defaultbrk" array and returns that.

    var breaklist = new Array();
    var breakListStr;

    try {
	breakListStr = localStorage.getItem(profname);
    } catch (err) {
	console.warn("could not access "+profname+" in localStorage: "+err);
	return false;
    }

    breaklist = JSON.parse(breakListStr);
    if ( ! breaklist ) {
	console.warn("load_breaks("+profname+"): came up empty.");
	return false;
    }

    // Just a historical note: The .begin and .end used to be Date()
    // objects, and of course, JSON records the date as a string
    // instead of an object.  Therefore, there USED TO BE a loop here
    // recreating objects from strings.  However, it did not seem
    // useful to keep them as objects, and instead they're offsets in
    // seconds from the start of the show.

    return breaklist;
}

function break_cmp(obja, objb) {
    // When sorting the brk array (the break descriptions), this
    // provides the comparison function to the .sort() method.
    return obja.begin - objb.begin;
}

function secs2minsec(insecs) {

    // Form a minutes:seconds string from "insecs" number of seconds,
    // if possible. This is visually nicer for break lengths than full
    // hh:mm:ss.  If the hours: portion is nonzero, return it.

    var str = "";
    var hrs;

    // Centralize this conversion by calling the hms converter (JUST
    // in case the format changes or a bug is discovered).  It is
    // admittedly a little inefficient in terms of computation.

    str = secs2hmsStr(insecs);
    hrs = str.substring(0,2);
    if ( hrs === "00" ) {
	// drop the "00:"
	str = str.substring(3);
	if ( str.charAt(0) === "0" ) {
	    // drop the leading zero
	    str = str.substring(1);
	}
    }

    return str;
}

function secs2hmsStr(insecs) {

    // form an hours, minutes, seconds string from "insecs" number of seconds

    var str = "";
    var t = insecs;
    var secs,mins,hrs;

    secs = t % 60;
    t = Math.floor(t/60);
    mins = t % 60;
    t = Math.floor(t/60);
    str += zeropad(t, 2)
    str += ":";
    str += zeropad(mins, 2)
    str += ":";
    str += zeropad(secs, 2)

    return str;
}

function hms2secs(str) {

    // Take string "str" which is in the format hours:minutes:seconds and
    // convert that to a number of seconds.  Zero, one, or two colons are
    // allowed.  Any characters other than the digits and a colon make this
    // function return false.  Also if minutes or seconds are greater than
    // 59 will cause a return of false.  A colon followed by nothing
    // assumes zero, so for example "4:" means four minutes.

    var secs = 0;
    var c, l, i;
    var coloncnt = 0;
    var val; // intermediate value while walking along the string
    var hms = new Array(); // hours, minutes, seconds as accumulated

    dbg(0, "  hms2secs(): converting "+str);

    val = 0;
    l = str.length;
    for ( i = 0; i < l; i++ ) {
	c = str.charAt(i);
	dbg(0, "    considering "+c+" at "+i+" ("+val+")");
	if ( c === ":" ) {
	    if ( ++coloncnt > 2 ) {
		dbg(1, "    too many colons, "+coloncnt);
		return false;
	    }
	    dbg(0, "      processing colon, before "+val);
	    hms.push(val);
	    val = 0;
	} else if ( c < "0" || c > "9" ) {
	    dbg(1, "    char "+c+" not in range");
	    return false;
	} else {
	    // add the next digit by shifting the <result so
	    // far> by one decimal place and add current one
	    val *= 10;
	    val += parseInt(c);
	}
    }
    hms.push(val);

    while ( ++coloncnt < 3 ) {
	hms.unshift(0);
    }

    dbg(1, "  ready with hms len "+hms.length+" and hms "+hms);
    if ( hms[0] > 23 || hms[1] > 59 || hms[2] > 59 ) {
	dbg(1, "    something was too big");
	return false;
    }

    secs = hms[0] * 3600 + hms[1] * 60 + hms[2];

    return secs;
}

function state2str(st) {
    // Given state "st", what does the const look
    // like in the source?
    // Returns "unknown" if the number is out of range.
    const state = [ "BEFORE_SHOW",
		    "BUMP_IN",
		    "ON_AIR",
		    "TIME_SHORT",
		    "TIME_VERY_SHORT",
		    "IN_BREAK",
		    "BUMP_SOON",
		    "BUMP_VERY_SOON",
		    "SHOW_DONE" ];

    if ( st < BEFORE_SHOW ||
	 st > SHOW_DONE ) {
	return "unknown";
    } else {
	return state[st];
    }
}

function st2col(st) {

    // For state "st", which member of st.color should be
    // used for coloration of the HTML node?  If the state
    // is out of range, let CSS take over by returning "".

    const statetab =
	  [ "revertCSS",
	    "onAir",
	    "onAir",
	    "soon",
	    "verysoon",
	    "revertCSS",
	    "soon",
	    "verysoon",
	    "revertCSS"
	  ];

    if ( st < BEFORE_SHOW ||
	 st > SHOW_DONE ) {
	return "revertCSS";
    } else {
	return statetab[st];
    }
}

function find_all_profiles() {

    var i, l, k;
    var seen = new Array();
    var prof;
    var optproto = document.createElement("option");
    var opt;

    var brksidx, klen;

    // empty any existing options
    while ( (opt = profiles.firstChild) !== null ) {
	profiles.removeChild(opt);
    }

    l = localStorage.length;
    for ( i = 0; i < l; i++ ) {
	k = localStorage.key(i);
	klen = k.length;
	dbg(0, "got key "+k+" length "+klen);
	prof = k.replace(/_(break|params)(_bak[0-9]*)?$/, "");
	if ( ! seen.includes(prof) ) {
	    dbg(0,"*** so added from key "+k);
	    seen.push(prof);
	    opt = optproto.cloneNode(false);
	    opt.textContent = prof;
	    profiles.appendChild(opt);
	}
    }
}

<commit_msg>minor tweaks to functions, add comments for documentation<commit_after>// STcommon.js : Show Timer common JavaScript
// This file should be included first, before module-specific JS.
//
// Example:
// <script type="text/javascript" src="STcommon.js"></script>
// <script type="text/javascript" src="edit_a_break.js"></script>
//
// This contains functions and variables used in all modules of
// the Show Timer.

var dbug = 3;

function get_show_start_hour_offset() {

    // This returns the number of hours difference between the
    // computer's configured local timezone and US/Eastern.  This
    // would depend on the browswer's implementatin of the Date
    // object's getTimezoneOffset() method.  US/Eastern is 5 hours
    // (300 minutes) behind UTC in January, and during daylight saving
    // time (DST), 240 minutes.  So for example, if the computer is
    // configured for US/Pacific time during DST, getTimezoneOffset()
    // will return 420, therefore 240-420 = -180, or 3 hours west of
    // EDT.  For the example of "The Tech Guy," this would be added to
    // 14 to produce 11, or 11 AM, which is the hour of the beginning
    // of the show in Petaluma.

    var now = new Date();
    var nowOff = now.getTimezoneOffset();
    now.setMonth(0);
    var JanOff = now.getTimezoneOffset();
    var EToff = 300;
    var localDiff;

    if ( nowOff !== JanOff ) {
	// We're under daylight saving time
	EToff = 240;
    }

    localDiff = EToff - nowOff;

    return localDiff / 60;
}

// symbolic states of the show
// This acts sort of like an enum in C

const BEFORE_SHOW = 0,
      BUMP_IN = 1, // technically ON_AIR, but while bumper music is playing
      ON_AIR = 2, // talent should be talking
      TIME_SHORT = 3, // break/end coming soon
      TIME_VERY_SHORT = 4, // break/end coming REALLY soon
      IN_BREAK = 5, // ad should be playing
      BUMP_SOON = 6, // ad still playing, but BUMP_IN approaching
      BUMP_VERY_SOON = 7, // ad still playing, but very near to BUMP_IN
      SHOW_DONE = 8;

// You could think of "after show" as "before show", as in, before
// the next show.  But SHOW_DONE marks the end of the precomputed
// events table.

function dbg(lvl, ...msg) {

    // This function is called for debugging.  "lvl" describes the
    // intended level of the message, from lower numbers being very
    // low level details, like every single member being added to a
    // complex structure, to higher numbers like general program
    // state.  If "lvl" is greater than or equal to the global
    // variable "dbug," the message (including its level and a colon)
    // goes to the debugging console, otherwise it's just ignored.
    // There are no constraints on "lvl" or "dbug", for example they
    // could be negative.

    if ( lvl >= dbug ) {
	console.log(lvl+": "+msg.join(" "));
    }
    return true;
}

function zeropad(num, places) {

    // This takes "num" and pads with zeroes so that the result ends
    // up being "places" digits.  For example, positive seconds less
    // than 10 are typically preceeded by ":0".

    var s = num.toString();
    var i;

    while ( s.length < places ) {
	s = "0" + s;
    }

    return s;
}

var brk = [];  // the list of breaks

function load_breaks(profname) {

    // Load the breaks list from the localStorage key named
    // "profname" and return it.  Historical note: this used
    // to append "_breaks" to the key name, but then that did
    // not allow for the idea of a profile backup name.  If the
    // "profname" profile is not in localStorage, see if "defaultbrk"
    // exists and prompt the user to return that instead.

    var breaktab = try_load_breaks(profname);

    if ( breaktab !== false ) {
	return breaktab;
    }

    if ( Array.isArray(defaultbrk) ) {
	console.warn('Tried to load breaks "'+profname+'" but that does not exist.');
	if ( confirm('Profile "'+profname.replace(/_breaks/, "")+'" not found in localStorage, load the default list instead?') ) {
	    return defaultbrk;
	}
    }

    return false;
}

function try_load_breaks(profname) {

    // This does the actual work of attempting to load the profile.
    // This was refactored because there are now two ways this can
    // fail to load the profile (return false), and I wanted an idea
    // of a built-in default breaks list.  So the "API" as it were is
    // to call "load_breaks()" and if we return false from
    // try_load_breaks(), load_breaks() sees if there exists a
    // "defaultbrk" array and returns that instead.  It's just a lot
    // handier this way, instead of longer if(){} blocks or a label.

    var breaklist = new Array();
    var breakListStr;

    try {
	breakListStr = localStorage.getItem(profname);
    } catch (err) {
	console.warn("could not access "+profname+" in localStorage: "+err);
	return false;
    }

    breaklist = JSON.parse(breakListStr);
    if ( ! breaklist ) {
	console.warn("load_breaks("+profname+"): came up empty.");
	return false;
    }

    // Just a historical note: The .begin and .end used to be Date()
    // objects, and of course, JSON records the date as a string
    // instead of an object.  Therefore, there USED TO BE a loop here
    // recreating objects from strings.  However, it did not seem
    // useful to keep them as objects, and instead they're offsets in
    // seconds from the start of the show.

    return breaklist;
}

function break_cmp(obja, objb) {

    // When sorting the brk array (the break descriptions), this
    // provides the comparison function to the .sort() method.

    return obja.begin - objb.begin;
}

function secs2minsec(insecs) {

    // Form a minutes:seconds string from "insecs" number of seconds,
    // if possible. This is visually nicer for break lengths than full
    // hh:mm:ss.  If the hours: portion is nonzero, return it.

    var str = "";
    var hrs;

    // Centralize this conversion by calling the hms converter (JUST
    // in case the format changes or a bug is discovered).  It is
    // admittedly a little inefficient in terms of computation.

    str = secs2hmsStr(insecs);
    hrs = str.substring(0,2);
    if ( hrs === "00" ) {
	// drop the "00:"
	str = str.substring(3);
	if ( str.charAt(0) === "0" ) {
	    // drop the leading zero
	    str = str.substring(1);
	}
    }

    return str;
}

function secs2hmsStr(insecs) {

    // form an hours, minutes, seconds string (hh:mm:ss) from "insecs"
    // number of seconds.  Each portion of the results is zero padded
    // to 2 digits.

    var str = "";
    var t = insecs;
    var secs,mins,hrs;

    secs = t % 60;
    t = Math.floor(t/60);
    mins = t % 60;
    t = Math.floor(t/60);
    str += zeropad(t, 2)
    str += ":";
    str += zeropad(mins, 2)
    str += ":";
    str += zeropad(secs, 2)

    return str;
}

function hms2secs(str) {

    // Take string "str" which is in the format hours:minutes:seconds and
    // convert that to a number of seconds.  Zero, one, or two colons are
    // allowed.  Any characters other than the digits and a colon make this
    // function return false.  Also if minutes or seconds are greater than
    // 59 will cause a return of false.  A colon followed by nothing
    // assumes zero, so for example "4:" means four minutes.

    var secs = 0;
    var c, l, i;
    var coloncnt = 0;
    var val; // intermediate value while walking along the string
    var hms = new Array(); // hours, minutes, seconds as accumulated

    dbg(0, "  hms2secs(): converting "+str);

    val = 0;
    l = str.length;
    for ( i = 0; i < l; i++ ) {
	c = str.charAt(i);
	dbg(0, "    considering "+c+" at "+i+" ("+val+")");
	if ( c === ":" ) {
	    if ( ++coloncnt > 2 ) {
		dbg(1, "    too many colons, "+coloncnt);
		return false;
	    }
	    dbg(0, "      processing colon, before "+val);
	    hms.push(val);
	    val = 0;
	} else if ( c < "0" || c > "9" ) {
	    dbg(1, "    char "+c+" not in range");
	    return false;
	} else {
	    // add the next digit by shifting the <result so
	    // far> by one decimal place and add current one
	    val *= 10;
	    val += parseInt(c);
	}
    }
    hms.push(val);

    while ( ++coloncnt < 3 ) {
	hms.unshift(0);
    }

    dbg(1, "  ready with hms len "+hms.length+" and hms "+hms);
    if ( hms[0] > 23 || hms[1] > 59 || hms[2] > 59 ) {
	dbg(1, "    something was too big");
	return false;
    }

    secs = hms[0] * 3600 + hms[1] * 60 + hms[2];

    return secs;
}

function state2str(st) {
    // Given state "st", what does the const look
    // like in the source?
    // Returns "unknown" if the number is out of range.
    const state = [ "BEFORE_SHOW",
		    "BUMP_IN",
		    "ON_AIR",
		    "TIME_SHORT",
		    "TIME_VERY_SHORT",
		    "IN_BREAK",
		    "BUMP_SOON",
		    "BUMP_VERY_SOON",
		    "SHOW_DONE" ];

    if ( st < BEFORE_SHOW ||
	 st > SHOW_DONE ) {
	return "unknown";
    } else {
	return state[st];
    }
}

function st2col(st) {

    // For state "st", which member of st.color should be
    // used for coloration of the HTML node?  If the state
    // is out of range, let CSS take over by returning "".

    const statetab =
	  [ "revertCSS",
	    "onAir",
	    "onAir",
	    "soon",
	    "verysoon",
	    "revertCSS",
	    "soon",
	    "verysoon",
	    "revertCSS"
	  ];

    if ( st < BEFORE_SHOW ||
	 st > SHOW_DONE ) {
	return "revertCSS";
    } else {
	return statetab[st];
    }
}

function find_all_profiles() {

    // finds all the profiles in localStorage and inserts <option>
    // elements into the node with an ID of "profiles" (expected to be
    // a <datalist> element)

    var i, l, k;
    var seen = new Array();
    var prof;
    var optproto = document.createElement("option");
    var opt;
    var profblk;

    var brksidx, klen;

    if ( typeof profiles !== "undefined" ) {
	profblk = profiles;
    } else {
	if ( (profblk = document.getElementById("profiles")) === null ) {
	    // If one isn't in the document already, provide one.
	    // Other JavaScript could likewise use appendChild to add
	    // an input field which utilizes this list.
	    profblk = document.createElement("datalist");
	    document.body.appendChild(profblk);
	}
    }

    // empty out any existing options
    while ( (opt = profiles.firstChild) !== null ) {
	profiles.removeChild(opt);
    }

    l = localStorage.length;
    for ( i = 0; i < l; i++ ) {
	k = localStorage.key(i);
	klen = k.length;
	dbg(0, "got key "+k+" length "+klen);
	prof = k.replace(/_(break|params)(_bak[0-9]*)?$/, "");
	if ( ! seen.includes(prof) ) {
	    dbg(0,"*** so added from key "+k);
	    seen.push(prof);
	    opt = optproto.cloneNode(false);
	    opt.textContent = prof;
	    profblk.appendChild(opt);
	}
    }
}

<|endoftext|>


<commit_before>var updateFilterTimer;
var resultCache;
var validSet;
var sortBy;
var nominateScatterChart = dc.scatterPlot("#scatter-chart");
var baseTip = d3.select("body").append("div").attr("class", "d3-tip").style("visibility","hidden").attr("id","mapTooltip");
var eW;

// From stackoverflow response, who borrowed it from Shopify--simple ordinal suffix.
function getGetOrdinal(n) {
    var s=["th","st","nd","rd"],
    v=n%100;
    return n+(s[(v-20)%10]||s[v]||s[0]);
 }

$(document).ready(function()
{
	congressNum = $("#congSelector").val();
	$.ajax({
		dataType: "JSON",
		url: "/api/getmembersbycongress?congress="+congressNum+"&chamber="+chamber_param+"&api=Web_Congress",
		success: function(data, status, xhr)
		{
			$('#content').fadeIn();
			$('#loading-container').slideUp();
			resultCache = data;
			writeBioTable();
			nomPlot();
			compositionBar();
		}
	});

	$("#congSelector").change(reloadBios);
});

function nomPlot()
{
	console.log('ok');
	var ndx = crossfilter(resultCache["results"]);
	var all = ndx.groupAll();
	var xDimension = ndx.dimension(
		function(d)
		{
			if(d.nominate!=undefined)
			{
				var x = d.nominate.dim1;
				var y = d.nominate.dim2;
			}
			else
			{
				var x = 999;
				var y = 999;
			}
			return [x,y];
		}
	);
	var xGroup = xDimension.group().reduce(
		function(p, d)
		{
			p.members.push(d);
			return p;
		},

		function(p, d)
		{
			var index = p.members.indexOf(d);
			if(index > -1) { p.members.splice(index, 1); }
			return p;
		},

		function() { return {members: []} ; }
	);

	nominateScatterChart
	.width(890)
	.height(432)
	.margins({top:25,right:25,bottom:75,left:75})
	.dimension(xDimension)
	.mouseZoomable(false)
	.group(xGroup)
	.data(function(group) { return group.all().filter(function(d) { return d.key!=[999,999]; });})
	.symbolSize(7)
	.colorCalculator(function(d) {
		var color = "#CCC";
		try {
			if(d.value.members.length > 0){
				color = blendColors(d.value.members);
			}
		}catch(e){
			console.log(e);
		}
		return color;
	})
	.highlightedSize(10)
	.x(d3.scale.linear().domain([-1.0,1.0]))
	.y(d3.scale.linear().domain([-1.2,1.2]));

	nominateScatterChart.on("filtered", function()
	{
		if(updateFilterTimer) { clearTimeout(updateFilterTimer); }
		updateFilterTimer = setTimeout(function()
		{
			var filterSelect= xDimension.top(Infinity);
			validSet = [];
			for(var i in filterSelect)
			{
				validSet.push(filterSelect[i].icpsr);
			}
			hasFilter=1;
			hideMembersUnselected();
		}, 300);
	});

	dc.filterAll();
	dc.renderAll();
	decorateNominate(nominateScatterChart, resultCache);
}

function rechamber()
{
	if(chamber_param=="house") { chamber_param="senate"; $("#memberLabel").html("Senators"); }
	else { chamber_param="house"; $("#memberLabel").html("Representatives"); }
	reloadBios();
}

function reloadBios()
{
	congressNum = $("#congSelector").val();
	$.ajax({
		dataType: "JSON",
		url: "/api/getmembersbycongress?congress="+congressNum+"&chamber="+chamber_param+"&api=Web_Congress",
		success: function(data, status, xhr)
		{
			validSet = [];
			resultCache = data;
			$("#sortChamber").unbind('click')
			$("#sortChamber").click(function() { resort('elected_'+chamber_param); return false; });
			writeBioTable();
			nomPlot();
			compositionBar();
		}
	});
}

function getVPP(congress)
{
	// This is a hack; we just list thresholds at which the VP/ President of the Senate switches
	var VPParty = {	"115": "Republican", "111": "Democrat", "107": "Republican", "103": "Democrat",
			"97": "Republican", "95": "Democrat", "91": "Republican", "87": "Democrat",
			"83": "Republican", "81": "Democrat", "80": "Vacant", "73": "Democrat",
			"67": "Republican", "63": "Democrat", "59": "Republican", "58": "Vacant",
			"55": "Republican", "53": "Democrat", "51": "Republican", "50": "Vacant",
			"49": "Democrat", "48": "Vacant", "41": "Republican", "40": "Vacant",
			"39": "Democrat", "37": "Republican", "35": "Democrat", "34": "Vacant",
			"33": "Democrat", "32": "Vacant", "31": "Whig", "29": "Democrat",
			"28": "Vacant", "27": "Whig", "25": "Democrat", "20": "Jackson",
			"15": "Democrat-Republican", "14": "Vacant", "5": "Democrat-Republican",
			"4": "Federalist", "1": "Pro-Administration"};

	// Sort the dict keys and reverse them so the short-circuit in the loop below works
	var keys = Object.keys(VPParty);
	keys.reverse();

	// Iterate through keys until we find the bucket we're in
	for(var i=0;i!=keys.length;i++)
	{
		// We just return party name.
		if(parseInt(keys[i])<=parseInt(congress)) { return VPParty[keys[i]]; }
	}
}

function compositionBar()
{
	var partyCount={}
	$.each(resultCache["results"], function(i, d) {
		if(partyCount[d.party_short_name]==undefined) { partyCount[d.party_short_name]=1; }
		else { partyCount[d.party_short_name]++; }
	});

	$("#partyComposition").html("");
	var svgBucket = d3.select("#partyComposition").append("svg").attr("width",300).attr("height",21);
	var x=0;
	var sorted_parties = Object.keys(partyCount).sort();

	var baseTipT = "Party Composition of "+getGetOrdinal(congressNum)+" "+chamber_param.substr(0,1).toUpperCase()+chamber_param.substr(1)+"
";
	var maxN = 0; var maxP = 0; var sumSet=0;
	for(pNi in sorted_parties)
	{
		pN = sorted_parties[pNi];
		if(partyCount[pN]>maxN || (partyCount[pN]==maxN && pN==getVPP(congressNum) && chamber_param=="senate")) { maxN=partyCount[pN]; maxP = pN; }
		var wid = Math.round(300*(partyCount[pN]/resultCache["results"].length));
		try {var voteCol = colorSchemes[partyColorMap[partyNameSimplify(pN)]][0]; } catch(e) { var voteCol = '#000000'; }
		var rect = svgBucket.append("rect")
				.attr("x",x).attr("y",3).attr("width",wid).attr("height",15)
				.attr('class',partyColorMap[partyNameSimplify(pN)])
				//.attr("fill",voteCol)
				.attr("stroke","#000000").attr("stroke-width",1);
		x=x+wid;
		baseTipT += '
'+pN+': '+partyCount[pN];
		if(chamber_param=="senate" && pN==getVPP(congressNum)) { baseTipT += " (+ Vice President)"; }
	}
	baseTipT+= "

Note: Counts include members elected through special elections after resignations, deaths, or vacancies.";
	svgBucket.on('mouseover',function(d) {
		baseTip.html(baseTipT);
		$('#mapTooltip').removeClass().addClass('d3-tip')
				.addClass(partyColorMap[partyNameSimplify(maxP)]);
		eW = baseTip.style('width');
		baseTip.style('visibility','visible');
	}).on('mousemove',function(d) {
		baseTip.style("top",(event.pageY+20)+"px").style("left", (event.pageX-(parseInt(eW.substr(0,eW.length-2))/2))+"px");
	}).on('mouseout',function(d) { baseTip.style('visibility','hidden'); });
}

function hideMembersUnselected()
{

	$("#memberList > li.memberResultBox").each(function(i, d) {
		if(validSet.indexOf(parseInt($(d).attr("id")))!=-1 || validSet.length==0) { $(d).show(); }
		else { $(d).hide(); }
	});

        // Set number of columns by number of selected members
        if(validSet.length < 5){
	        $("#memberList").css("columns","1").css("width","25%");
	} else if (validSet.length < 9)
	{
	        $("#memberList").css("columns","2").css("width","50%")
	} else if (validSet.length < 13)
	{
	        $("#memberList").css("columns","3").css("width","75%")
	} else
	{
	        $("#memberList").css("columns","4").css("width","100%")
	}

}

<commit_msg>Jeff's changes to selecting members + my changes to selecting and deselecting members.<commit_after>var updateFilterTimer;
var resultCache;
var validSet;
var sortBy;
var nominateScatterChart = dc.scatterPlot("#scatter-chart");
var baseTip = d3.select("body").append("div").attr("class", "d3-tip").style("visibility","hidden").attr("id","mapTooltip");
var eW;

// From stackoverflow response, who borrowed it from Shopify--simple ordinal suffix.
function getGetOrdinal(n) {
    var s=["th","st","nd","rd"],
    v=n%100;
    return n+(s[(v-20)%10]||s[v]||s[0]);
 }

$(document).ready(function()
{
	congressNum = $("#congSelector").val();
	$.ajax({
		dataType: "JSON",
		url: "/api/getmembersbycongress?congress="+congressNum+"&chamber="+chamber_param+"&api=Web_Congress",
		success: function(data, status, xhr)
		{
			$('#content').fadeIn();
			$('#loading-container').slideUp();
			resultCache = data;
			writeBioTable();
			nomPlot();
			compositionBar();
		}
	});

	$("#congSelector").change(reloadBios);
});

function nomPlot()
{
	var ndx = crossfilter(resultCache["results"]);
	var all = ndx.groupAll();
	var xDimension = ndx.dimension(
		function(d)
		{
			if(d.nominate!=undefined)
			{
				var x = d.nominate.dim1;
				var y = d.nominate.dim2;
			}
			else
			{
				var x = 999;
				var y = 999;
			}
			return [x,y];
		}
	);
	var xGroup = xDimension.group().reduce(
		function(p, d)
		{
			p.members.push(d);
			return p;
		},

		function(p, d)
		{
			var index = p.members.indexOf(d);
			if(index > -1) { p.members.splice(index, 1); }
			return p;
		},

		function() { return {members: []} ; }
	);

    nominateScatterChart
        .clipPadding(4)
        .transitionDuration(250) // JBL:Speed up symbol size changes on brush per AB request
	.width(890)
	.height(790*nomDWeight+100)
	.margins({top:25,right:25,bottom:75,left:75})
	.dimension(xDimension)
	.mouseZoomable(false)
	.group(xGroup)
	.data(function(group) { return group.all().filter(function(d) { return d.key!=[999,999]; });})
	.symbolSize(7)
	.colorCalculator(function(d) {
		var color = "#CCC";
		try {
			if(d.value.members.length > 0){
				color = blendColors(d.value.members);
			}
		}catch(e){
			console.log(e);
		}
		return color;
	})
	.highlightedSize(10)
	.x(d3.scale.linear().domain([-1.0,1.0]))
	.y(d3.scale.linear().domain([-1.0,1.0]));

	nominateScatterChart.on("filtered", function()
	{
		if(updateFilterTimer) { clearTimeout(updateFilterTimer); }
		updateFilterTimer = setTimeout(function()
		{
			var filterSelect= xDimension.top(Infinity);
			validSet = [];
			for(var i in filterSelect)
			{
				validSet.push(filterSelect[i].icpsr);
			}
			hasFilter=1;
			hideMembersUnselected();
		}, 300);
	});

	dc.filterAll();
	dc.renderAll();
        decorateNominate(nominateScatterChart, resultCache);

        // Make brush box appear on click
        var scb = nominateScatterChart.select(".brush");
        scb.on('click', function(){
  	  var extent = nominateScatterChart.brush().extent();
	  var x = nominateScatterChart.x().invert(d3.mouse(this)[0]),
	      y = nominateScatterChart.y().invert(d3.mouse(this)[1]);
	  // Only draw box if there isn't one already there...
 	  if (extent[0][0]==extent[1][0] & extent[0][1]==extent[1][1]) {
	      if (x*x + y*y <= 1) {
		  var insideBox = $.grep(nominateScatterChart.data(), function(n, i) {
			return (n["value"]["members"][0]["nominate"]["dim1"] >= x-0.025 &&
				n["value"]["members"][0]["nominate"]["dim1"] <= x+0.025 &&
				n["value"]["members"][0]["nominate"]["dim2"] >= y-0.025/nomDWeight &&
				n["value"]["members"][0]["nominate"]["dim2"] <= y+0.025/nomDWeight);
		 });
		if(insideBox.length) { nominateScatterChart.brush().extent([[x-0.025,y-0.025/nomDWeight],[x+0.025,y+0.025/nomDWeight]]).event(scb); }
		else { nominateScatterChart.brush().extent([[x,y],[x,y]]).event(scb); }
	      } else {
		  nominateScatterChart.brush().extent([[x,y],[x,y]]).event(scb);
	      }
	  }
        });
}

function rechamber()
{
	if(chamber_param=="house") { chamber_param="senate"; $("#memberLabel").html("Senators"); }
	else { chamber_param="house"; $("#memberLabel").html("Representatives"); }
	reloadBios();
}

function reloadBios()
{
	congressNum = $("#congSelector").val();
	$.ajax({
		dataType: "JSON",
		url: "/api/getmembersbycongress?congress="+congressNum+"&chamber="+chamber_param+"&api=Web_Congress",
		success: function(data, status, xhr)
		{
			validSet = [];
			resultCache = data;
			$("#sortChamber").unbind('click')
			$("#sortChamber").click(function() { resort('elected_'+chamber_param); return false; });
			writeBioTable();
			nomPlot();
			compositionBar();
		}
	});
}

function getVPP(congress)
{
	// This is a hack; we just list thresholds at which the VP/ President of the Senate switches
	var VPParty = {	"115": "Republican", "111": "Democrat", "107": "Republican", "103": "Democrat",
			"97": "Republican", "95": "Democrat", "91": "Republican", "87": "Democrat",
			"83": "Republican", "81": "Democrat", "80": "Vacant", "73": "Democrat",
			"67": "Republican", "63": "Democrat", "59": "Republican", "58": "Vacant",
			"55": "Republican", "53": "Democrat", "51": "Republican", "50": "Vacant",
			"49": "Democrat", "48": "Vacant", "41": "Republican", "40": "Vacant",
			"39": "Democrat", "37": "Republican", "35": "Democrat", "34": "Vacant",
			"33": "Democrat", "32": "Vacant", "31": "Whig", "29": "Democrat",
			"28": "Vacant", "27": "Whig", "25": "Democrat", "20": "Jackson",
			"15": "Democrat-Republican", "14": "Vacant", "5": "Democrat-Republican",
			"4": "Federalist", "1": "Pro-Administration"};

	// Sort the dict keys and reverse them so the short-circuit in the loop below works
	var keys = Object.keys(VPParty);
	keys.reverse();

	// Iterate through keys until we find the bucket we're in
	for(var i=0;i!=keys.length;i++)
	{
		// We just return party name.
		if(parseInt(keys[i])<=parseInt(congress)) { return VPParty[keys[i]]; }
	}
}

function compositionBar()
{
	var partyCount={}
	$.each(resultCache["results"], function(i, d) {
		if(partyCount[d.party_short_name]==undefined) { partyCount[d.party_short_name]=1; }
		else { partyCount[d.party_short_name]++; }
	});

	$("#partyComposition").html("");
	var svgBucket = d3.select("#partyComposition").append("svg").attr("width",300).attr("height",21);
	var x=0;
	var sorted_parties = Object.keys(partyCount).sort();

	var baseTipT = "Party Composition of "+getGetOrdinal(congressNum)+" "+chamber_param.substr(0,1).toUpperCase()+chamber_param.substr(1)+"
";
	var maxN = 0; var maxP = 0; var sumSet=0;
	for(pNi in sorted_parties)
	{
		pN = sorted_parties[pNi];
		if(partyCount[pN]>maxN || (partyCount[pN]==maxN && pN==getVPP(congressNum) && chamber_param=="senate")) { maxN=partyCount[pN]; maxP = pN; }
		var wid = Math.round(300*(partyCount[pN]/resultCache["results"].length));
		try {var voteCol = colorSchemes[partyColorMap[partyNameSimplify(pN)]][0]; } catch(e) { var voteCol = '#000000'; }
		var rect = svgBucket.append("rect")
				.attr("x",x).attr("y",3).attr("width",wid).attr("height",15)
				.attr('class',partyColorMap[partyNameSimplify(pN)])
				//.attr("fill",voteCol)
				.attr("stroke","#000000").attr("stroke-width",1);
		x=x+wid;
		baseTipT += '
'+pN+': '+partyCount[pN];
		if(chamber_param=="senate" && pN==getVPP(congressNum)) { baseTipT += " (+ Vice President)"; }
	}
	baseTipT+= "

Note: Counts include members elected through special elections after resignations, deaths, or vacancies.";
	svgBucket.on('mouseover',function(d) {
		baseTip.html(baseTipT);
		$('#mapTooltip').removeClass().addClass('d3-tip')
				.addClass(partyColorMap[partyNameSimplify(maxP)]);
		eW = baseTip.style('width');
		baseTip.style('visibility','visible');
	}).on('mousemove',function(d) {
		baseTip.style("top",(event.pageY+20)+"px").style("left", (event.pageX-(parseInt(eW.substr(0,eW.length-2))/2))+"px");
	}).on('mouseout',function(d) { baseTip.style('visibility','hidden'); });
}

function hideMembersUnselected()
{

	$("#memberList > li.memberResultBox").each(function(i, d) {
		if(validSet.indexOf(parseInt($(d).attr("id")))!=-1 || validSet.length==0) { $(d).show(); }
		else { $(d).hide(); }
	});

        // Set number of columns by number of selected members
        if(validSet.length < 5){
	        $("#memberList").css("columns","1").css("width","25%");
	} else if (validSet.length < 9)
	{
	        $("#memberList").css("columns","2").css("width","50%")
	} else if (validSet.length < 13)
	{
	        $("#memberList").css("columns","3").css("width","75%")
	} else
	{
	        $("#memberList").css("columns","4").css("width","100%")
	}

}

<|endoftext|>


<commit_before>var updateFilterTimer;
var resultCache;
var validSet;
var sortBy;
var nominateScatterChart = dc.scatterPlot("#scatter-chart");
var baseTip = d3.select("body").append("div").attr("class", "d3-tip").style("visibility","hidden").attr("id","mapTooltip");
var eW;

// From stackoverflow response, who borrowed it from Shopify--simple ordinal suffix.
function getGetOrdinal(n) {
    var s=["th","st","nd","rd"],
    v=n%100;
    return n+(s[(v-20)%10]||s[v]||s[0]);
 }

$(document).ready(function()
{
	congressNum = $("#congSelector").val();
	$.ajax({
		dataType: "JSON",
		url: "/api/getmembersbycongress?congress="+congressNum+"&chamber="+chamber_param+"&api=Web_Congress",
		success: function(data, status, xhr)
		{
			$('#content').fadeIn();
			$('#loading-container').slideUp();
			resultCache = data;
			writeBioTable();
			nomPlot();
			compositionBar();
		}
	});

	$("#congSelector").change(reloadBios);
});

function nomPlot()
{
	var ndx = crossfilter(resultCache["results"]);
	var all = ndx.groupAll();
	var xDimension = ndx.dimension(
		function(d)
		{
			if(d.nominate!=undefined)
			{
				var x = d.nominate.dim1;
				var y = d.nominate.dim2;
			}
			else
			{
				var x = 999;
				var y = 999;
			}
			return [x,y];
		}
	);
	var xGroup = xDimension.group().reduce(
		function(p, d)
		{
			p.members.push(d);
			return p;
		},

		function(p, d)
		{
			var index = p.members.indexOf(d);
			if(index > -1) { p.members.splice(index, 1); }
			return p;
		},

		function() { return {members: []} ; }
	);

    nominateScatterChart
        .clipPadding(4)
        .transitionDuration(250) // JBL:Speed up symbol size changes on brush per AB request
	.width(890)
	.height(790*nomDWeight+100)
	.margins({top:25,right:25,bottom:75,left:75})
	.dimension(xDimension)
	.mouseZoomable(false)
	.group(xGroup)
	.data(function(group) { return group.all().filter(function(d) { return d.key!=[999,999]; });})
	.symbolSize(7)
	.colorCalculator(function(d) {
		var color = "#CCC";
		try {
			if(d.value.members.length > 0){
				color = blendColors(d.value.members);
			}
		}catch(e){
			console.log(e);
		}
		return color;
	})
	.highlightedSize(10)
	.x(d3.scale.linear().domain([-1.0,1.0]))
	.y(d3.scale.linear().domain([-1.0,1.0]));

	nominateScatterChart.on("filtered", function()
	{
		if(updateFilterTimer) { clearTimeout(updateFilterTimer); }
		updateFilterTimer = setTimeout(function()
		{
			var filterSelect= xDimension.top(Infinity);
			validSet = [];
			for(var i in filterSelect)
			{
				validSet.push(filterSelect[i].icpsr);
			}
			hasFilter=1;
			hideMembersUnselected();
		}, 300);
	});

	dc.filterAll();
	dc.renderAll();
        decorateNominate(nominateScatterChart, resultCache);

        // Make brush box appear on click
        var scb = nominateScatterChart.select(".brush");
        scb.on('click', function(){
  	  var extent = nominateScatterChart.brush().extent();
	  var x = nominateScatterChart.x().invert(d3.mouse(this)[0]),
	      y = nominateScatterChart.y().invert(d3.mouse(this)[1]);
	  // Only draw box if there isn't one already there...
 	  if (extent[0][0]==extent[1][0] & extent[0][1]==extent[1][1]) {
	      if (x*x + y*y <= 1) {
		  var insideBox = $.grep(nominateScatterChart.data(), function(n, i) {
			return (n["value"]["members"][0]["nominate"]["dim1"] >= x-0.025 &&
				n["value"]["members"][0]["nominate"]["dim1"] <= x+0.025 &&
				n["value"]["members"][0]["nominate"]["dim2"] >= y-0.025/nomDWeight &&
				n["value"]["members"][0]["nominate"]["dim2"] <= y+0.025/nomDWeight);
		 });
		if(insideBox.length) { nominateScatterChart.brush().extent([[x-0.025,y-0.025/nomDWeight],[x+0.025,y+0.025/nomDWeight]]).event(scb); }
		else { nominateScatterChart.brush().extent([[x,y],[x,y]]).event(scb); }
	      } else {
		  nominateScatterChart.brush().extent([[x,y],[x,y]]).event(scb);
	      }
	  }
        });
}

function rechamber()
{
	if(chamber_param=="house") { chamber_param="senate"; $("#memberLabel").html("Senators"); }
	else { chamber_param="house"; $("#memberLabel").html("Representatives"); }
	reloadBios();
}

function reloadBios()
{
	congressNum = $("#congSelector").val();
	$.ajax({
		dataType: "JSON",
		url: "/api/getmembersbycongress?congress="+congressNum+"&chamber="+chamber_param+"&api=Web_Congress",
		success: function(data, status, xhr)
		{
			validSet = [];
			resultCache = data;
			$("#sortChamber").unbind('click')
			$("#sortChamber").click(function() { resort('elected_'+chamber_param); return false; });
			writeBioTable();
			nomPlot();
			compositionBar();
		}
	});
}

function getVPP(congress)
{
	// This is a hack; we just list thresholds at which the VP/ President of the Senate switches
	var VPParty = {	"115": "Republican", "111": "Democrat", "107": "Republican", "103": "Democrat",
			"97": "Republican", "95": "Democrat", "91": "Republican", "87": "Democrat",
			"83": "Republican", "81": "Democrat", "80": "Vacant", "73": "Democrat",
			"67": "Republican", "63": "Democrat", "59": "Republican", "58": "Vacant",
			"55": "Republican", "53": "Democrat", "51": "Republican", "50": "Vacant",
			"49": "Democrat", "48": "Vacant", "41": "Republican", "40": "Vacant",
			"39": "Democrat", "37": "Republican", "35": "Democrat", "34": "Vacant",
			"33": "Democrat", "32": "Vacant", "31": "Whig", "29": "Democrat",
			"28": "Vacant", "27": "Whig", "25": "Democrat", "20": "Jackson",
			"15": "Democrat-Republican", "14": "Vacant", "5": "Democrat-Republican",
			"4": "Federalist", "1": "Pro-Administration"};

	// Sort the dict keys and reverse them so the short-circuit in the loop below works
	var keys = Object.keys(VPParty);
	keys.reverse();

	// Iterate through keys until we find the bucket we're in
	for(var i=0;i!=keys.length;i++)
	{
		// We just return party name.
		if(parseInt(keys[i])<=parseInt(congress)) { return VPParty[keys[i]]; }
	}
}

function compositionBar()
{
	var partyCount={}
	$.each(resultCache["results"], function(i, d) {
		if(partyCount[d.party_short_name]==undefined) { partyCount[d.party_short_name]=1; }
		else { partyCount[d.party_short_name]++; }
	});

	$("#partyComposition").html("");
	var svgBucket = d3.select("#partyComposition").append("svg").attr("width",300).attr("height",21);
	var x=0;
	var sorted_parties = Object.keys(partyCount).sort();

	var baseTipT = "Party Composition of "+getGetOrdinal(congressNum)+" "+chamber_param.substr(0,1).toUpperCase()+chamber_param.substr(1)+"
";
	var maxN = 0; var maxP = 0; var sumSet=0;
	for(pNi in sorted_parties)
	{
		pN = sorted_parties[pNi];
		if(partyCount[pN]>maxN || (partyCount[pN]==maxN && pN==getVPP(congressNum) && chamber_param=="senate")) { maxN=partyCount[pN]; maxP = pN; }
		var wid = Math.round(300*(partyCount[pN]/resultCache["results"].length));
		try {var voteCol = colorSchemes[partyColorMap[partyNameSimplify(pN)]][0]; } catch(e) { var voteCol = '#000000'; }
		var rect = svgBucket.append("rect")
				.attr("x",x).attr("y",3).attr("width",wid).attr("height",15)
				.attr('class',partyColorMap[partyNameSimplify(pN)])
				//.attr("fill",voteCol)
				.attr("stroke","#000000").attr("stroke-width",1);
		x=x+wid;
		baseTipT += '
'+pN+': '+partyCount[pN];
		if(chamber_param=="senate" && pN==getVPP(congressNum)) { baseTipT += " (+ Vice President)"; }
	}
	baseTipT+= "

Note: Counts include members elected through special elections after resignations, deaths, or vacancies.";
	svgBucket.on('mouseover',function(d) {
		baseTip.html(baseTipT);
		$('#mapTooltip').removeClass().addClass('d3-tip')
				.addClass(partyColorMap[partyNameSimplify(maxP)]);
		eW = baseTip.style('width');
		baseTip.style('visibility','visible');
	}).on('mousemove',function(d) {
		baseTip.style("top",(event.pageY+20)+"px").style("left", (event.pageX-(parseInt(eW.substr(0,eW.length-2))/2))+"px");
	}).on('mouseout',function(d) { baseTip.style('visibility','hidden'); });
}

function hideMembersUnselected()
{

	$("#memberList > li.memberResultBox").each(function(i, d) {
		if(validSet.indexOf(parseInt($(d).attr("id")))!=-1 || validSet.length==0) { $(d).show(); }
		else { $(d).hide(); }
	});

        // Set number of columns by number of selected members
        if(validSet.length < 5){
	        $("#memberList").css("columns","1").css("width","25%");
	} else if (validSet.length < 9)
	{
	        $("#memberList").css("columns","2").css("width","50%")
	} else if (validSet.length < 13)
	{
	        $("#memberList").css("columns","3").css("width","75%")
	} else
	{
	        $("#memberList").css("columns","4").css("width","100%")
	}

}

<commit_msg>Fixed the show/hide filter to fix the single column bug mentioned in #131 and #1110<commit_after>var updateFilterTimer;
var resultCache;
var validSet;
var sortBy;
var nominateScatterChart = dc.scatterPlot("#scatter-chart");
var baseTip = d3.select("body").append("div").attr("class", "d3-tip").style("visibility","hidden").attr("id","mapTooltip");
var eW;

// From stackoverflow response, who borrowed it from Shopify--simple ordinal suffix.
function getGetOrdinal(n) {
    var s=["th","st","nd","rd"],
    v=n%100;
    return n+(s[(v-20)%10]||s[v]||s[0]);
 }

$(document).ready(function()
{
	congressNum = $("#congSelector").val();
	$.ajax({
		dataType: "JSON",
		url: "/api/getmembersbycongress?congress="+congressNum+"&chamber="+chamber_param+"&api=Web_Congress",
		success: function(data, status, xhr)
		{
			$('#content').fadeIn();
			$('#loading-container').slideUp();
			resultCache = data;
			writeBioTable();
			nomPlot();
			compositionBar();
		}
	});

	$("#congSelector").change(reloadBios);
});

function nomPlot()
{
	var ndx = crossfilter(resultCache["results"]);
	var all = ndx.groupAll();
	var xDimension = ndx.dimension(
		function(d)
		{
			if(d.nominate!=undefined)
			{
				var x = d.nominate.dim1;
				var y = d.nominate.dim2;
			}
			else
			{
				var x = 999;
				var y = 999;
			}
			return [x,y];
		}
	);
	var xGroup = xDimension.group().reduce(
		function(p, d)
		{
			p.members.push(d);
			return p;
		},

		function(p, d)
		{
			var index = p.members.indexOf(d);
			if(index > -1) { p.members.splice(index, 1); }
			return p;
		},

		function() { return {members: []} ; }
	);

    nominateScatterChart
        .clipPadding(4)
        .transitionDuration(250) // JBL:Speed up symbol size changes on brush per AB request
	.width(890)
	.height(790*nomDWeight+100)
	.margins({top:25,right:25,bottom:75,left:75})
	.dimension(xDimension)
	.mouseZoomable(false)
	.group(xGroup)
	.data(function(group) { return group.all().filter(function(d) { return d.key!=[999,999]; });})
	.symbolSize(7)
	.colorCalculator(function(d) {
		var color = "#CCC";
		try {
			if(d.value.members.length > 0){
				color = blendColors(d.value.members);
			}
		}catch(e){
			console.log(e);
		}
		return color;
	})
	.highlightedSize(10)
	.x(d3.scale.linear().domain([-1.0,1.0]))
	.y(d3.scale.linear().domain([-1.0,1.0]));

	nominateScatterChart.on("filtered", function()
	{
		if(updateFilterTimer) { clearTimeout(updateFilterTimer); }
		updateFilterTimer = setTimeout(function()
		{
			var filterSelect= xDimension.top(Infinity);
			validSet = [];
			for(var i in filterSelect)
			{
				validSet.push(filterSelect[i].icpsr);
			}
			hasFilter=1;
			hideMembersUnselected();
		}, 300);
	});

	dc.filterAll();
	dc.renderAll();
        decorateNominate(nominateScatterChart, resultCache);

        // Make brush box appear on click
        var scb = nominateScatterChart.select(".brush");
        scb.on('click', function(){
  	  var extent = nominateScatterChart.brush().extent();
	  var x = nominateScatterChart.x().invert(d3.mouse(this)[0]),
	      y = nominateScatterChart.y().invert(d3.mouse(this)[1]);
	  // Only draw box if there isn't one already there...
 	  if (extent[0][0]==extent[1][0] & extent[0][1]==extent[1][1]) {
	      if (x*x + y*y <= 1) {
		  var insideBox = $.grep(nominateScatterChart.data(), function(n, i) {
			return (n["value"]["members"][0]["nominate"]["dim1"] >= x-0.025 &&
				n["value"]["members"][0]["nominate"]["dim1"] <= x+0.025 &&
				n["value"]["members"][0]["nominate"]["dim2"] >= y-0.025/nomDWeight &&
				n["value"]["members"][0]["nominate"]["dim2"] <= y+0.025/nomDWeight);
		 });
		if(insideBox.length) { nominateScatterChart.brush().extent([[x-0.025,y-0.025/nomDWeight],[x+0.025,y+0.025/nomDWeight]]).event(scb); }
		else { nominateScatterChart.brush().extent([[x,y],[x,y]]).event(scb); }
	      } else {
		  nominateScatterChart.brush().extent([[x,y],[x,y]]).event(scb);
	      }
	  }
        });
}

function rechamber()
{
	if(chamber_param=="house") { chamber_param="senate"; $("#memberLabel").html("Senators"); }
	else { chamber_param="house"; $("#memberLabel").html("Representatives"); }
	reloadBios();
}

function reloadBios()
{
	congressNum = $("#congSelector").val();
	$.ajax({
		dataType: "JSON",
		url: "/api/getmembersbycongress?congress="+congressNum+"&chamber="+chamber_param+"&api=Web_Congress",
		success: function(data, status, xhr)
		{
			validSet = [];
			resultCache = data;
			$("#sortChamber").unbind('click')
			$("#sortChamber").click(function() { resort('elected_'+chamber_param); return false; });
			writeBioTable();
			nomPlot();
			compositionBar();
		}
	});
}

function getVPP(congress)
{
	// This is a hack; we just list thresholds at which the VP/ President of the Senate switches
	var VPParty = {	"115": "Republican", "111": "Democrat", "107": "Republican", "103": "Democrat",
			"97": "Republican", "95": "Democrat", "91": "Republican", "87": "Democrat",
			"83": "Republican", "81": "Democrat", "80": "Vacant", "73": "Democrat",
			"67": "Republican", "63": "Democrat", "59": "Republican", "58": "Vacant",
			"55": "Republican", "53": "Democrat", "51": "Republican", "50": "Vacant",
			"49": "Democrat", "48": "Vacant", "41": "Republican", "40": "Vacant",
			"39": "Democrat", "37": "Republican", "35": "Democrat", "34": "Vacant",
			"33": "Democrat", "32": "Vacant", "31": "Whig", "29": "Democrat",
			"28": "Vacant", "27": "Whig", "25": "Democrat", "20": "Jackson",
			"15": "Democrat-Republican", "14": "Vacant", "5": "Democrat-Republican",
			"4": "Federalist", "1": "Pro-Administration"};

	// Sort the dict keys and reverse them so the short-circuit in the loop below works
	var keys = Object.keys(VPParty);
	keys.reverse();

	// Iterate through keys until we find the bucket we're in
	for(var i=0;i!=keys.length;i++)
	{
		// We just return party name.
		if(parseInt(keys[i])<=parseInt(congress)) { return VPParty[keys[i]]; }
	}
}

function compositionBar()
{
	var partyCount={}
	$.each(resultCache["results"], function(i, d) {
		if(partyCount[d.party_short_name]==undefined) { partyCount[d.party_short_name]=1; }
		else { partyCount[d.party_short_name]++; }
	});

	$("#partyComposition").html("");
	var svgBucket = d3.select("#partyComposition").append("svg").attr("width",300).attr("height",21);
	var x=0;
	var sorted_parties = Object.keys(partyCount).sort();

	var baseTipT = "Party Composition of "+getGetOrdinal(congressNum)+" "+chamber_param.substr(0,1).toUpperCase()+chamber_param.substr(1)+"
";
	var maxN = 0; var maxP = 0; var sumSet=0;
	for(pNi in sorted_parties)
	{
		pN = sorted_parties[pNi];
		if(partyCount[pN]>maxN || (partyCount[pN]==maxN && pN==getVPP(congressNum) && chamber_param=="senate")) { maxN=partyCount[pN]; maxP = pN; }
		var wid = Math.round(300*(partyCount[pN]/resultCache["results"].length));
		try {var voteCol = colorSchemes[partyColorMap[partyNameSimplify(pN)]][0]; } catch(e) { var voteCol = '#000000'; }
		var rect = svgBucket.append("rect")
				.attr("x",x).attr("y",3).attr("width",wid).attr("height",15)
				.attr('class',partyColorMap[partyNameSimplify(pN)])
				//.attr("fill",voteCol)
				.attr("stroke","#000000").attr("stroke-width",1);
		x=x+wid;
		baseTipT += '
'+pN+': '+partyCount[pN];
		if(chamber_param=="senate" && pN==getVPP(congressNum)) { baseTipT += " (+ Vice President)"; }
	}
	baseTipT+= "

Note: Counts include members elected through special elections after resignations, deaths, or vacancies.";
	svgBucket.on('mouseover',function(d) {
		baseTip.html(baseTipT);
		$('#mapTooltip').removeClass().addClass('d3-tip')
				.addClass(partyColorMap[partyNameSimplify(maxP)]);
		eW = baseTip.style('width');
		baseTip.style('visibility','visible');
	}).on('mousemove',function(d) {
		baseTip.style("top",(event.pageY+20)+"px").style("left", (event.pageX-(parseInt(eW.substr(0,eW.length-2))/2))+"px");
	}).on('mouseout',function(d) { baseTip.style('visibility','hidden'); });
}

function hideMembersUnselected()
{

	$("#memberList > li.memberResultBox").each(function(i, d) {
		if(validSet.indexOf(parseInt($(d).attr("id")))!=-1 || validSet.length==0) { $(d).show(); }
		else { $(d).hide(); }
	});

        // Set number of columns by number of selected members
        if(validSet.length && validSet.length < 5){
	        $("#memberList").css("columns","1").css("width","25%");
	} else if (validSet.length && validSet.length < 9)
	{
	        $("#memberList").css("columns","2").css("width","50%")
	} else if (validSet.length && validSet.length < 13)
	{
	        $("#memberList").css("columns","3").css("width","75%")
	} else
	{
	        $("#memberList").css("columns","4").css("width","100%")
	}

}

<|endoftext|>