<commit_before>/*
	jQuery.Typograf.js

	https://github.com/Spearance/jQuery.Typograf.js
	http://www.typograf.ru/

	Copyright 2015, Evgeniy Lepeshkin

	Released under the MIT license.
	http://www.opensource.org/licenses/mit-license.php

	Version: v 0.1
	Date: Thu Mar 24, 2015
 */

(function($){
	$.fn.typograf = function(options){
		options = $.extend({
			leftQuote: "«",
			rightQuote: "»",
			handler: "",
			restor: ""
		}, options);

		return this.each(function(){
			var $this = $(this),
			    obj = $this.get(0),
			    exclude = false,
			    selected = false,
			    caretPosition = 0,
			    original;

			// exist Typograf handler
			if (options.handler) {
				original = "";

				$(options.handler).click(function(){
					if (original == "") {
						original = $this.val();
					}

					if (original) {
						if (options.restore) {
							$(options.restore).removeAttr("disabled");
						}
						typograf($this);
					}
				});

				// exist Restore handler
				if (options.restore) {
					$(options.restore).click(function(){
						$(this).attr("disabled", "true");
						$this.val(original);
					});
				}
			} else {
				// auto
				$this
					.keydown(function(e){
						// check for Ctrl/⌘
						exclude = Boolean(
							(e.metaKey || e.ctrlKey) &&
							!(e.altKey && e.ctrlKey) // Do not exclude Alt Gr

						);
					})
					.keyup(function(e){
						// check for selected text
						selected = Boolean(getSelectionText() != "");

						// exclude arrows and Ctrl/⌘ + A/C/V/X/Z
						if (
							// home, end, page up, page down
							e.which != "33" &&
							e.which != "34" &&
							e.which != "35" &&
							e.which != "36" &&

							// arrows
							e.which != "37" &&
							e.which != "38" &&
							e.which != "39" &&
							e.which != "40" &&

							// single Shift
							e.which != "16" &&

							// select all, copy, paste, cut, undo
							!(exclude && (e.which == "65" ||
							              e.which == "67" ||
							              e.which == "86" ||
							              e.which == "88" ||
							              e.which == "90")) &&

							// selected text
							!selected
						){
							typograf($this);
						}
						return true;
					}
				);
			}

			function typograf (textField){
				var text = textField.val();
				caretPosition = getCaretPosition(obj);

				text = text
					// Minus
					.replace(/\u0020-(\d)/g, "\u0020−$1")

					// Dash
					.replace(/(^|\n|\s|>)\-(\s)/g, "$1—$2")

					// Double hyphen
					.replace(/\-{2} /g, function(){
						caretPosition--;
						return "— ";
					})

					// Multiple nbsp
					.replace(/\u00a0{2,}|\u00a0\u0020|\u0020\u00a0/g, function(str){
						caretPosition -= str.length - 1;
						return "\u00a0";
					})

					// HTML-comment
					.replace(/<!—/ig, function(){
						caretPosition++;
						return "<!--";
					})

					// Numerical interval
					.replace(/(\d)(\u0020)?[-—](\u0020)?(\d)/g, function(str, $1, $2, $3, $4){
						if ($2 == "\u0020") { caretPosition-- }
						if ($3 == "\u0020") { caretPosition-- }
						return $1 + "–" + $4;
					})

					// Copyright
					.replace(/\([cс]\)/ig, function(){
						caretPosition -= 2;
						return "©";
					})

					// Registered trademark
					.replace(/\(r\)/ig, function(){
						caretPosition -= 2;
						return "®";
					})

					// Trademark
					.replace(/\(tm\)/ig, function(){
						caretPosition -= 2;
						return "™";
					})

					// Rouble
					.replace(/\([рp]\)/ig, function(){
						caretPosition -= 2;
						return "₽";
					})

					// Three dots
					.replace(/\.{3}/g, function(){
						caretPosition -= 2;
						return "…";
					})

					// Sizes
					.replace(/(\d)[xх](\d)/ig, "$1×$2")

					// Open quote
					.replace(/\"([a-zа-я0-9…])/ig,
					         options.leftQuote + "$1")

					// Close quote
					.replace(/([a-zа-я0-9…?!])\"/ig,
					         "$1" + options.rightQuote)

					// Open quote
					.replace(new RegExp("\"(" + options.leftQuote + "[a-zа-я0-9…])", "ig"),
					         options.leftQuote + "$1")

					// Close quote
					.replace(new RegExp("([a-zа-я0-9…?!]" + options.rightQuote + ")\"", "ig"),
					         "$1" + options.rightQuote)

					// Fix HTML open quotes
					.replace(new RegExp("([-a-z0-9]+=)" +
					                    "["   + options.leftQuote + options.rightQuote + "]" +
					                    "([^" + options.leftQuote + options.rightQuote + "]*?)", "ig"),
					         "$1\"$2")

					// Fix HTML close quotes
					.replace(new RegExp("([-a-z0-9]+=)[\"]" +
					                    "([^" + options.leftQuote + options.rightQuote + "]*?)" +
					                    "["   + options.leftQuote + options.rightQuote + "]", "ig"),
					         "$1\"$2\"");

				textField.val(text);

				if (!exclude) {
					setCaretPosition(obj, caretPosition);
				}
			}

		});

		function getCaretPosition(ctrl) {
			var CaretPos = 0, // IE Support
			    Sel;

			if (document.selection) {
				ctrl.focus();
				Sel = document.selection.createRange();
				Sel.moveStart('character', -ctrl.value.length);
				CaretPos = Sel.text.length;
			}

			// Firefox support
			else if (ctrl.selectionStart || ctrl.selectionStart == '0') {
				CaretPos = ctrl.selectionStart;
			}

			return CaretPos;
		}

		function setCaretPosition(ctrl, pos){
			if (ctrl.setSelectionRange) {
				ctrl.focus();
				ctrl.setSelectionRange(pos, pos);
			} else if (ctrl.createTextRange) {
				var range = ctrl.createTextRange();
				range.collapse(true);
				range.moveEnd("character", pos);
				range.moveStart("character", pos);
				range.select();
			}
		}

		function getSelectionText(){
			var t = "";

			if (window.getSelection) {
				t = window.getSelection();
			} else if (document.getSelection) {
				t = document.getSelection();
			} else if (document.selection) {
				t = document.selection.createRange().text;
			}

			return t;
		}
	};
})(jQuery);<commit_msg>Add replace for predlogi<commit_after>/*
	jQuery.Typograf.js

	https://github.com/Spearance/jQuery.Typograf.js
	http://www.typograf.ru/

	Copyright 2015, Evgeniy Lepeshkin

	Released under the MIT license.
	http://www.opensource.org/licenses/mit-license.php

	Version: v 0.1
	Date: Thu Mar 24, 2015
 */

(function($){
	$.fn.typograf = function(options){
		options = $.extend({
			leftQuote: "«",
			rightQuote: "»",
			handler: "",
			restor: ""
		}, options);

		var predlogi = [
		    'a', 'for', 'if', 'in', 'of', 'on', 'or', 'the',
	     'A', 'For', 'If', 'In', 'Of', 'On', 'Or', 'The',
 		    'а', 'без', 'в', 'для', 'до', 'за', 'и', 'из', 'из-за', 'из-под', 'к', 'на', 'над', 'не', 'ну', 'о', 'об', 'от', 'по', 'под', 'при', 'про', 'с', 'у',
		    'А', 'Без', 'В', 'Для', 'До', 'За', 'И', 'Из', 'Из-за', 'Из-под', 'К', 'На', 'Над', 'Не', 'Ну', 'О', 'Об', 'От', 'По', 'Под', 'При', 'Про', 'С', 'У'
	 ];

	 String.prototype.replacePredlogi = replacePredlogi();

		return this.each(function(){
			var $this = $(this),
			    obj = $this.get(0),
			    exclude = false,
			    selected = false,
			    caretPosition = 0,
			    original;

			// exist Typograf handler
			if (options.handler) {
				original = "";

				$(options.handler).click(function(){
					if (original == "") {
						original = $this.val();
					}

					if (original) {
						if (options.restore) {
							$(options.restore).removeAttr("disabled");
						}
						typograf($this);
					}
				});

				// exist Restore handler
				if (options.restore) {
					$(options.restore).click(function(){
						$(this).attr("disabled", "true");
						$this.val(original);
					});
				}
			} else {
				// auto
				$this
					.keydown(function(e){
						// check for Ctrl/⌘
						exclude = Boolean(
							(e.metaKey || e.ctrlKey) &&
							!(e.altKey && e.ctrlKey) // Do not exclude Alt Gr

						);
					})
					.keyup(function(e){
						// check for selected text
						selected = Boolean(getSelectionText() != "");

						// exclude arrows and Ctrl/⌘ + A/C/V/X/Z
						if (
							// home, end, page up, page down
							e.which != "33" &&
							e.which != "34" &&
							e.which != "35" &&
							e.which != "36" &&

							// arrows
							e.which != "37" &&
							e.which != "38" &&
							e.which != "39" &&
							e.which != "40" &&

							// single Shift
							e.which != "16" &&

							// select all, copy, paste, cut, undo
							!(exclude && (e.which == "65" ||
							              e.which == "67" ||
							              e.which == "86" ||
							              e.which == "88" ||
							              e.which == "90")) &&

							// selected text
							!selected
						){
							typograf($this);
						}
						return true;
					}
				);
			}

			function typograf (textField){
				var text = textField.val();
				caretPosition = getCaretPosition(obj);
				String.prototype.replacePredlogi = replacePredlogi();

				text = text
					// Minus
					.replace(/\u0020-(\d)/g, "\u0020−$1")

					// Dash
					.replace(/(^|\n|\s|>)\-(\s)/g, "$1—$2")

					// Double hyphen
					.replace(/\-{2} /g, function(){
						caretPosition--;
						return "— ";
					})

					// Multiple nbsp
					.replace(/\u00a0{2,}|\u00a0\u0020|\u0020\u00a0/g, function(str){
						caretPosition -= str.length - 1;
						return "\u00a0";
					})

					// HTML-comment
					.replace(/<!—/ig, function(){
						caretPosition++;
						return "<!--";
					})

					// Numerical interval
					.replace(/(\d)(\u0020)?[-—](\u0020)?(\d)/g, function(str, $1, $2, $3, $4){
						if ($2 == "\u0020") { caretPosition-- }
						if ($3 == "\u0020") { caretPosition-- }
						return $1 + "–" + $4;
					})

					// Copyright
					.replace(/\([cс]\)/ig, function(){
						caretPosition -= 2;
						return "©";
					})

					// Registered trademark
					.replace(/\(r\)/ig, function(){
						caretPosition -= 2;
						return "®";
					})

					// Trademark
					.replace(/\(tm\)/ig, function(){
						caretPosition -= 2;
						return "™";
					})

					// Rouble
					.replace(/\([рp]\)/ig, function(){
						caretPosition -= 2;
						return "₽";
					})

					// Three dots
					.replace(/\.{3}/g, function(){
						caretPosition -= 2;
						return "…";
					})

					// Sizes
					.replace(/(\d)[xх](\d)/ig, "$1×$2")

					// Predlogi
					.replacePredlogi()

					// Open quote
					.replace(/\"([a-zа-я0-9…])/ig,
					         options.leftQuote + "$1")

					// Close quote
					.replace(/([a-zа-я0-9…?!])\"/ig,
					         "$1" + options.rightQuote)

					// Open quote
					.replace(new RegExp("\"(" + options.leftQuote + "[a-zа-я0-9…])", "ig"),
					         options.leftQuote + "$1")

					// Close quote
					.replace(new RegExp("([a-zа-я0-9…?!]" + options.rightQuote + ")\"", "ig"),
					         "$1" + options.rightQuote)

					// Fix HTML open quotes
					.replace(new RegExp("([-a-z0-9]+=)" +
					                    "["   + options.leftQuote + options.rightQuote + "]" +
					                    "([^" + options.leftQuote + options.rightQuote + "]*?)", "ig"),
					         "$1\"$2")

					// Fix HTML close quotes
					.replace(new RegExp("([-a-z0-9]+=)[\"]" +
					                    "([^" + options.leftQuote + options.rightQuote + "]*?)" +
					                    "["   + options.leftQuote + options.rightQuote + "]", "ig"),
					         "$1\"$2\"");

				textField.val(text);

				if (!exclude) {
					setCaretPosition(obj, caretPosition);
				}
			}

		});

		function getCaretPosition(ctrl) {
			var CaretPos = 0, // IE Support
			    Sel;

			if (document.selection) {
				ctrl.focus();
				Sel = document.selection.createRange();
				Sel.moveStart('character', -ctrl.value.length);
				CaretPos = Sel.text.length;
			}

			// Firefox support
			else if (ctrl.selectionStart || ctrl.selectionStart == '0') {
				CaretPos = ctrl.selectionStart;
			}

			return CaretPos;
		}

		function setCaretPosition(ctrl, pos){
			if (ctrl.setSelectionRange) {
				ctrl.focus();
				ctrl.setSelectionRange(pos, pos);
			} else if (ctrl.createTextRange) {
				var range = ctrl.createTextRange();
				range.collapse(true);
				range.moveEnd("character", pos);
				range.moveStart("character", pos);
				range.select();
			}
		}

		function getSelectionText(){
			var t = "";

			if (window.getSelection) {
				t = window.getSelection();
			} else if (document.getSelection) {
				t = document.getSelection();
			} else if (document.selection) {
				t = document.selection.createRange().text;
			}

			return t;
		}

		function replacePredlogi(){
			var self = this;

			predlogi.foreach(function(p){
			    self.replace(new Regexp("\s" + p + "\u0020", "g"), "$1" + p + "\u00a0");
			});

			return self;
		}
	};
})(jQuery);<|endoftext|>


<commit_before>/*
    Extends jquery to allow element traversal relative to the current viewport.  using a snapshot of the viewport
    at the time the function is called it compares the exisiting collection of jquery elements to that current instance
    of the snapshot.
*/
(function (jQuery) {
    // get the elements in the collection that are below the current viewport

    var getSelection = function(selector) {
        var matched = this;
        if (selector && typeof selector === 'string') {
            matched = $.find(selector, matched);
        }
        return matched;
    };

    jQuery.fn.aboveViewport = (function (selector) {
        var viewTop = $(window).scrollTop(),
            matched = getSelection.call(this, selector);

        matched = $.grep(matched, function (el) {
            el = $(el);
            return viewTop > (el.offset().top + el.height());
        });

        return this.pushStack(matched);
    });

    // get the elements in the collection that are above the current viewport
    jQuery.fn.belowViewport = (function (selector) {
        var viewBottom = $(window).scrollTop() + $(window).height(),
            matched = getSelection.call(this, selector);

        matched = $.grep(matched, function (el) {
            return viewBottom < $(el).offset().top;
        });
        return this.pushStack(matched);
    });

    // get the elements in the collection that are outside the current viewport
    jQuery.fn.outsideViewport = (function (selector) {
        var viewTop = $(window).scrollTop(),
            viewBottom = viewTop + $(window).height(),
            matched = getSelection.call(this, selector);

        matched = $.grep(matched, function (el) {
            var el = $(el);
            return viewBottom < el.offset().top || viewTop > (el.offset().top + el.height());
        });

        return this.pushStack(matched);
    });

    // get the elements in the collection that are outside the current viewport
    jQuery.fn.insideViewport = (function (selector) {
        var viewTop = $(window).scrollTop(),
            viewBottom = viewTop + $(window).height(),
            matched = getSelection.call(this, selector);

        matched = $.grep(matched, function (el) {
            var top = $(el).offset().top,
                bottom = top + $(el).height();
            return (viewTop < top && viewBottom > top) || (viewBottom < bottom && viewTop > bottom);

        });

        return this.pushStack(matched);
    });

})(jQuery);
<commit_msg>updated top comment<commit_after>/*
    Extends jquery to allow element traversal relative to the current viewport.  using a snapshot of the viewport
    at the time the function is called it compares the exisiting collection of jquery elements to that snapshot.
*/
(function (jQuery) {
    // get the elements in the collection that are below the current viewport

    var getSelection = function(selector) {
        var matched = this;
        if (selector && typeof selector === 'string') {
            matched = $.find(selector, matched);
        }
        return matched;
    };

    jQuery.fn.aboveViewport = (function (selector) {
        var viewTop = $(window).scrollTop(),
            matched = getSelection.call(this, selector);

        matched = $.grep(matched, function (el) {
            el = $(el);
            return viewTop > (el.offset().top + el.height());
        });

        return this.pushStack(matched);
    });

    // get the elements in the collection that are above the current viewport
    jQuery.fn.belowViewport = (function (selector) {
        var viewBottom = $(window).scrollTop() + $(window).height(),
            matched = getSelection.call(this, selector);

        matched = $.grep(matched, function (el) {
            return viewBottom < $(el).offset().top;
        });
        return this.pushStack(matched);
    });

    // get the elements in the collection that are outside the current viewport
    jQuery.fn.outsideViewport = (function (selector) {
        var viewTop = $(window).scrollTop(),
            viewBottom = viewTop + $(window).height(),
            matched = getSelection.call(this, selector);

        matched = $.grep(matched, function (el) {
            var el = $(el);
            return viewBottom < el.offset().top || viewTop > (el.offset().top + el.height());
        });

        return this.pushStack(matched);
    });

    // get the elements in the collection that are outside the current viewport
    jQuery.fn.insideViewport = (function (selector) {
        var viewTop = $(window).scrollTop(),
            viewBottom = viewTop + $(window).height(),
            matched = getSelection.call(this, selector);

        matched = $.grep(matched, function (el) {
            var top = $(el).offset().top,
                bottom = top + $(el).height();
            return (viewTop < top && viewBottom > top) || (viewBottom < bottom && viewTop > bottom);

        });

        return this.pushStack(matched);
    });

})(jQuery);
<|endoftext|>


<commit_before>import { h, Component } from 'preact' /** @jsx h */
import { ViewPager, Frame, Track, View, AnimatedView } from 'react-view-pager'
import {
  ButtonPrimary,
  MemorySummary,
  Quote,
  SectionHeader,
  SectionPrimary,
} from '~src/views/components'
import * as paths from '~src/paths'
import './slider.scss'

const Memory = ({ memory }) => (

    <MemorySummary
      name={memory.victimName}
      owner={memory.ownerFirstName}
      birthYear={memory.victimBornAt}
      deathYear={memory.victimDeadAt}
      description={memory.victimHistory}
      image={`${process.env.SERVER_DOMAIN}${memory.victimPhoto}`}
      imageWidth="164px"
      imageHeight="132px"
      imageAlignment="left"
      distanceY="100px"
      distanceX="36%"
      width="63%"
    />
    <Quote>
      {memory.victimRememberText}
    </Quote>
    <ButtonPrimary href={paths.memory(memory.id)}>
      Ver homenagem completa
    </ButtonPrimary>

)

class FeaturedMemoriesSection extends Component {
  constructor(props) {
    super(props)
    this.state = {
      currentView: 0,
    }
  }

  //
  // This method should not exists when we have some fetching tool
  // like redial, to fetch the data on ssr.
  //
  componentWillReceiveProps(nextProps) {
    const str = JSON.stringify
    if (str(this.props.memories) !== str(nextProps.memories)) {
      this.props.setListCarousel(nextProps.memories)
    }
  }

  render() {
    const { loading, memories, className } = this.props

    return loading ? null : (
      <SectionPrimary
        className={`section--featured-memories ${className}`}
        header={<SectionHeader title="Homenagens" />}
      >
        <ViewPager>
          <Frame className="frame" autoSize="height">
            <Track
              infinite
              viewsToShow={1}
              className="track"
              ref={(track) => { this.track = track }}
              onViewChange={(indicies) => {
                this.setState({ currentView: indicies[0] })
              }}
            >
              {memories.map(memory => (
                <View className="view">

                      <div
                        className="image"
                        style={{
                          backgroundImage: `url('${process.env.SERVER_DOMAIN}${memory.victimPhoto}')`,
                          width: 164,
                          height: 132,
                          left: 0,
                        }}
                      />

{memory.victimName}

                            {memory.victimBornAt} / {memory.victimDeadAt}

{memory.ownerFirstName}

                            {memory.victimHistory}

                      {memory.victimRememberText}

                      {Array(memories.length).fill('').map((element, index) => (
                        <button
                          type="button"
                          className={`pager-control ${this.state.currentView === index ? 'active' : ''}`}
                          onClick={() => this.track.scrollTo(index)}
                        >
                          {index + 1}
                        </button>
                      ))}

                      Ver homenagem completa

                </View>
              ))}
            </Track>
          </Frame>
        </ViewPager>
      </SectionPrimary>
    )
  }
}

FeaturedMemoriesSection.defaultProps = {
  className: '',
}

export default FeaturedMemoriesSection
<commit_msg>chore(home): pager controls  with fixed position close #89<commit_after>import { h, Component } from 'preact' /** @jsx h */
import { ViewPager, Frame, Track, View, AnimatedView } from 'react-view-pager'
import {
  ButtonPrimary,
  MemorySummary,
  Quote,
  SectionHeader,
  SectionPrimary,
} from '~src/views/components'
import * as paths from '~src/paths'
import './slider.scss'

const Memory = ({ memory }) => (

    <MemorySummary
      name={memory.victimName}
      owner={memory.ownerFirstName}
      birthYear={memory.victimBornAt}
      deathYear={memory.victimDeadAt}
      description={memory.victimHistory}
      image={`${process.env.SERVER_DOMAIN}${memory.victimPhoto}`}
      imageWidth="164px"
      imageHeight="132px"
      imageAlignment="left"
      distanceY="100px"
      distanceX="36%"
      width="63%"
    />
    <Quote>
      {memory.victimRememberText}
    </Quote>
    <ButtonPrimary href={paths.memory(memory.id)}>
      Ver homenagem completa
    </ButtonPrimary>

)

class FeaturedMemoriesSection extends Component {
  constructor(props) {
    super(props)
    this.state = {
      currentView: 0,
    }
  }

  //
  // This method should not exists when we have some fetching tool
  // like redial, to fetch the data on ssr.
  //
  componentWillReceiveProps(nextProps) {
    const str = JSON.stringify
    if (str(this.props.memories) !== str(nextProps.memories)) {
      this.props.setListCarousel(nextProps.memories)
    }
  }

  render() {
    const { loading, memories, className } = this.props

    return loading ? null : (
      <SectionPrimary
        className={`section--featured-memories ${className}`}
        header={<SectionHeader title="Homenagens" />}
      >
        <ViewPager>
          <Frame className="frame" autoSize="height">
            <Track
              infinite
              viewsToShow={1}
              className="track"
              ref={(track) => { this.track = track }}
              onViewChange={(indicies) => {
                this.setState({ currentView: indicies[0] })
              }}
            >
              {memories.map(memory => (
                <View className="view">

                      <div
                        className="image"
                        style={{
                          backgroundImage: `url('${process.env.SERVER_DOMAIN}${memory.victimPhoto}')`,
                          width: 164,
                          height: 132,
                          left: 0,
                        }}
                      />

{memory.victimName}

                            {memory.victimBornAt} / {memory.victimDeadAt}

{memory.ownerFirstName}

                            {memory.victimHistory}

                      {memory.victimRememberText}

                </View>
              ))}
            </Track>
          </Frame>

            {Array(memories.length).fill('').map((element, index) => (
              <button
                type="button"
                className={`pager-control ${this.state.currentView === index ? 'active' : ''}`}
                onClick={() => this.track.scrollTo(index)}
              >
                {index + 1}
              </button>
            ))}

          <a
            type="button"
            className="button-primary"
            href={`/memory/${memories[this.state.currentView].id}`}
          >
            Ver homenagem completa

        </ViewPager>
      </SectionPrimary>
    )
  }
}

FeaturedMemoriesSection.defaultProps = {
  className: '',
}

export default FeaturedMemoriesSection
<|endoftext|>


<commit_before>Ext.define('devilry_student.view.browse.BrowseCalendar' ,{
    extend: 'Ext.container.Container',
    alias: 'widget.browsecalendar',
    cls: 'devilry_student_browsecalendar',

    autoScroll: true,
    layout: 'fit',

    items: [{
        xtype: 'box',
        html: 'TODO: calendar view'
    }]
});
<commit_msg>devilry_student: Removed unused BrowseCalendar.<commit_after><|endoftext|>


<commit_before>/* Copyright 2012-2015, Marc S. Brooks (https://mbrooks.info) */
eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(3($){4 h,q,r,l,o,w,F;4 j={V:3(f){4 g=$.1k({W:1l,G:X},f);s m.H(3(){4 a=$(m),2=a.2();7($.1m(2)){h=a;q=a.6(\'.1n\');r=a.6(\'.Y\');l=a.6(\'.Z\');o=a.6(\'.1o\');w=a.6(\'.11\');F=a.6(\'.1p\');4 b=g.G.1q;4 c=X;1r{$.k.12();c=1s 1t(b)}1u(1v){}7(c&&c.1w(\'1x/1y\')){$.k.I();a.2({5:c,J:g});a.x(\'K\')}n{4 d=13(10);4 e=$(\'<L></L>\').M(\'1z\',d);a.1A(e);1B.1C(\'1D-1E.1F\',d,\'0\',\'0\',\'9.0\',\'\',g.G,\'1G\',\'\',3(){a.2({5:1H.1I[d],t:N,J:g});a.x(\'K\')})}}})},1J:3(){s m.H(3(){$(m).1K()})},K:3(){s m.H(3(){4 f=$(m),2=f.2();l.M(\'y\',\'N\');F.1L({1M:\'14\',14:1,1N:z,p:2.J.W,1O:3(e,a){4 b=1P(a.p/z);7(2.t){2.5.A(\'15\',b)}n{2.5.15=b}}});q.B(3(){7(2.t){2.5.A(\'O\')}n{2.5.O()}16()});r.B(3(){7(2.t){2.5.A(\'C\')}n{2.5.C()}17()});l.B(3(){7(2.t){2.5.A(\'D\')}n{2.5.O();2.5.E=0}P()});$(2.5).18(\'1Q\',3(){4 a=2.5;4 b=u.Q(a.E/R)%R;4 c=u.Q(a.E%R);4 d=b+\':\'+(c<10?\'0\':\'\')+c;4 e=u.1R(a.E/a.11*z);7(e==z){l.1S(\'B\')}n{$.k.19(d,e)}});$(2.5).18(\'1T\',3(){$.k.I()})})}};$.k.x=3(a){7(j[a]){s j[a].1a(m,1U.1V.1W.1X(1b,1))}n 7(1Y a===\'L\'||!a){s j.V.1a(m,1b)}n{$.1Z(\'20 \'+a+\' 21 22 23 24 1c.x\')}};$.k.I=3(){h.1d(\'1e\');h.6(\'.Y, .Z, 1f, 1g\').25(\'26\')};$.k.12=3(a){h.27(\'1e\');h.6(\'28, 1f, 1g\').29({2a:\'2b\'})};$.k.2c=3(){o.v({p:0});w.1h(\'0:2d\');P()};$.k.19=3(a,b){o.v({p:b});w.1h(a)};3 16(){q.S(0);r.T(0);l.1i(\'y\');o.6(\'.U-v-p\').1j(\'C\',\'D\')}3 17(){r.S(0);q.T(0);l.1i(\'y\');o.6(\'.U-v-p\').1j(\'D\',\'C\')}3 P(){q.S(0);r.T(0);l.M(\'y\',\'N\');o.6(\'.U-v-p\').1d(\'D\')}3 13(a){4 b=(a)?a:8;4 c=\'2e\'.2f(\'\');4 d=\'\';2g(4 i=0;i<b;i++){d+=c[u.Q(u.2h()*c.2i)]}s d}})(1c);',62,143,'||data|function|var|soundObj|children|if|||||||||||||fn|buttonStop|this|else|progressBar|value|buttonPause|buttonPlay|return|useFlash|Math|progressbar|duraTimer|MP3Flashback|disabled|100|player|click|play|stop|currentTime|volumeBar|tracks|each|loadComplete|options|setup|object|attr|true|pause|buttonStopEvent|floor|60|hide|show|ui|init|volumeStart|null|button_play|button_stop||duration|loadProgress|genRandStr|min|volume|buttonPauseEvent|buttonPlayEvent|bind|playProgress|apply|arguments|jQuery|removeClass|loading|div|span|html|removeAttr|switchClass|extend|70|isEmptyObject|button_pause|progress_bar|volume_bar|file0|try|new|Audio|catch|err|canPlayType|audio|mp3|id|append|swfobject|embedSWF|MP3|Flashback|swf|transparent|window|document|destroy|removeData|slider|range|max|slide|parseFloat|timeupdate|ceil|trigger|canplay|Array|prototype|slice|call|typeof|error|Method|does|not|exist|on|fadeIn|slow|addClass|button|css|display|none|playComplete|00|abcdefghiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXTZ0123456789|split|for|random|length'.split('|'),0,{}))
<commit_msg>Packed new release, and likely the last<commit_after>/* Copyright 2012-2015, Marc S. Brooks (https://mbrooks.info) */
eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('h(!w.o||(w.o&&11(w.o.j.1v.12(\'.\',\'\'))<11(\'1.8.3\'.12(\'.\',\'\')))){1w 13 1x(\'14-15 1y o 1.8.3 1z 1A.\');}(4($){6 f,p,q,k,m,x,I;6 g={"16":4(a){6 b=$(r),2=b.2();6 c=$.1B({17:1C,J:18},a);h($.1D(2)){f=b;p=b.7(\'.1E\');q=b.7(\'.19\');k=b.7(\'.1a\');m=b.7(\'.1F\');x=b.7(\'.y\');I=b.7(\'.1G\');6 d=c.J.1H,s=18;1I{$.j.1b();s=13 1J(d)}1K(1L){}h(s&&s.1M(\'1N/1O\')){$.j.K();b.2({5:s,L:c});b.z(\'M\')}l{6 e=1c(10),1d=$(\'<N></N>\').1P(\'1Q\',e);b.1R(1d);1S.1T(\'14-15.1U\',e,\'0\',\'0\',\'9.0\',\'\',c.J,\'1V\',\'\',4(){b.2({5:w.1W[e],t:O,L:c});b.z(\'M\')})}}},"1X":4(){$(r).1Y()},"M":4(){6 d=$(r),2=d.2();k.1e(\'A\',O);I.1Z({20:\'1f\',1f:1,21:B,n:2.L.17,22:4(a,b){6 c=23(b.n/B);h(2.t){2.5.C(\'1g\',c)}l{2.5.1g=c}}});p.D(4(){h(2.t){2.5.C(\'P\')}l{2.5.P()}1h()});q.D(4(){h(2.t){2.5.C(\'E\')}l{2.5.E()}1i()});k.D(4(){h(2.t){2.5.C(\'F\')}l{2.5.P();2.5.G=0}Q()});$(2.5).1j(\'24\',4(){6 a=2.5,1k=u.R(a.G/S)%S,T=u.R(a.G%S),y=1k+\':\'+(T<10?\'0\':\'\')+T,U=u.25(a.G/a.y*B);h(U==B){k.26(\'D\')}l{$.j.1l(y,U)}});$(2.5).1j(\'27\',4(){$.j.K()})}};$.j.z=4(a){h(g[a]){V g[a].1m(r,28.29.2a.2b(1n,1))}l h(2c a===\'N\'||!a){V g.16.1m(r,1n)}l{$.2d(\'2e \'+a+\' 2f 2g 2h 2i o.z\')}};$.j.K=4(){f.1o(\'1p\');f.7(\'.19, .1a, 1q, 1r\').2j(\'2k\')};$.j.1b=4(){f.2l(\'1p\');f.7(\'2m, 1q, 1r\').H(0)};$.j.2n=4(){m.v({n:0});x.1s(\'0:2o\');Q()};$.j.1l=4(a,b){m.v({n:b});x.1s(a)};4 1h(){p.H(0);q.W(0);k.1t(\'A\');m.7(\'.X-v-n\').1u(\'E\',\'F\')}4 1i(){q.H(0);p.W(0);k.1t(\'A\');m.7(\'.X-v-n\').1u(\'F\',\'E\')}4 Q(){p.H(0);q.W(0);k.1e(\'A\',O);m.7(\'.X-v-n\').1o(\'F\')}4 1c(a){6 b=(a)?a:8,Y=\'2p\'.2q(\'\'),Z=\'\';2r(6 i=0;i<b;i++){Z+=Y[u.R(u.2s()*Y.2t)]}V Z}})(o);',62,154,'||data||function|soundObj|var|children||||||||||if||fn|buttonStop|else|progressBar|value|jQuery|buttonPause|buttonPlay|this|audioObj|useFlash|Math|progressbar|window|duraTimer|duration|MP3Flashback|disabled|100|player|click|play|stop|currentTime|hide|volumeBar|tracks|loadComplete|options|_createPlayer|object|true|pause|buttonStopEvent|floor|60|seconds|percent|return|show|ui|chars|str||parseInt|replace|new|MP3|Flashback|init|volumeStart|null|button_play|button_stop|loadProgress|genRandStr|flashObj|prop|min|volume|buttonPauseEvent|buttonPlayEvent|bind|minutes|playProgress|apply|arguments|removeClass|loading|div|span|html|removeAttr|switchClass|jquery|throw|Error|requires|or|greater|extend|70|isEmptyObject|button_pause|progress_bar|volume_bar|file0|try|Audio|catch|err|canPlayType|audio|mp3|attr|id|append|swfobject|embedSWF|swf|transparent|document|destroy|removeData|slider|range|max|slide|parseFloat|timeupdate|ceil|trigger|canplay|Array|prototype|slice|call|typeof|error|Method|does|not|exist|on|fadeIn|slow|addClass|button|playComplete|00|abcdefghiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXTZ0123456789|split|for|random|length'.split('|'),0,{}));
<|endoftext|>


<commit_before>import React from 'react'
import ReactDOM from 'react-dom'
import axios from 'axios'
import { GDQ_STORAGE_ENDPOINT, PRIMARY_COLOR, SECONDARY_COLOR } from './constants'
import { BarChart, Bar, LineChart, Line, Tooltip, ResponsiveContainer, XAxis, YAxis, CartesianGrid } from 'recharts'
import C3Chart from 'react-c3js'
import 'c3/c3.css'
import PacmanLoader from 'halogen/PacmanLoader'
import VerticalLabel from './components/VerticalLabel'
import ReturnHome from './components/ReturnHome'
import { format } from 'd3-format'
import Grid from 'react-bootstrap/lib/Grid'
import Col from 'react-bootstrap/lib/Col'
import Row from 'react-bootstrap/lib/Col'
import Stat from './components/Stat'
import moment from 'moment'

class DonationsApp extends React.PureComponent {
  constructor (props) {
    super(props)
    this.state = {}
  }

  componentWillMount () {
    // Fetch donation stats
    axios.get(GDQ_STORAGE_ENDPOINT + '/donation_stats.json')
      .then((response) => this.setState({ donationStats: response.data }))

    // Fetch donation words
    axios.get(GDQ_STORAGE_ENDPOINT + '/donation_words.json')
      .then((response) => this.setState({ donationWords: response.data }))

    // Fetch top donors
    axios.get(GDQ_STORAGE_ENDPOINT + '/top_donors.json')
      .then((response) => this.setState({ topDonors: response.data }))
  }

  getLoader () {
    return <PacmanLoader color={PRIMARY_COLOR} className='graph-loader' />
  }

  getCommentCountPieChart () {
    if (!this.state.donationStats) {
      return this.getLoader()
    }
    let columns = []
    this.state.donationStats.comment_stats.forEach((stat) => {
      const columnArr = [stat.has_comment ? 'commented' : 'uncommented']
      columnArr.push(stat.count)
      columns.push(columnArr)
    })
    const c3Data = {
      columns: columns,
      type: 'donut',
      names: {
        commented: 'Has Comment',
        uncommented: 'No Comment'
      },
      colors: {
        commented: PRIMARY_COLOR,
        uncommented: SECONDARY_COLOR
      }
    }
    const c3Donut = {
      label: {
        format: (value) => format(',.0f')(value)
      },
      width: 60,
      title: 'Number of Donations'
    }
    return (
      <C3Chart data={c3Data} donut={c3Donut} />
    )
  }

  getAnonymousCountPieChart () {
    if (!this.state.donationStats) {
      return this.getLoader()
    }
    let columns = []
    this.state.donationStats.anonymous.forEach((stat) => {
      const columnArr = [stat.anonymous ? 'anonymous' : 'signed']
      columnArr.push(stat.count)
      columns.push(columnArr)
    })
    const c3Data = {
      columns: columns,
      type: 'donut',
      names: {
        signed: 'Signed',
        anonymous: 'Anonymous',
      },
      colors: {
        anonymous: SECONDARY_COLOR,
        signed: PRIMARY_COLOR
      }
    }
    const c3Donut = {
      label: {
        format: (value) => format(',.0f')(value)
      },
      width: 60,
      title: 'Number of Donations'
    }
    return (
      <C3Chart data={c3Data} donut={c3Donut} />
    )
  }

  getAnonymousSumPieChart () {
    if (!this.state.donationStats) {
      return this.getLoader()
    }
    let columns = []
    this.state.donationStats.anonymous.forEach((stat) => {
      const columnArr = [stat.anonymous ? 'anonymous' : 'signed']
      columnArr.push(stat.sum)
      columns.push(columnArr)
    })
    const c3Data = {
      columns: columns,
      type: 'donut',
      names: {
        signed: 'Signed',
        anonymous: 'Anonymous'
      },
      colors: {
        anonymous: SECONDARY_COLOR,
        signed: PRIMARY_COLOR
      }
    }
    const c3Donut = {
      label: {
        format: (value) => format('$,.2s')(value)
      },
      width: 60,
      title: 'Total Donations'
    }
    return (
      <C3Chart data={c3Data} donut={c3Donut} />
    )
  }

  getCommentSumPieChart () {
    if (!this.state.donationStats) {
      return this.getLoader()
    }
    let columns = []
    this.state.donationStats.comment_stats.forEach((stat) => {
      const columnArr = [stat.has_comment ? 'commented' : 'uncommented']
      columnArr.push(stat.sum)
      columns.push(columnArr)
    })
    const c3Data = {
      columns: columns,
      type: 'donut',
      names: {
        commented: 'Has Comment',
        uncommented: 'No Comment'
      },
      colors: {
        commented: PRIMARY_COLOR,
        uncommented: SECONDARY_COLOR
      }
    }
    const c3Donut = {
      label: {
        format: (value) => format('$,.2s')(value)
      },
      width: 60,
      title: 'Total Donations'
    }
    return (
      <C3Chart data={c3Data} donut={c3Donut} />
    )
  }

  getDonationStatContainer () {
    if (!this.state.donationStats) {
      return this.getLoader()
    }
    const [ commented, uncommented ] = this.state.donationStats.comment_stats
    const [ anonymous, named ] = this.state.donationStats.anonymous
    const stats = [
      { title: 'Median Donation w/ Comment', emoji: '🗣', value: commented.median },
      { title: 'Median Donation w/o Comment', emoji: '🙊', value: uncommented.median },
      { title: 'Median Donation - Anonymous', emoji: '❓', value: anonymous.median },
      { title: 'Average Donation w/ Comment', emoji: '🗣', value: commented.avg },
      { title: 'Average Donation w/o Comment', emoji: '🙊', value: uncommented.avg },
      { title: 'Average Donation - Anonymous', emoji: '❓', value: anonymous.avg }
    ]
    return stats.map((props, idx) => {
      return <Stat key={idx} {...props} prefix='$' value={+(props.value.toFixed(2))} format='(,ddd).dd' />
    })
  }

  getOverallDonationStatConatainer () {
    if (!this.state.donationStats) {
      return null
    }
    const overall = this.state.donationStats.overall[0]
    const overall_stats = [
      { title: 'Median Donation - Overall', emoji: '💸', value: overall.median },
      { title: 'Average Donation - Overall', emoji: '💸', value: overall.avg },
    ]
    return overall_stats.map((props, idx) => {
      return <Stat key={idx} {...props} prefix='$' value={+(props.value.toFixed(2))} format='(,ddd).dd' />
    })
  }

  getMediansChart () {
    if (!this.state.donationStats) {
      return this.getLoader()
    }
    const medians = this.state.donationStats.medians.map((obj) => {
      return {...obj, time: moment(obj.time).unix()}
    })
    return (
      <ResponsiveContainer width='100%' height={500}>
        <LineChart data={medians} margin={{top: 20}}>
          <Line
            type='basis'
            dataKey='median'
            name='Median Donation Amount'
            stroke={PRIMARY_COLOR}
            strokeWidth={1.5}
            dot={false}
            activeDot />
          <YAxis
            dataKey='median'
            tickFormatter={format('$,')}
            axisLine={{stroke: '#ddd'}}
            tickLine={{stroke: '#ddd'}}
            tick={{fill: '#333', fontWeight: 300, fontSize: 13}}
            domain={[0, 'dataMax']}
            interval='preserveStartEnd'
            minTickGap={0} />
          <Tooltip
            formatter={format('$,.2f')}
            labelFormatter={(d) => moment.unix(d).format('ddd, MMM Do YYYY, h:mm a')} />
          <XAxis
            dataKey='time'
            type='number'
            scale='time'
            axisLine={{stroke: '#ddd'}}
            tickLine={{stroke: '#ddd'}}
            tickFormatter={(d) => moment.unix(d).format('ddd, hA')}
            tick={{fill: '#333', fontWeight: 300, fontSize: 13}}
            interval='preserveStart'
            domain={['dataMin', 'dataMax']}
            minTickGap={50} />
        </LineChart>
      </ResponsiveContainer>
    )
  }

  getDonationWordsChart () {
    if (!this.state.donationWords) {
      return this.getLoader()
    }
    const yAxisLabel = (
      <VerticalLabel
        axisType='yAxis'
        xOffset={0}
        yOffset={275}
        className='recharts-label'>
        Word
      </VerticalLabel>
    )
    return (
      <ResponsiveContainer width='100%' minHeight={800}>
        <BarChart
          margin={{top: 25, left: 50, bottom: 24, right: 24}}
          barSize={10}
          barCategoryGap={2}
          layout='vertical'
          data={this.state.donationWords}>
          <Tooltip />
          <CartesianGrid horizontal={false} />
          <XAxis label={'Number of Uses in Donation Comments'} orientation='top' type='number' />
          <YAxis label={yAxisLabel} interval={0} type='category' dataKey='word' />
          <Bar name='Uses' dataKey='entries' fill={PRIMARY_COLOR} />
        </BarChart>
      </ResponsiveContainer>
    )
  }

  getFrequentDonorsChart () {
    if (!this.state.topDonors) {
      return this.getLoader()
    }
    const yAxisLabel = (
      <VerticalLabel
        axisType='yAxis'
        xOffset={-30}
        yOffset={275}
        className='recharts-label'>
        Donor
      </VerticalLabel>
    )
    return (
      <ResponsiveContainer width='100%' height={800}>
        <BarChart
          margin={{top: 25, left: 60, bottom: 24, right: 24}}
          barSize={10}
          barCategoryGap={2}
          layout='vertical'
          data={this.state.topDonors.frequent}>
          <Tooltip />
          <CartesianGrid horizontal={false} />
          <XAxis label={'Most Frequent Donors'} orientation='top' type='number' />
          <YAxis
            tickFormatter={(t) => t.length < 11 ? t : (t.substring(0, 8) + '...')}
            label={yAxisLabel}
            interval={0}
            type='category'
            dataKey='name' />
          <Bar dataKey='count' name='Number of Donations' fill={PRIMARY_COLOR} />
        </BarChart>
      </ResponsiveContainer>
    )
  }

  getGenerousDonorsChart () {
    if (!this.state.topDonors) {
      return this.getLoader()
    }
    const yAxisLabel = (
      <VerticalLabel
        axisType='yAxis'
        xOffset={-30}
        yOffset={275}
        className='recharts-label'>
        Donor
      </VerticalLabel>
    )
    return (
      <ResponsiveContainer width='100%' height={800}>
        <BarChart
          margin={{top: 25, left: 60, bottom: 24, right: 24}}
          barSize={10}
          barCategoryGap={2}
          layout='vertical'
          data={this.state.topDonors.generous}>
          <Tooltip formatter={format('$,.2f')} />
          <CartesianGrid horizontal={false} />
          <XAxis
            label={'Most Generous Donors'}
            orientation='top'
            type='number'
            tickFormatter={format('$,.2f')} />
          <YAxis
            tickFormatter={(t) => t.length < 11 ? t : (t.substring(0, 8) + '...')}
            label={yAxisLabel}
            interval={0}
            type='category'
            dataKey='name' />
          <Bar name='Donation Total' dataKey='total' fill={PRIMARY_COLOR} />
        </BarChart>
      </ResponsiveContainer>
    )
  }

  render () {
    return (

        <ReturnHome />

Donation Distribution Stats

          <Grid className='current_stats content'>
            {this.getDonationStatContainer()}
            <Row>
              <Col lg={2} md={0}/>
              {this.getOverallDonationStatConatainer()}
            </Row>
          </Grid>

Anonymous Donation Stats

          <Grid>
            <Col md={6} xs={12}>{this.getAnonymousCountPieChart()}</Col>
            <Col md={6} xs={12}>{this.getAnonymousSumPieChart()}</Col>
          </Grid>

Most Commonly Used Words in Donation Comments

          {this.getDonationWordsChart()}

Most Frequent Donors

          {this.getFrequentDonorsChart()}

Most Generous Donors

          {this.getGenerousDonorsChart()}

Donation Comment Stats

          <Grid>
            <Col md={6} xs={12}>{this.getCommentCountPieChart()}</Col>
            <Col md={6} xs={12}>{this.getCommentSumPieChart()}</Col>
          </Grid>

Median Donation Over Time

          <Grid>
            <Col md={12} >{this.getMediansChart()}</Col>
          </Grid>

    )
  }
}

ReactDOM.render(
  <DonationsApp />,
  document.getElementById('react-root')
)
<commit_msg>Fix interpolation method<commit_after>import React from 'react'
import ReactDOM from 'react-dom'
import axios from 'axios'
import { GDQ_STORAGE_ENDPOINT, PRIMARY_COLOR, SECONDARY_COLOR } from './constants'
import { BarChart, Bar, LineChart, Line, Tooltip, ResponsiveContainer, XAxis, YAxis, CartesianGrid } from 'recharts'
import C3Chart from 'react-c3js'
import 'c3/c3.css'
import PacmanLoader from 'halogen/PacmanLoader'
import VerticalLabel from './components/VerticalLabel'
import ReturnHome from './components/ReturnHome'
import { format } from 'd3-format'
import Grid from 'react-bootstrap/lib/Grid'
import Col from 'react-bootstrap/lib/Col'
import Row from 'react-bootstrap/lib/Col'
import Stat from './components/Stat'
import moment from 'moment'

class DonationsApp extends React.PureComponent {
  constructor (props) {
    super(props)
    this.state = {}
  }

  componentWillMount () {
    // Fetch donation stats
    axios.get(GDQ_STORAGE_ENDPOINT + '/donation_stats.json')
      .then((response) => this.setState({ donationStats: response.data }))

    // Fetch donation words
    axios.get(GDQ_STORAGE_ENDPOINT + '/donation_words.json')
      .then((response) => this.setState({ donationWords: response.data }))

    // Fetch top donors
    axios.get(GDQ_STORAGE_ENDPOINT + '/top_donors.json')
      .then((response) => this.setState({ topDonors: response.data }))
  }

  getLoader () {
    return <PacmanLoader color={PRIMARY_COLOR} className='graph-loader' />
  }

  getCommentCountPieChart () {
    if (!this.state.donationStats) {
      return this.getLoader()
    }
    let columns = []
    this.state.donationStats.comment_stats.forEach((stat) => {
      const columnArr = [stat.has_comment ? 'commented' : 'uncommented']
      columnArr.push(stat.count)
      columns.push(columnArr)
    })
    const c3Data = {
      columns: columns,
      type: 'donut',
      names: {
        commented: 'Has Comment',
        uncommented: 'No Comment'
      },
      colors: {
        commented: PRIMARY_COLOR,
        uncommented: SECONDARY_COLOR
      }
    }
    const c3Donut = {
      label: {
        format: (value) => format(',.0f')(value)
      },
      width: 60,
      title: 'Number of Donations'
    }
    return (
      <C3Chart data={c3Data} donut={c3Donut} />
    )
  }

  getAnonymousCountPieChart () {
    if (!this.state.donationStats) {
      return this.getLoader()
    }
    let columns = []
    this.state.donationStats.anonymous.forEach((stat) => {
      const columnArr = [stat.anonymous ? 'anonymous' : 'signed']
      columnArr.push(stat.count)
      columns.push(columnArr)
    })
    const c3Data = {
      columns: columns,
      type: 'donut',
      names: {
        signed: 'Signed',
        anonymous: 'Anonymous',
      },
      colors: {
        anonymous: SECONDARY_COLOR,
        signed: PRIMARY_COLOR
      }
    }
    const c3Donut = {
      label: {
        format: (value) => format(',.0f')(value)
      },
      width: 60,
      title: 'Number of Donations'
    }
    return (
      <C3Chart data={c3Data} donut={c3Donut} />
    )
  }

  getAnonymousSumPieChart () {
    if (!this.state.donationStats) {
      return this.getLoader()
    }
    let columns = []
    this.state.donationStats.anonymous.forEach((stat) => {
      const columnArr = [stat.anonymous ? 'anonymous' : 'signed']
      columnArr.push(stat.sum)
      columns.push(columnArr)
    })
    const c3Data = {
      columns: columns,
      type: 'donut',
      names: {
        signed: 'Signed',
        anonymous: 'Anonymous'
      },
      colors: {
        anonymous: SECONDARY_COLOR,
        signed: PRIMARY_COLOR
      }
    }
    const c3Donut = {
      label: {
        format: (value) => format('$,.2s')(value)
      },
      width: 60,
      title: 'Total Donations'
    }
    return (
      <C3Chart data={c3Data} donut={c3Donut} />
    )
  }

  getCommentSumPieChart () {
    if (!this.state.donationStats) {
      return this.getLoader()
    }
    let columns = []
    this.state.donationStats.comment_stats.forEach((stat) => {
      const columnArr = [stat.has_comment ? 'commented' : 'uncommented']
      columnArr.push(stat.sum)
      columns.push(columnArr)
    })
    const c3Data = {
      columns: columns,
      type: 'donut',
      names: {
        commented: 'Has Comment',
        uncommented: 'No Comment'
      },
      colors: {
        commented: PRIMARY_COLOR,
        uncommented: SECONDARY_COLOR
      }
    }
    const c3Donut = {
      label: {
        format: (value) => format('$,.2s')(value)
      },
      width: 60,
      title: 'Total Donations'
    }
    return (
      <C3Chart data={c3Data} donut={c3Donut} />
    )
  }

  getDonationStatContainer () {
    if (!this.state.donationStats) {
      return this.getLoader()
    }
    const [ commented, uncommented ] = this.state.donationStats.comment_stats
    const [ anonymous, named ] = this.state.donationStats.anonymous
    const stats = [
      { title: 'Median Donation w/ Comment', emoji: '🗣', value: commented.median },
      { title: 'Median Donation w/o Comment', emoji: '🙊', value: uncommented.median },
      { title: 'Median Donation - Anonymous', emoji: '❓', value: anonymous.median },
      { title: 'Average Donation w/ Comment', emoji: '🗣', value: commented.avg },
      { title: 'Average Donation w/o Comment', emoji: '🙊', value: uncommented.avg },
      { title: 'Average Donation - Anonymous', emoji: '❓', value: anonymous.avg }
    ]
    return stats.map((props, idx) => {
      return <Stat key={idx} {...props} prefix='$' value={+(props.value.toFixed(2))} format='(,ddd).dd' />
    })
  }

  getOverallDonationStatConatainer () {
    if (!this.state.donationStats) {
      return null
    }
    const overall = this.state.donationStats.overall[0]
    const overall_stats = [
      { title: 'Median Donation - Overall', emoji: '💸', value: overall.median },
      { title: 'Average Donation - Overall', emoji: '💸', value: overall.avg },
    ]
    return overall_stats.map((props, idx) => {
      return <Stat key={idx} {...props} prefix='$' value={+(props.value.toFixed(2))} format='(,ddd).dd' />
    })
  }

  getMediansChart () {
    if (!this.state.donationStats) {
      return this.getLoader()
    }
    const medians = this.state.donationStats.medians.map((obj) => {
      return {...obj, time: moment(obj.time).unix()}
    })
    return (
      <ResponsiveContainer width='100%' height={500}>
        <LineChart data={medians} margin={{top: 20}}>
          <Line
            type='natural'
            dataKey='median'
            name='Median Donation Amount'
            stroke={PRIMARY_COLOR}
            strokeWidth={1.5}
            dot={false}
            activeDot />
          <YAxis
            dataKey='median'
            tickFormatter={format('$,')}
            axisLine={{stroke: '#ddd'}}
            tickLine={{stroke: '#ddd'}}
            tick={{fill: '#333', fontWeight: 300, fontSize: 13}}
            domain={[0, 'dataMax']}
            interval='preserveStartEnd'
            minTickGap={0} />
          <Tooltip
            formatter={format('$,.2f')}
            labelFormatter={(d) => moment.unix(d).format('ddd, MMM Do YYYY, h:mm a')} />
          <XAxis
            dataKey='time'
            type='number'
            scale='time'
            axisLine={{stroke: '#ddd'}}
            tickLine={{stroke: '#ddd'}}
            tickFormatter={(d) => moment.unix(d).format('ddd, hA')}
            tick={{fill: '#333', fontWeight: 300, fontSize: 13}}
            interval='preserveStart'
            domain={['dataMin', 'dataMax']}
            minTickGap={50} />
        </LineChart>
      </ResponsiveContainer>
    )
  }

  getDonationWordsChart () {
    if (!this.state.donationWords) {
      return this.getLoader()
    }
    const yAxisLabel = (
      <VerticalLabel
        axisType='yAxis'
        xOffset={0}
        yOffset={275}
        className='recharts-label'>
        Word
      </VerticalLabel>
    )
    return (
      <ResponsiveContainer width='100%' minHeight={800}>
        <BarChart
          margin={{top: 25, left: 50, bottom: 24, right: 24}}
          barSize={10}
          barCategoryGap={2}
          layout='vertical'
          data={this.state.donationWords}>
          <Tooltip />
          <CartesianGrid horizontal={false} />
          <XAxis label={'Number of Uses in Donation Comments'} orientation='top' type='number' />
          <YAxis label={yAxisLabel} interval={0} type='category' dataKey='word' />
          <Bar name='Uses' dataKey='entries' fill={PRIMARY_COLOR} />
        </BarChart>
      </ResponsiveContainer>
    )
  }

  getFrequentDonorsChart () {
    if (!this.state.topDonors) {
      return this.getLoader()
    }
    const yAxisLabel = (
      <VerticalLabel
        axisType='yAxis'
        xOffset={-30}
        yOffset={275}
        className='recharts-label'>
        Donor
      </VerticalLabel>
    )
    return (
      <ResponsiveContainer width='100%' height={800}>
        <BarChart
          margin={{top: 25, left: 60, bottom: 24, right: 24}}
          barSize={10}
          barCategoryGap={2}
          layout='vertical'
          data={this.state.topDonors.frequent}>
          <Tooltip />
          <CartesianGrid horizontal={false} />
          <XAxis label={'Most Frequent Donors'} orientation='top' type='number' />
          <YAxis
            tickFormatter={(t) => t.length < 11 ? t : (t.substring(0, 8) + '...')}
            label={yAxisLabel}
            interval={0}
            type='category'
            dataKey='name' />
          <Bar dataKey='count' name='Number of Donations' fill={PRIMARY_COLOR} />
        </BarChart>
      </ResponsiveContainer>
    )
  }

  getGenerousDonorsChart () {
    if (!this.state.topDonors) {
      return this.getLoader()
    }
    const yAxisLabel = (
      <VerticalLabel
        axisType='yAxis'
        xOffset={-30}
        yOffset={275}
        className='recharts-label'>
        Donor
      </VerticalLabel>
    )
    return (
      <ResponsiveContainer width='100%' height={800}>
        <BarChart
          margin={{top: 25, left: 60, bottom: 24, right: 24}}
          barSize={10}
          barCategoryGap={2}
          layout='vertical'
          data={this.state.topDonors.generous}>
          <Tooltip formatter={format('$,.2f')} />
          <CartesianGrid horizontal={false} />
          <XAxis
            label={'Most Generous Donors'}
            orientation='top'
            type='number'
            tickFormatter={format('$,.2f')} />
          <YAxis
            tickFormatter={(t) => t.length < 11 ? t : (t.substring(0, 8) + '...')}
            label={yAxisLabel}
            interval={0}
            type='category'
            dataKey='name' />
          <Bar name='Donation Total' dataKey='total' fill={PRIMARY_COLOR} />
        </BarChart>
      </ResponsiveContainer>
    )
  }

  render () {
    return (

        <ReturnHome />

Donation Distribution Stats

          <Grid className='current_stats content'>
            {this.getDonationStatContainer()}
            <Row>
              <Col lg={2} md={0}/>
              {this.getOverallDonationStatConatainer()}
            </Row>
          </Grid>

Anonymous Donation Stats

          <Grid>
            <Col md={6} xs={12}>{this.getAnonymousCountPieChart()}</Col>
            <Col md={6} xs={12}>{this.getAnonymousSumPieChart()}</Col>
          </Grid>

Most Commonly Used Words in Donation Comments

          {this.getDonationWordsChart()}

Most Frequent Donors

          {this.getFrequentDonorsChart()}

Most Generous Donors

          {this.getGenerousDonorsChart()}

Donation Comment Stats

          <Grid>
            <Col md={6} xs={12}>{this.getCommentCountPieChart()}</Col>
            <Col md={6} xs={12}>{this.getCommentSumPieChart()}</Col>
          </Grid>

Median Donation Over Time

          <Grid>
            <Col md={12} >{this.getMediansChart()}</Col>
          </Grid>

    )
  }
}

ReactDOM.render(
  <DonationsApp />,
  document.getElementById('react-root')
)
<|endoftext|>


<commit_before>if (typeof(Humble) == 'undefined') {
    var Humble = {};
}

/**
 * HumbleBubble Flotr Charts
 *
 * @copyright Copyright (c) 2010 Humble Software Development
 * @license MIT License <http://www.opensource.org/licenses/mit-license.php>
 * @author Carl Sutherland
 * @version 1.0.0
 */
(function () {

    var Bubble = function (options) {

        this.node       = $(options.node);
        this.data       = options.data;
        this.animate    = options.animate;
    }

    Bubble.prototype = {

        draw : function () {

            var data = this.data,
                that = this,
                i    = 0,
                timeout;

            var localDraw = function () {

                that._flotrDraw(data[i]);
                i++;

                if (i < data.length) {
                    if (timeout) clearTimeout(timeout);
                    timeout = setTimeout(localDraw, 100);
                }
            }

            localDraw();
        },

        _flotrDraw : function (data) {

            Flotr.draw(
                this.node,
                data,
                {
                    bubbles : { show : true, baseRadius : 5 },
                    grid: {outlineWidth: 0, labelMargin: 0},
                    xaxis   : { min : 0, max : 10, ticks : null, noTicks : 10, tickFormatter : function () { return ''; } },
                    yaxis   : { min : 0, max : 10, ticks : null, noTicks : 10, tickFormatter : function () { return ''; } }
                }
            );
        }
    }

    Humble.Bubble = Bubble;

})();
<commit_msg>Added basic animation.<commit_after>if (typeof(Humble) == 'undefined') {
    var Humble = {};
}

/**
 * HumbleBubble Flotr Charts
 *
 * @copyright Copyright (c) 2010 Humble Software Development
 * @license MIT License <http://www.opensource.org/licenses/mit-license.php>
 * @author Carl Sutherland
 * @version 1.0.0
 */
(function () {

    var Bubble = function (options) {

        this.node       = $(options.node);
        this.data       = options.data;
        this.animate    = options.animate;
    }

    Bubble.prototype = {

        draw : function () {

            var data = this.data,
                that = this,
                i    = 0, // Timeseries iterator
                j    = 0, // Animation iterator
                timeout;

            if (this.animate) {

                var localDraw = function () {

                    var d = [];

                    // Series iteration
                    for (var k = 0; k < data[i].length; k++) {

                        var point;

                        point = that._midPoint(
                            data[i][k][0],
                            data[i+1][k][0],
                            j / 15
                        );

                        d.push([point]);
                    }

                    that._flotrDraw(d);

                    if (j < 15) {
                        j++;
                    } else {
                        j = 0;
                        i++;
                    }

                    if (i < data.length - 1) {
                        if (timeout) clearTimeout(timeout);
                        timeout = setTimeout(localDraw, 10);
                    }
                }

            } else {

                var localDraw = function () {

                    that._flotrDraw(data[i]);
                    i++;

                    if (i < data.length) {
                        if (timeout) clearTimeout(timeout);
                        timeout = setTimeout(localDraw, 100);
                    }
                }
            }

            localDraw();
        },

        _mid : function (a, b, ratio) {
            if (ratio === null || typeof (ratio) === 'undefined') ratio = .5;
            return (a + (b - a) * ratio);
        },

        _midPoint : function (a, b, ratio) {

            var point = [];

            for (var i = 0; i < a.length; i++) {
                point[i] = this._mid(a[i], b[i], ratio);
            }

            return point;
        },

        _flotrDraw : function (data) {

            Flotr.draw(
                this.node,
                data,
                {
                    bubbles : { show : true, baseRadius : 5 },
                    grid: {outlineWidth: 0, labelMargin: 0},
                    xaxis   : { min : 0, max : 10, ticks : null, noTicks : 10, tickFormatter : function () { return ''; } },
                    yaxis   : { min : 0, max : 10, ticks : null, noTicks : 10, tickFormatter : function () { return ''; } }
                }
            );
        }
    }

    Humble.Bubble = Bubble;

})();
<|endoftext|>


<commit_before>if (typeof(Humble) == 'undefined') {
    var Humble = {};
}

/**
 * HumbleBubble Flotr Charts
 *
 * @copyright Copyright (c) 2010 Humble Software Development
 * @license MIT License <http://www.opensource.org/licenses/mit-license.php>
 * @author Carl Sutherland
 * @version 1.0.0
 */
(function () {

    var Bubble = function (options) {

        this.options = options;

        this.node       = $(options.node);
        this.data       = options.data;
        this.timeout    = null;
    }

    Bubble.prototype = {

        draw : function () {
            this._flotrDraw(this.data[0]);
        },

        animate : function () {

            var data = this.data,
                that = this,
                i    = 0, // Timeseries iterator
                j    = 0; // Animation iterator

            if (this.timeout) clearTimeout(this.timeout);

            if (this.options.animate) {

                var localDraw = function () {

                    var d = [];

                    // Series iteration
                    for (var k = 0; k < data[i].length; k++) {

                        var point;

                        point = that._midPoint(
                            data[i][k][0],
                            data[i+1][k][0],
                            j / 15
                        );

                        d.push([point]);
                    }

                    that._flotrDraw(d);

                    if (j < 15) {
                        j++;
                    } else {
                        j = 0;
                        i++;
                    }

                    if (i < data.length - 1) {
                        if (that.timeout) clearTimeout(that.timeout);
                        that.timeout = setTimeout(localDraw, 10);
                    }
                }

            } else {

                var localDraw = function () {

                    that._flotrDraw(data[i]);
                    i++;

                    if (i < data.length) {
                        if (that.timeout) clearTimeout(that.timeout);
                        that.timeout = setTimeout(localDraw, 100);
                    }
                }
            }

            localDraw();
        },

        _mid : function (a, b, ratio) {
            if (ratio === null || typeof (ratio) === 'undefined') ratio = .5;
            return (a + (b - a) * ratio);
        },

        _midPoint : function (a, b, ratio) {

            var point = [];

            for (var i = 0; i < a.length; i++) {
                point[i] = this._mid(a[i], b[i], ratio);
            }

            return point;
        },

        _flotrDraw : function (data) {

            Flotr.draw(
                this.node,
                data,
                {
                    bubbles : { show : true, baseRadius : 5 },
                    grid: {outlineWidth: 0, labelMargin: 0},
                    xaxis   : { min : 0, max : 10, ticks : null, noTicks : 10, tickFormatter : function () { return ''; } },
                    yaxis   : { min : 0, max : 10, ticks : null, noTicks : 10, tickFormatter : function () { return ''; } }
                }
            );
        }
    }

    Humble.Bubble = Bubble;

})();
<commit_msg>Updated some hard coded configs for country data.<commit_after>if (typeof(Humble) == 'undefined') {
    var Humble = {};
}

/**
 * HumbleBubble Flotr Charts
 *
 * @copyright Copyright (c) 2010 Humble Software Development
 * @license MIT License <http://www.opensource.org/licenses/mit-license.php>
 * @author Carl Sutherland
 * @version 1.0.0
 */
(function () {

    var Bubble = function (options) {

        this.options = options;

        this.node       = $(options.node);
        this.data       = options.data;
        this.timeout    = null;
    }

    Bubble.prototype = {

        draw : function () {
            this._flotrDraw(this.data[0]);
        },

        animate : function () {

            var data = this.data,
                that = this,
                i    = 0, // Timeseries iterator
                j    = 0; // Animation iterator

            if (this.timeout) clearTimeout(this.timeout);

            if (this.options.animate) {

                var localDraw = function () {

                    var d = [];

                    // Series iteration
                    for (var k = 0; k < data[i].length; k++) {

                        var point;

                        point = that._midPoint(
                            data[i][k][0],
                            data[i+1][k][0],
                            j / 15
                        );

                        d.push([point]);
                    }

                    that._flotrDraw(d);

                    if (j < 15) {
                        j++;
                    } else {
                        j = 0;
                        i++;
                    }

                    if (i < data.length - 1) {
                        if (that.timeout) clearTimeout(that.timeout);
                        that.timeout = setTimeout(localDraw, 10);
                    }
                }

            } else {

                var localDraw = function () {

                    that._flotrDraw(data[i]);
                    i++;

                    if (i < data.length) {
                        if (that.timeout) clearTimeout(that.timeout);
                        that.timeout = setTimeout(localDraw, 50);
                    }
                }
            }

            localDraw();
        },

        _mid : function (a, b, ratio) {
            if (ratio === null || typeof (ratio) === 'undefined') ratio = .5;
            return (a + (b - a) * ratio);
        },

        _midPoint : function (a, b, ratio) {

            var point = [];

            for (var i = 0; i < a.length; i++) {
                point[i] = this._mid(a[i], b[i], ratio);
            }

            return point;
        },

        _flotrDraw : function (data) {

            Flotr.draw(
                this.node,
                data,
                {
                    bubbles : { show : true, baseRadius : .000001 },
                    grid: {outlineWidth: 0, labelMargin: 0},
                    xaxis   : { min : 0, max : 25000, scaling : 'linear', ticks : null, noTicks : 10, tickFormatter : function () { return ''; } },
                    yaxis   : { min : 0, max : 80, ticks : null, noTicks : 10, tickFormatter : function () { return ''; } }
                }
            );
        }
    }

    Humble.Bubble = Bubble;

})();
<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado Boulder

/**
 * User-interface component for picking a number value from a range.
 * This is essentially a value with integrated up/down spinners.
 * But PhET has been calling it a 'picker', so that's what this type is named.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var ButtonListener = require( 'SCENERY/input/ButtonListener' );
  var Color = require( 'SCENERY/util/Color' );
  var DerivedProperty = require( 'AXON/DerivedProperty' );
  var Dimension2 = require( 'DOT/Dimension2' );
  var inherit = require( 'PHET_CORE/inherit' );
  var LinearGradient = require( 'SCENERY/util/LinearGradient' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var Shape = require( 'KITE/Shape' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Timer = require( 'JOIST/Timer' );
  var Util = require( 'DOT/Util' );

  // creates a vertical gradient where with color1 at the top and bottom, color2 in the center
  var createBackgroundGradient = function( color1, color2, height ) {
    return new LinearGradient( 0, 0, 0, height )
      .addColorStop( 0, color1 )
      .addColorStop( 0.5, color2 )
      .addColorStop( 1, color1 );
  };

  //-------------------------------------------------------------------------------------------

  /**
   * @param {Property<String>} stateProperty 'up'|'over'|'down'|'out'
   * @param {Property<Boolean>} enabledProperty
   * @param {Function} fireFunction
   * @param {Number} timerDelay start to fire continuously after pressing for this long (milliseconds)
   * @param {Number} intervalDelay // fire continuously at this frequency (milliseconds)
   * @constructor
   */
  function PickerListener( stateProperty, enabledProperty, fireFunction, timerDelay, intervalDelay ) {

    // stuff related to press-&-hold feature
    var fired = false;
    var timeoutID = null;
    var intervalID = null;
    var cleanupTimer = function() {
      if ( timeoutID ) {
        Timer.clearTimeout( timeoutID );
        timeoutID = null;
      }
      if ( intervalID ) {
        Timer.clearInterval( intervalID );
        intervalID = null;
      }
    };

    ButtonListener.call( this, {

      up: function() {
        stateProperty.set( 'up' );
        cleanupTimer();
      },

      over: function() {
        stateProperty.set( 'over' );
      },

      down: function() {
        if ( timeoutID === null && intervalID === null ) {
          if ( enabledProperty.get() ) {
            fireFunction(); // fire once immediately
          }
          stateProperty.set( 'down' );
          fired = false;
          timeoutID = Timer.setTimeout( function() {
            timeoutID = null;
            fired = true;
            intervalID = Timer.setInterval( function() {
              if ( enabledProperty.get() ) {
                fireFunction();
              }
            }, intervalDelay );
          }, timerDelay );
        }
      },

      out: function() {
        stateProperty.set( 'out' );
      },

      fire: function() {
        cleanupTimer();
      }
    } );
  }

  inherit( ButtonListener, PickerListener );

  //-------------------------------------------------------------------------------------------

  /**
   * @param {Property<Number>} valueProperty
   * @param {Property<Range>} rangeProperty
   * @param {*} options
   * @constructor
   */
  function NumberPicker( valueProperty, rangeProperty, options ) {

    options = _.extend( {
      color: new Color( 0, 0, 255 ), // {Color|String}
      cornerRadius: 6,
      xMargin: 3,
      yMargin: 3,
      decimalPlaces: 0,
      font: new PhetFont( 24 ),
      upFunction: function() { return valueProperty.get() + 1; },
      downFunction: function() { return valueProperty.get() - 1; },
      timerDelay: 400, // start to fire continuously after pressing for this long (milliseconds)
      intervalDelay: 100, // fire continuously at this frequency (milliseconds),
      noValueString: '-', // string to display if valueProperty.get is null or undefined
      align: 'center', // horizontal alignment of the value, 'center'|'right'|'left'
      touchAreaExpandX: 10,
      touchAreaExpandY: 20,
      mouseAreaExpandX: 0,
      mouseAreaExpandY: 14,
      backgroundStroke: 'gray',
      backgroundLineWidth: 0.5,
      arrowHeight: 6
    }, options );
    options.activatedColor = options.activatedColor || Color.toColor( options.color ).darkerColor();

    var thisNode = this;
    Node.call( thisNode, { cursor: 'pointer' } );

    // properties for the "up" (increment) control
    var upStateProperty = new Property( 'up' ); // up|down|over|out
    var upEnabledProperty = new DerivedProperty( [ valueProperty, rangeProperty ], function( value, range ) {
      return ( value !== null && value !== undefined && value < range.max );
    } );

    // properties for the "down" (decrement) control
    var downStateProperty = new Property( 'up' ); // up|down|over|out
    var downEnabledProperty = new DerivedProperty( [ valueProperty, rangeProperty ], function( value, range ) {
      return ( value !== null && value !== undefined && value > range.min );
    } );

    // callbacks for changing the value
    var fireUp = function() {
      valueProperty.set( Math.min( options.upFunction(), rangeProperty.get().max ) );
    };
    var fireDown = function() {
      valueProperty.set( Math.max( options.downFunction(), rangeProperty.get().min ) );
    };

    // displays the value
    var valueNode = new Text( "", { font: options.font, pickable: false } );

    // compute max width of text based on value range
    valueNode.text = Util.toFixed( rangeProperty.get().min, options.decimalPlaces );
    var maxWidth = valueNode.width;
    valueNode.text = Util.toFixed( rangeProperty.get().max, options.decimalPlaces );
    maxWidth = Math.max( maxWidth, valueNode.width );

    // compute shape of the background behind the numeric value
    var backgroundWidth = maxWidth + ( 2 * options.xMargin );
    var backgroundHeight = valueNode.height + ( 2 * options.yMargin );
    var backgroundOverlap = 1;
    var backgroundCornerRadius = options.cornerRadius;

    // top half of the background, for "up". Shape computed starting at upper-left, going clockwise.
    var upBackground = new Path( new Shape()
      .arc( backgroundCornerRadius, backgroundCornerRadius, backgroundCornerRadius, Math.PI, Math.PI * 3 / 2, false )
      .arc( backgroundWidth - backgroundCornerRadius, backgroundCornerRadius, backgroundCornerRadius, -Math.PI / 2, 0, false )
      .lineTo( backgroundWidth, ( backgroundHeight / 2 ) + backgroundOverlap )
      .lineTo( 0, ( backgroundHeight / 2 ) + backgroundOverlap )
      .close() );
    upBackground.addInputListener( new PickerListener( upStateProperty, upEnabledProperty, fireUp, options.timerDelay, options.intervalDelay ) );

    // bottom half of the background, for "down". Shape computed starting at bottom-right, going clockwise.
    var downBackground = new Path( new Shape()
      .arc( backgroundWidth - backgroundCornerRadius, backgroundHeight - backgroundCornerRadius, backgroundCornerRadius, 0, Math.PI / 2, false )
      .arc( backgroundCornerRadius, backgroundHeight - backgroundCornerRadius, backgroundCornerRadius, Math.PI / 2, Math.PI, false )
      .lineTo( 0, backgroundHeight / 2 )
      .lineTo( backgroundWidth, backgroundHeight / 2 )
      .close() );
    downBackground.addInputListener( new PickerListener( downStateProperty, downEnabledProperty, fireDown, options.timerDelay, options.intervalDelay ) );

    // separate rectangle for stroke around value background
    var strokedBackground = new Path( new Shape()
      .arc( backgroundCornerRadius, backgroundCornerRadius, backgroundCornerRadius, Math.PI, Math.PI * 3 / 2, false )
      .arc( backgroundWidth - backgroundCornerRadius, backgroundCornerRadius, backgroundCornerRadius, -Math.PI / 2, 0, false )
      .arc( backgroundWidth - backgroundCornerRadius, backgroundHeight - backgroundCornerRadius, backgroundCornerRadius, 0, Math.PI / 2, false )
      .arc( backgroundCornerRadius, backgroundHeight - backgroundCornerRadius, backgroundCornerRadius, Math.PI / 2, Math.PI, false )
      .close(), { pickable: false, stroke: options.backgroundStroke, lineWidth: options.backgroundLineWidth } );

    // touch area for buttons
    upBackground.touchArea = Shape.rectangle(
      upBackground.left - ( options.touchAreaExpandX / 2 ), upBackground.top - options.touchAreaExpandY,
      upBackground.width + options.touchAreaExpandX, upBackground.height + options.touchAreaExpandY );
    downBackground.touchArea = Shape.rectangle(
      downBackground.left - ( options.touchAreaExpandX / 2 ), downBackground.top,
      downBackground.width + options.touchAreaExpandX, downBackground.height + options.touchAreaExpandY );

    // mouse area for buttons
    upBackground.mouseArea = Shape.rectangle(
      upBackground.left - ( options.mouseAreaExpandX / 2 ), upBackground.top - options.mouseAreaExpandY,
      upBackground.width + options.mouseAreaExpandX, upBackground.height + options.mouseAreaExpandY );
    downBackground.mouseArea = Shape.rectangle(
      downBackground.left - ( options.mouseAreaExpandX / 2 ), downBackground.top,
      downBackground.width + options.mouseAreaExpandX, downBackground.height + options.mouseAreaExpandY );

    // compute colors
    var arrowColors = {
      up: options.color,
      over: options.color,
      down: options.activatedColor,
      out: options.color,
      disabled: 'rgb(176,176,176)'
    };
    var centerColor = 'white';
    var highlightGradient = createBackgroundGradient( options.color, centerColor, backgroundHeight );
    var pressedGradient = createBackgroundGradient( options.activatedColor, centerColor, backgroundHeight );
    var backgroundColors = {
      up: centerColor,
      over: highlightGradient,
      down: pressedGradient,
      out: pressedGradient,
      disabled: centerColor
    };

    // compute size of arrows
    var arrowButtonSize = new Dimension2( 0.5 * backgroundWidth, options.arrowHeight );

    // 'up' arrow
    var arrowOptions = { fill: 'white', stroke: 'black', lineWidth: 0.25 };
    var upArrowShape = new Shape()
      .moveTo( arrowButtonSize.width / 2, 0 )
      .lineTo( arrowButtonSize.width, arrowButtonSize.height )
      .lineTo( 0, arrowButtonSize.height )
      .close();
    var upArrow = new Path( upArrowShape, arrowOptions );
    upArrow.addInputListener( new PickerListener( upStateProperty, upEnabledProperty, fireUp, options.timerDelay, options.intervalDelay ) );

    // 'down' arrow
    var downArrowShape = new Shape()
      .moveTo( arrowButtonSize.width / 2, arrowButtonSize.height )
      .lineTo( 0, 0 )
      .lineTo( arrowButtonSize.width, 0 )
      .close();
    var downArrow = new Path( downArrowShape, arrowOptions );
    downArrow.addInputListener( new PickerListener( downStateProperty, downEnabledProperty, fireDown, options.timerDelay, options.intervalDelay ) );

    // rendering order
    thisNode.addChild( upBackground );
    thisNode.addChild( downBackground );
    thisNode.addChild( strokedBackground );
    thisNode.addChild( upArrow );
    thisNode.addChild( downArrow );
    thisNode.addChild( valueNode );

    // layout, background nodes are already drawn in the local coordinate frame
    var ySpacing = 3;
    valueNode.x = 0;
    valueNode.centerY = backgroundHeight / 2;
    upArrow.centerX = upBackground.centerX;
    upArrow.bottom = upBackground.top - ySpacing;
    downArrow.centerX = downBackground.centerX;
    downArrow.top = downBackground.bottom + ySpacing;

    // Update text to match the value
    valueProperty.link( function( value ) {
      if ( value === null || value === undefined ) {
        valueNode.text = options.noValueString;
        valueNode.x = ( backgroundWidth - valueNode.width ) / 2; // horizontally centered
      }
      else {
        valueNode.text = Util.toFixed( value, options.decimalPlaces );
        if ( options.align === 'center' ) {
          valueNode.centerX = upBackground.centerX;
        }
        else if ( options.align === 'right' ) {
          valueNode.right = upBackground.right - options.xMargin;
        }
        else if ( options.align === 'left' ) {
          valueNode.left = upBackground.left + options.xMargin;
        }
        else {
          throw new Error( 'unsupported value for option.align: ' + options.align );
        }
      }
    } );

    // Update button colors
    var updateColors = function( stateProperty, enabledProperty, background, arrow ) {
      if ( enabledProperty.get() ) {
        arrow.stroke = 'black';
        var state = stateProperty.get();
        if ( state === 'up' ) {
          background.fill = backgroundColors.up;
          arrow.fill = arrowColors.up;
        }
        else if ( state === 'over' ) {
          background.fill = backgroundColors.over;
          arrow.fill = arrowColors.over;
        }
        else if ( state === 'down' ) {
          background.fill = backgroundColors.down;
          arrow.fill = arrowColors.down;
        }
        else if ( state === 'out' ) {
          background.fill = backgroundColors.out;
          arrow.fill = arrowColors.out;
        }
        else {
          throw new Error( 'unsupported state: ' + state );
        }
      }
      else {
        background.fill = backgroundColors.disabled;
        arrow.fill = arrowColors.disabled;
        arrow.stroke = arrowColors.disabled; // stroke so that arrow size will look the same when it's enabled/disabled
      }
    };
    Property.multilink( [ upStateProperty, upEnabledProperty ],
      updateColors.bind( thisNode, upStateProperty, upEnabledProperty, upBackground, upArrow ) );
    Property.multilink( [ downStateProperty, downEnabledProperty ],
      updateColors.bind( thisNode, downStateProperty, downEnabledProperty, downBackground, downArrow ) );

    thisNode.mutate( options );
  }

  return inherit( Node, NumberPicker );
} );
<commit_msg>#65 delete 'fired' flag that is no longer needed<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * User-interface component for picking a number value from a range.
 * This is essentially a value with integrated up/down spinners.
 * But PhET has been calling it a 'picker', so that's what this type is named.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var ButtonListener = require( 'SCENERY/input/ButtonListener' );
  var Color = require( 'SCENERY/util/Color' );
  var DerivedProperty = require( 'AXON/DerivedProperty' );
  var Dimension2 = require( 'DOT/Dimension2' );
  var inherit = require( 'PHET_CORE/inherit' );
  var LinearGradient = require( 'SCENERY/util/LinearGradient' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var Shape = require( 'KITE/Shape' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Timer = require( 'JOIST/Timer' );
  var Util = require( 'DOT/Util' );

  // creates a vertical gradient where with color1 at the top and bottom, color2 in the center
  var createBackgroundGradient = function( color1, color2, height ) {
    return new LinearGradient( 0, 0, 0, height )
      .addColorStop( 0, color1 )
      .addColorStop( 0.5, color2 )
      .addColorStop( 1, color1 );
  };

  //-------------------------------------------------------------------------------------------

  /**
   * @param {Property<String>} stateProperty 'up'|'over'|'down'|'out'
   * @param {Property<Boolean>} enabledProperty
   * @param {Function} fireFunction
   * @param {Number} timerDelay start to fire continuously after pressing for this long (milliseconds)
   * @param {Number} intervalDelay // fire continuously at this frequency (milliseconds)
   * @constructor
   */
  function PickerListener( stateProperty, enabledProperty, fireFunction, timerDelay, intervalDelay ) {

    // stuff related to press-&-hold feature
    var timeoutID = null;
    var intervalID = null;
    var cleanupTimer = function() {
      if ( timeoutID ) {
        Timer.clearTimeout( timeoutID );
        timeoutID = null;
      }
      if ( intervalID ) {
        Timer.clearInterval( intervalID );
        intervalID = null;
      }
    };

    ButtonListener.call( this, {

      up: function() {
        stateProperty.set( 'up' );
        cleanupTimer();
      },

      over: function() {
        stateProperty.set( 'over' );
      },

      down: function() {
        if ( timeoutID === null && intervalID === null ) {
          if ( enabledProperty.get() ) {
            fireFunction(); // fire once immediately
          }
          stateProperty.set( 'down' );
          timeoutID = Timer.setTimeout( function() {
            timeoutID = null;
            intervalID = Timer.setInterval( function() {
              if ( enabledProperty.get() ) {
                fireFunction();
              }
            }, intervalDelay );
          }, timerDelay );
        }
      },

      out: function() {
        stateProperty.set( 'out' );
      },

      fire: function() {
        cleanupTimer();
      }
    } );
  }

  inherit( ButtonListener, PickerListener );

  //-------------------------------------------------------------------------------------------

  /**
   * @param {Property<Number>} valueProperty
   * @param {Property<Range>} rangeProperty
   * @param {*} options
   * @constructor
   */
  function NumberPicker( valueProperty, rangeProperty, options ) {

    options = _.extend( {
      color: new Color( 0, 0, 255 ), // {Color|String}
      cornerRadius: 6,
      xMargin: 3,
      yMargin: 3,
      decimalPlaces: 0,
      font: new PhetFont( 24 ),
      upFunction: function() { return valueProperty.get() + 1; },
      downFunction: function() { return valueProperty.get() - 1; },
      timerDelay: 400, // start to fire continuously after pressing for this long (milliseconds)
      intervalDelay: 100, // fire continuously at this frequency (milliseconds),
      noValueString: '-', // string to display if valueProperty.get is null or undefined
      align: 'center', // horizontal alignment of the value, 'center'|'right'|'left'
      touchAreaExpandX: 10,
      touchAreaExpandY: 20,
      mouseAreaExpandX: 0,
      mouseAreaExpandY: 14,
      backgroundStroke: 'gray',
      backgroundLineWidth: 0.5,
      arrowHeight: 6
    }, options );
    options.activatedColor = options.activatedColor || Color.toColor( options.color ).darkerColor();

    var thisNode = this;
    Node.call( thisNode, { cursor: 'pointer' } );

    // properties for the "up" (increment) control
    var upStateProperty = new Property( 'up' ); // up|down|over|out
    var upEnabledProperty = new DerivedProperty( [ valueProperty, rangeProperty ], function( value, range ) {
      return ( value !== null && value !== undefined && value < range.max );
    } );

    // properties for the "down" (decrement) control
    var downStateProperty = new Property( 'up' ); // up|down|over|out
    var downEnabledProperty = new DerivedProperty( [ valueProperty, rangeProperty ], function( value, range ) {
      return ( value !== null && value !== undefined && value > range.min );
    } );

    // callbacks for changing the value
    var fireUp = function() {
      valueProperty.set( Math.min( options.upFunction(), rangeProperty.get().max ) );
    };
    var fireDown = function() {
      valueProperty.set( Math.max( options.downFunction(), rangeProperty.get().min ) );
    };

    // displays the value
    var valueNode = new Text( "", { font: options.font, pickable: false } );

    // compute max width of text based on value range
    valueNode.text = Util.toFixed( rangeProperty.get().min, options.decimalPlaces );
    var maxWidth = valueNode.width;
    valueNode.text = Util.toFixed( rangeProperty.get().max, options.decimalPlaces );
    maxWidth = Math.max( maxWidth, valueNode.width );

    // compute shape of the background behind the numeric value
    var backgroundWidth = maxWidth + ( 2 * options.xMargin );
    var backgroundHeight = valueNode.height + ( 2 * options.yMargin );
    var backgroundOverlap = 1;
    var backgroundCornerRadius = options.cornerRadius;

    // top half of the background, for "up". Shape computed starting at upper-left, going clockwise.
    var upBackground = new Path( new Shape()
      .arc( backgroundCornerRadius, backgroundCornerRadius, backgroundCornerRadius, Math.PI, Math.PI * 3 / 2, false )
      .arc( backgroundWidth - backgroundCornerRadius, backgroundCornerRadius, backgroundCornerRadius, -Math.PI / 2, 0, false )
      .lineTo( backgroundWidth, ( backgroundHeight / 2 ) + backgroundOverlap )
      .lineTo( 0, ( backgroundHeight / 2 ) + backgroundOverlap )
      .close() );
    upBackground.addInputListener( new PickerListener( upStateProperty, upEnabledProperty, fireUp, options.timerDelay, options.intervalDelay ) );

    // bottom half of the background, for "down". Shape computed starting at bottom-right, going clockwise.
    var downBackground = new Path( new Shape()
      .arc( backgroundWidth - backgroundCornerRadius, backgroundHeight - backgroundCornerRadius, backgroundCornerRadius, 0, Math.PI / 2, false )
      .arc( backgroundCornerRadius, backgroundHeight - backgroundCornerRadius, backgroundCornerRadius, Math.PI / 2, Math.PI, false )
      .lineTo( 0, backgroundHeight / 2 )
      .lineTo( backgroundWidth, backgroundHeight / 2 )
      .close() );
    downBackground.addInputListener( new PickerListener( downStateProperty, downEnabledProperty, fireDown, options.timerDelay, options.intervalDelay ) );

    // separate rectangle for stroke around value background
    var strokedBackground = new Path( new Shape()
      .arc( backgroundCornerRadius, backgroundCornerRadius, backgroundCornerRadius, Math.PI, Math.PI * 3 / 2, false )
      .arc( backgroundWidth - backgroundCornerRadius, backgroundCornerRadius, backgroundCornerRadius, -Math.PI / 2, 0, false )
      .arc( backgroundWidth - backgroundCornerRadius, backgroundHeight - backgroundCornerRadius, backgroundCornerRadius, 0, Math.PI / 2, false )
      .arc( backgroundCornerRadius, backgroundHeight - backgroundCornerRadius, backgroundCornerRadius, Math.PI / 2, Math.PI, false )
      .close(), { pickable: false, stroke: options.backgroundStroke, lineWidth: options.backgroundLineWidth } );

    // touch area for buttons
    upBackground.touchArea = Shape.rectangle(
      upBackground.left - ( options.touchAreaExpandX / 2 ), upBackground.top - options.touchAreaExpandY,
      upBackground.width + options.touchAreaExpandX, upBackground.height + options.touchAreaExpandY );
    downBackground.touchArea = Shape.rectangle(
      downBackground.left - ( options.touchAreaExpandX / 2 ), downBackground.top,
      downBackground.width + options.touchAreaExpandX, downBackground.height + options.touchAreaExpandY );

    // mouse area for buttons
    upBackground.mouseArea = Shape.rectangle(
      upBackground.left - ( options.mouseAreaExpandX / 2 ), upBackground.top - options.mouseAreaExpandY,
      upBackground.width + options.mouseAreaExpandX, upBackground.height + options.mouseAreaExpandY );
    downBackground.mouseArea = Shape.rectangle(
      downBackground.left - ( options.mouseAreaExpandX / 2 ), downBackground.top,
      downBackground.width + options.mouseAreaExpandX, downBackground.height + options.mouseAreaExpandY );

    // compute colors
    var arrowColors = {
      up: options.color,
      over: options.color,
      down: options.activatedColor,
      out: options.color,
      disabled: 'rgb(176,176,176)'
    };
    var centerColor = 'white';
    var highlightGradient = createBackgroundGradient( options.color, centerColor, backgroundHeight );
    var pressedGradient = createBackgroundGradient( options.activatedColor, centerColor, backgroundHeight );
    var backgroundColors = {
      up: centerColor,
      over: highlightGradient,
      down: pressedGradient,
      out: pressedGradient,
      disabled: centerColor
    };

    // compute size of arrows
    var arrowButtonSize = new Dimension2( 0.5 * backgroundWidth, options.arrowHeight );

    // 'up' arrow
    var arrowOptions = { fill: 'white', stroke: 'black', lineWidth: 0.25 };
    var upArrowShape = new Shape()
      .moveTo( arrowButtonSize.width / 2, 0 )
      .lineTo( arrowButtonSize.width, arrowButtonSize.height )
      .lineTo( 0, arrowButtonSize.height )
      .close();
    var upArrow = new Path( upArrowShape, arrowOptions );
    upArrow.addInputListener( new PickerListener( upStateProperty, upEnabledProperty, fireUp, options.timerDelay, options.intervalDelay ) );

    // 'down' arrow
    var downArrowShape = new Shape()
      .moveTo( arrowButtonSize.width / 2, arrowButtonSize.height )
      .lineTo( 0, 0 )
      .lineTo( arrowButtonSize.width, 0 )
      .close();
    var downArrow = new Path( downArrowShape, arrowOptions );
    downArrow.addInputListener( new PickerListener( downStateProperty, downEnabledProperty, fireDown, options.timerDelay, options.intervalDelay ) );

    // rendering order
    thisNode.addChild( upBackground );
    thisNode.addChild( downBackground );
    thisNode.addChild( strokedBackground );
    thisNode.addChild( upArrow );
    thisNode.addChild( downArrow );
    thisNode.addChild( valueNode );

    // layout, background nodes are already drawn in the local coordinate frame
    var ySpacing = 3;
    valueNode.x = 0;
    valueNode.centerY = backgroundHeight / 2;
    upArrow.centerX = upBackground.centerX;
    upArrow.bottom = upBackground.top - ySpacing;
    downArrow.centerX = downBackground.centerX;
    downArrow.top = downBackground.bottom + ySpacing;

    // Update text to match the value
    valueProperty.link( function( value ) {
      if ( value === null || value === undefined ) {
        valueNode.text = options.noValueString;
        valueNode.x = ( backgroundWidth - valueNode.width ) / 2; // horizontally centered
      }
      else {
        valueNode.text = Util.toFixed( value, options.decimalPlaces );
        if ( options.align === 'center' ) {
          valueNode.centerX = upBackground.centerX;
        }
        else if ( options.align === 'right' ) {
          valueNode.right = upBackground.right - options.xMargin;
        }
        else if ( options.align === 'left' ) {
          valueNode.left = upBackground.left + options.xMargin;
        }
        else {
          throw new Error( 'unsupported value for option.align: ' + options.align );
        }
      }
    } );

    // Update button colors
    var updateColors = function( stateProperty, enabledProperty, background, arrow ) {
      if ( enabledProperty.get() ) {
        arrow.stroke = 'black';
        var state = stateProperty.get();
        if ( state === 'up' ) {
          background.fill = backgroundColors.up;
          arrow.fill = arrowColors.up;
        }
        else if ( state === 'over' ) {
          background.fill = backgroundColors.over;
          arrow.fill = arrowColors.over;
        }
        else if ( state === 'down' ) {
          background.fill = backgroundColors.down;
          arrow.fill = arrowColors.down;
        }
        else if ( state === 'out' ) {
          background.fill = backgroundColors.out;
          arrow.fill = arrowColors.out;
        }
        else {
          throw new Error( 'unsupported state: ' + state );
        }
      }
      else {
        background.fill = backgroundColors.disabled;
        arrow.fill = arrowColors.disabled;
        arrow.stroke = arrowColors.disabled; // stroke so that arrow size will look the same when it's enabled/disabled
      }
    };
    Property.multilink( [ upStateProperty, upEnabledProperty ],
      updateColors.bind( thisNode, upStateProperty, upEnabledProperty, upBackground, upArrow ) );
    Property.multilink( [ downStateProperty, downEnabledProperty ],
      updateColors.bind( thisNode, downStateProperty, downEnabledProperty, downBackground, downArrow ) );

    thisNode.mutate( options );
  }

  return inherit( Node, NumberPicker );
} );
<|endoftext|>