<commit_before>/**
 * formBuilder Widget
 *
 * TODO: handle submission, bind to buttons somehow, and on-submit event
 *
 * TODO: maintain a model, take in the model, link to the fields and make is
 * accessible by a getter model linker? listen to changes
 *
 * TODO: can you call _init() a second type with more options? is that how you
 * call _setOption from outside?
 */

(function($) {
	"use strict";
	var util = $.formBuilder.util;

	$.widget("formBuilder.formBuilder", {

		options: {
			converter: {
				fromForm: function(data) {
					return data;
				},
				toForm: function(data) {
					return data;
				}
			},

			// ignore fields that are not $(':visible')
			ignoreHidden: false,

			// default required option for fields
			defaultRequired: false,

			// Hide the form while it is being setup, then show it
			loadHidden: true

		},

		_create: function() {
			var self = this,
				o = self.options,
				e = self.element;

			// Get options
			util.loadDomToggleData(e, o, ['ignoreHidden','defaultRequired','loadHidden']);

			if(o.loadHidden) {
				e.hide();
			}

			self.baseData = {};

			self.fieldsWidgets = $([]);
			self.fields = $([]);

			self.scanForNewFields();

			self.dirty = false;

			/*
			 * proxy the dirty events from all of the fields in the form
			 */
			e.on('dirty', function (ev) {
				if(self.dirty){
					ev.stopPropagation();
					return;
				}
				self.dirty = true;
			}).on('clean', function (ev) {

				/*
				 * if the form is clean kill the clean event
				 */
				if(!self.dirty){
					ev.stopPropagation();
					return;
				}

				/*
				 * check if form is really clean
				 */
				self.dirty = self.isDirty();

				/*
				 * if form is still dirty, kill the clean event
				 */
				if(self.dirty){
					ev.stopPropagation();
				}
			});

			if(o.loadHidden) {
				e.show();
			}
		},

		scanForNewFields: function() {
			var self = this,
				o = self.options,
				e = self.element;

			/*
			 * expected methods for a widget to act a as field:
			 *
			 * set()
			 * get()
			 * isDirty()
			 * clearDirty()
			 * flashDirty()
			 * flashError()
			 * disable()
			 * enable()
			 * clear()
			 * validate()
			 *
			 */
			e.find('[data-load-widget-as-field]').each(function () {
				var el = $(this);

				if(!el.attr('name')){
					throw new Error('data-load-widget-as-field must have name attribute');
				}

				if(self.fieldsWidgets.has(el[0]).length){
					return;
				}

				var widgetName = el.attr('data-load-widget-as-field');
				if(!widgetName || !$.isFunction(el[widgetName])) {
					throw new Error('Cannot load widget "'+widgetName+'" as field, it does not exist');
				}

				el[widgetName]();

				/*
				 * check if the widget extends the base FieldWidget widget
				 */

				self.fieldsWidgets = self.fieldsWidgets.add(el);

				/*
				 * wrap if label is found
				 */
				var label = el.attr('data-label');

				if(!label) {
					return;
				}
				el.addClass('field-items').wrap('

').before('' + label + '');
			});

			e.find('input[type="checkbox"]')
				.not(':formBuilder-selectionField')
				.not('.form-builder-ignore *')
				.not('[data-load-widget-as-field]')
				.not('[data-load-widget-as-field] *')
				.each(function() {
					var el = $(this);

					el.selectionField({
						required: o.defaultRequired
					});

					self.fieldsWidgets = self.fieldsWidgets.add(el);
				});

			var radios = e.find('input[type="radio"]')
				.not(':formBuilder-selectionField')
				.not('.form-builder-ignore *')
				.not('[data-load-widget-as-field]')
				.not('[data-load-widget-as-field] *');

			radios.each(function() {
				var el = $(this);

				if(el.is(':formBuilder-selectionField')) {
					// ignore the ones already grouped
					return;
				}

				var radioGroup = radios.filter('[name="'+this.name+'"]');

				radioGroup.each(function() {
					$(this).selectionField({
						required: o.defaultRequired,
						radioGroup: radioGroup
					});
				});

				self.fieldsWidgets = self.fieldsWidgets.add(el);
			});

			e.find('input[type!=submit][type!="checkbox"][type!="radio"], select, textarea')
				.not(':formBuilder-inputField')
				.not(':formBuilder-selectionField')
				.not('.form-builder-ignore *')
				.not('[data-load-widget-as-field]')
				.not('[data-load-widget-as-field] *')
				.each(function () {
					var newInput = $(this);

					newInput.inputField({
						required: o.defaultRequired
					});

					self.fields = self.fields.add(newInput);
				});

			/*
			 * set the first class on the first fields for proper styling
			 */
			//setTimeout(function() {
				//var ingroup = e.find('.input-field-group .input-field .field-item:nth(0)').addClass('first');
				//
				e.find('.input-field-group').each(function () {
					$(this).find('.field-item:first').addClass('first');
				});
				//e.find('.input-field .field-item:first-child').not('.input-field-group .input-field:first-child .field-item:first-child').addClass('first');
			//}, 0);
		},

		_proxyCommandToWidget: function (widgetElement, ignoreInvalid) {
			var self = this,
				method = Array.prototype.splice.call(arguments, 2, 1),
				args = Array.prototype.slice.call(arguments, 2),
				widgetName, instance;

			if(widgetElement.is(':formBuilder-inputField')) {
				widgetName = 'formBuilderInputField';
			} else if(widgetElement.is(':formBuilder-selectionField')) {
				widgetName = 'formBuilderSelectionField';
			} else {
				widgetName = widgetElement.data('load-widget-as-field');
			}

			instance = widgetElement.data(widgetName);

			if(ignoreInvalid && (!widgetName || !instance || !instance[method])) {
				return;
			}

			try {
				return instance[method].apply(instance, args);
			}catch(err){
				throw new Error('ERROR: widget field error NAME[' + widgetName + '] METHOD[' + method + ']');
			}
		},

		isDirty: function() {
			var self = this,
				dirty = false;

			self.fields.each(function() {
				dirty = !!$(this).inputField('isDirty');
				return !dirty;
			});

			if(dirty){
				return dirty;
			}

			self.fieldsWidgets.each(function() {
				dirty = !!self._proxyCommandToWidget($(this), false, 'isDirty');
				return !dirty;
			});

			return dirty;
		},

		clearDirty: function() {
			var self = this;
			self.dirty = false;

			self.fields.each(function() {
				$(this).inputField('clearDirty');
			});

			self.fieldsWidgets.each(function() {
				self._proxyCommandToWidget($(this), false, 'clearDirty');
			});
		},

		flashDirty: function(numberOfTimes) {
			//this.dirty.inputField('flash', numberOfTimes);
			/*
			var self = this;
			self.dirty.each(function () {
				self._proxyCommandToWidget($(this), 'flash', numberOfTimes);
			});
			*/
		},

		flashError: function(numberOfTimes) {
			var self = this;

			this.fields.filter(function() {
				return $(this).data('inputField').options.error;
			}).inputField('flash', numberOfTimes);
		},

		disable: function() {
			var self = this;

			this.fields.inputField('status', 'disable', true);

			self.fieldsWidgets.each(function() {
				self._proxyCommandToWidget($(this), true, 'status', 'disabled', true);
			});
		},

		enable: function() {
			var self = this;

			self.fields.inputField('status', 'disable', false);

			self.fieldsWidgets.each(function() {
				self._proxyCommandToWidget($(this), true, 'status', 'disabled', false);
			});

		},

		get: function() {
			var self = this,
				o = self.options,
				formData = self._readDataFromDom();

			if($.isFunction(o.converter.fromForm)) {
				formData = o.converter.fromForm(formData);
			}

			if(!formData){
				formData = {};
			}

			return formData;
		},

		clear: function() {
			var self = this,
				o = self.options;

			self.baseData = {};

			self.fields.each(function() {
				$(this).inputField('clear');
			});

			self.fieldsWidgets.each(function() {
				self._proxyCommandToWidget($(this), false, 'clear');
			});
		},

		/*
		 * check if the given data would overwrite any dirty fields if set
		 *
		 * returns an array of conflict data, and false for no conflicts
		 */
		conflicts: function (data, ignoreKeys) {
			var self = this,
				formData = self._convertFormData(data),
				conflicts = [];

			/*
			 * if dirty flag is not set, assume there is no conflict
			 */
			if(!self.isDirty()){
				return;
			}

			var aData = $.extend(true, {}, self.baseData),
				bData = $.extend(true, {}, formData);

			/*
			 * only check fields that are different than the base value
			 */
			util.removeEqual(aData, bData, ignoreKeys);

			self.fields.each(function() {
				var fieldElement = $(this),
					fieldName = fieldElement.attr('name');

				var conflict = fieldElement.inputField('conflicts', formData[fieldName]);
				if(conflict){
					conflicts.push(conflict);
				}
			});

			self.fieldsWidgets.each(function() {
				var conflict = self._proxyCommandToWidget($(this), false, 'conflicts', formData);
				if(conflict){
					if($.isArray(conflict)){
						conflicts.concat(conflict);
					}else{
						conflicts.push(conflict);
					}
				}
			});

			if(conflicts.length){
				return conflicts;
			}else{
				return false;
			}
		},

		set: function(data, setOptions) {
			var self = this,
				formData = self._convertFormData(data);

			self._trigger('beforeset', null, formData);
			self._writeDataToDom(formData, setOptions);
			self._trigger('afterset', null, formData);
		},

		_convertFormData: function (data) {
			var self = this,
				o = self.options;

			/*
			 * clone data object so form can not change the original
			 */
			var formData = $.extend(true, {}, data || {});

			if($.isFunction(o.converter.toForm)) {
				formData = o.converter.toForm(formData);
			}

			return formData;
		},

		validate: function() {
			var self = this,
				o = self.options,
				valid = true;

			self.fields.each(function() {
				var el = $(this);
				if((!o.ignoreHidden || el.is(':visible')) && el.inputField('validate') === false){
					valid = false;
				}
			});

			self.fieldsWidgets.each(function() {
				var el = $(this);
				if((!o.ignoreHidden || el.is(':visible')) && self._proxyCommandToWidget(el, false, 'validate') === false){
					valid = false;
				}
			});

			return valid;
		},

		_destroy: function() {
			var self = this;
			self.fields.inputField('destroy');
			self.fieldsWidgets.each(function() {
				self._proxyCommandToWidget($(this), false, 'destroy');
			});
		},

		getFields: function() {
			return this.fields;
		},

		_writeDataToDom: function(data, setOptions) {
			var self = this,
				o = self.options;

			self.fields.each(function() {
				$(this).inputField('set', util.selectPath(data, $(this).attr('name')), setOptions);
			});

			self.fieldsWidgets.each(function() {
				self._proxyCommandToWidget($(this), false, 'set', util.selectPath(data, $(this).attr('name')), setOptions);
			});
		},

		_readDataFromDom: function() {
			var self = this,
				ignoreHidden = self.options.ignoreHidden,
				data = {};

			self.fields.each(function() {
				var el = $(this);
				if(ignoreHidden && !el.is(':visible')){
					return;
				}

				var val = el.inputField('get');

				if($.isArray(val)) {
					val = val[0];
				}

				util.insertPath(data, el.attr('name'), val);
			});

			self.fieldsWidgets.each(function() {
				var el = $(this),
					ignoreHidden = self.options.ignoreHidden;

				if(ignoreHidden && !el.is(':visible')){
					return;
				}
				var val = self._proxyCommandToWidget(el, false, 'get');
				util.insertPath(data, el.attr('name'), val);
			});

			return data;
		}
	});

}(jQuery));<commit_msg>Updated proxyCommandToWidget to handle jQuery UI Namespace change<commit_after>/**
 * formBuilder Widget
 *
 * TODO: handle submission, bind to buttons somehow, and on-submit event
 *
 * TODO: maintain a model, take in the model, link to the fields and make is
 * accessible by a getter model linker? listen to changes
 *
 * TODO: can you call _init() a second type with more options? is that how you
 * call _setOption from outside?
 */

(function($) {
	"use strict";
	var util = $.formBuilder.util;

	$.widget("formBuilder.formBuilder", {

		options: {
			converter: {
				fromForm: function(data) {
					return data;
				},
				toForm: function(data) {
					return data;
				}
			},

			// ignore fields that are not $(':visible')
			ignoreHidden: false,

			// default required option for fields
			defaultRequired: false,

			// Hide the form while it is being setup, then show it
			loadHidden: true

		},

		_create: function() {
			var self = this,
				o = self.options,
				e = self.element;

			// Get options
			util.loadDomToggleData(e, o, ['ignoreHidden','defaultRequired','loadHidden']);

			if(o.loadHidden) {
				e.hide();
			}

			self.baseData = {};

			self.fieldsWidgets = $([]);
			self.fields = $([]);

			self.scanForNewFields();

			self.dirty = false;

			/*
			 * proxy the dirty events from all of the fields in the form
			 */
			e.on('dirty', function (ev) {
				if(self.dirty){
					ev.stopPropagation();
					return;
				}
				self.dirty = true;
			}).on('clean', function (ev) {

				/*
				 * if the form is clean kill the clean event
				 */
				if(!self.dirty){
					ev.stopPropagation();
					return;
				}

				/*
				 * check if form is really clean
				 */
				self.dirty = self.isDirty();

				/*
				 * if form is still dirty, kill the clean event
				 */
				if(self.dirty){
					ev.stopPropagation();
				}
			});

			if(o.loadHidden) {
				e.show();
			}
		},

		scanForNewFields: function() {
			var self = this,
				o = self.options,
				e = self.element;

			/*
			 * expected methods for a widget to act a as field:
			 *
			 * set()
			 * get()
			 * isDirty()
			 * clearDirty()
			 * flashDirty()
			 * flashError()
			 * disable()
			 * enable()
			 * clear()
			 * validate()
			 *
			 */
			e.find('[data-load-widget-as-field]').each(function () {
				var el = $(this);

				if(!el.attr('name')){
					throw new Error('data-load-widget-as-field must have name attribute');
				}

				if(self.fieldsWidgets.has(el[0]).length){
					return;
				}

				var widgetName = el.attr('data-load-widget-as-field');
				if(!widgetName || !$.isFunction(el[widgetName])) {
					throw new Error('Cannot load widget "'+widgetName+'" as field, it does not exist');
				}

				el[widgetName]();

				/*
				 * check if the widget extends the base FieldWidget widget
				 */

				self.fieldsWidgets = self.fieldsWidgets.add(el);

				/*
				 * wrap if label is found
				 */
				var label = el.attr('data-label');

				if(!label) {
					return;
				}
				el.addClass('field-items').wrap('

').before('' + label + '');
			});

			e.find('input[type="checkbox"]')
				.not(':formBuilder-selectionField')
				.not('.form-builder-ignore *')
				.not('[data-load-widget-as-field]')
				.not('[data-load-widget-as-field] *')
				.each(function() {
					var el = $(this);

					el.selectionField({
						required: o.defaultRequired
					});

					self.fieldsWidgets = self.fieldsWidgets.add(el);
				});

			var radios = e.find('input[type="radio"]')
				.not(':formBuilder-selectionField')
				.not('.form-builder-ignore *')
				.not('[data-load-widget-as-field]')
				.not('[data-load-widget-as-field] *');

			radios.each(function() {
				var el = $(this);

				if(el.is(':formBuilder-selectionField')) {
					// ignore the ones already grouped
					return;
				}

				var radioGroup = radios.filter('[name="'+this.name+'"]');

				radioGroup.each(function() {
					$(this).selectionField({
						required: o.defaultRequired,
						radioGroup: radioGroup
					});
				});

				self.fieldsWidgets = self.fieldsWidgets.add(el);
			});

			e.find('input[type!=submit][type!="checkbox"][type!="radio"], select, textarea')
				.not(':formBuilder-inputField')
				.not(':formBuilder-selectionField')
				.not('.form-builder-ignore *')
				.not('[data-load-widget-as-field]')
				.not('[data-load-widget-as-field] *')
				.each(function () {
					var newInput = $(this);

					newInput.inputField({
						required: o.defaultRequired
					});

					self.fields = self.fields.add(newInput);
				});

			/*
			 * set the first class on the first fields for proper styling
			 */
			//setTimeout(function() {
				//var ingroup = e.find('.input-field-group .input-field .field-item:nth(0)').addClass('first');
				//
				e.find('.input-field-group').each(function () {
					$(this).find('.field-item:first').addClass('first');
				});
				//e.find('.input-field .field-item:first-child').not('.input-field-group .input-field:first-child .field-item:first-child').addClass('first');
			//}, 0);
		},

		_proxyCommandToWidget: function (widgetElement, ignoreInvalid) {
			var self = this,
				method = Array.prototype.splice.call(arguments, 2, 1),
				args = Array.prototype.slice.call(arguments, 2),
				data = widgetElement.data(),
				widgetName, instance, keys, pattern;

			if(widgetElement.is(':formBuilder-inputField')) {
				widgetName = 'formBuilderInputField';
			} else if(widgetElement.is(':formBuilder-selectionField')) {
				widgetName = 'formBuilderSelectionField';
			} else {
				widgetName = widgetElement.data('load-widget-as-field');
			}

			keys = Object.keys(data);
		    for (var i = 0; i < keys.length; i++) {
		    	var key = keys[i];
	        	if(key.match('-' + widgetName)) {
	        		instance = data[key];
	        	}
		    }

			if(ignoreInvalid && (!widgetName || !instance || !instance[method])) {
				return;
			}

			try {
				return instance[method].apply(instance, args);
			}catch(err){
				throw new Error('ERROR: widget field error NAME[' + widgetName + '] METHOD[' + method + ']');
			}
		},

		isDirty: function() {
			var self = this,
				dirty = false;

			self.fields.each(function() {
				dirty = !!$(this).inputField('isDirty');
				return !dirty;
			});

			if(dirty){
				return dirty;
			}

			self.fieldsWidgets.each(function() {
				dirty = !!self._proxyCommandToWidget($(this), false, 'isDirty');
				return !dirty;
			});

			return dirty;
		},

		clearDirty: function() {
			var self = this;
			self.dirty = false;

			self.fields.each(function() {
				$(this).inputField('clearDirty');
			});

			self.fieldsWidgets.each(function() {
				self._proxyCommandToWidget($(this), false, 'clearDirty');
			});
		},

		flashDirty: function(numberOfTimes) {
			//this.dirty.inputField('flash', numberOfTimes);
			/*
			var self = this;
			self.dirty.each(function () {
				self._proxyCommandToWidget($(this), 'flash', numberOfTimes);
			});
			*/
		},

		flashError: function(numberOfTimes) {
			var self = this;

			this.fields.filter(function() {
				return $(this).data('inputField').options.error;
			}).inputField('flash', numberOfTimes);
		},

		disable: function() {
			var self = this;

			this.fields.inputField('status', 'disable', true);

			self.fieldsWidgets.each(function() {
				self._proxyCommandToWidget($(this), true, 'status', 'disabled', true);
			});
		},

		enable: function() {
			var self = this;

			self.fields.inputField('status', 'disable', false);

			self.fieldsWidgets.each(function() {
				self._proxyCommandToWidget($(this), true, 'status', 'disabled', false);
			});

		},

		get: function() {
			var self = this,
				o = self.options,
				formData = self._readDataFromDom();

			if($.isFunction(o.converter.fromForm)) {
				formData = o.converter.fromForm(formData);
			}

			if(!formData){
				formData = {};
			}

			return formData;
		},

		clear: function() {
			var self = this,
				o = self.options;

			self.baseData = {};

			self.fields.each(function() {
				$(this).inputField('clear');
			});

			self.fieldsWidgets.each(function() {
				self._proxyCommandToWidget($(this), false, 'clear');
			});
		},

		/*
		 * check if the given data would overwrite any dirty fields if set
		 *
		 * returns an array of conflict data, and false for no conflicts
		 */
		conflicts: function (data, ignoreKeys) {
			var self = this,
				formData = self._convertFormData(data),
				conflicts = [];

			/*
			 * if dirty flag is not set, assume there is no conflict
			 */
			if(!self.isDirty()){
				return;
			}

			var aData = $.extend(true, {}, self.baseData),
				bData = $.extend(true, {}, formData);

			/*
			 * only check fields that are different than the base value
			 */
			util.removeEqual(aData, bData, ignoreKeys);

			self.fields.each(function() {
				var fieldElement = $(this),
					fieldName = fieldElement.attr('name');

				var conflict = fieldElement.inputField('conflicts', formData[fieldName]);
				if(conflict){
					conflicts.push(conflict);
				}
			});

			self.fieldsWidgets.each(function() {
				var conflict = self._proxyCommandToWidget($(this), false, 'conflicts', formData);
				if(conflict){
					if($.isArray(conflict)){
						conflicts.concat(conflict);
					}else{
						conflicts.push(conflict);
					}
				}
			});

			if(conflicts.length){
				return conflicts;
			}else{
				return false;
			}
		},

		set: function(data, setOptions) {
			var self = this,
				formData = self._convertFormData(data);

			self._trigger('beforeset', null, formData);
			self._writeDataToDom(formData, setOptions);
			self._trigger('afterset', null, formData);
		},

		_convertFormData: function (data) {
			var self = this,
				o = self.options;

			/*
			 * clone data object so form can not change the original
			 */
			var formData = $.extend(true, {}, data || {});

			if($.isFunction(o.converter.toForm)) {
				formData = o.converter.toForm(formData);
			}

			return formData;
		},

		validate: function() {
			var self = this,
				o = self.options,
				valid = true;

			self.fields.each(function() {
				var el = $(this);
				if((!o.ignoreHidden || el.is(':visible')) && el.inputField('validate') === false){
					valid = false;
				}
			});

			self.fieldsWidgets.each(function() {
				var el = $(this);
				if((!o.ignoreHidden || el.is(':visible')) && self._proxyCommandToWidget(el, false, 'validate') === false){
					valid = false;
				}
			});

			return valid;
		},

		_destroy: function() {
			var self = this;
			self.fields.inputField('destroy');
			self.fieldsWidgets.each(function() {
				self._proxyCommandToWidget($(this), false, 'destroy');
			});
		},

		getFields: function() {
			return this.fields;
		},

		_writeDataToDom: function(data, setOptions) {
			var self = this,
				o = self.options;

			self.fields.each(function() {
				$(this).inputField('set', util.selectPath(data, $(this).attr('name')), setOptions);
			});

			self.fieldsWidgets.each(function() {
				self._proxyCommandToWidget($(this), false, 'set', util.selectPath(data, $(this).attr('name')), setOptions);
			});
		},

		_readDataFromDom: function() {
			var self = this,
				ignoreHidden = self.options.ignoreHidden,
				data = {};

			self.fields.each(function() {
				var el = $(this);
				if(ignoreHidden && !el.is(':visible')){
					return;
				}

				var val = el.inputField('get');

				if($.isArray(val)) {
					val = val[0];
				}

				util.insertPath(data, el.attr('name'), val);
			});

			self.fieldsWidgets.each(function() {
				var el = $(this),
					ignoreHidden = self.options.ignoreHidden;

				if(ignoreHidden && !el.is(':visible')){
					return;
				}
				var val = self._proxyCommandToWidget(el, false, 'get');
				util.insertPath(data, el.attr('name'), val);
			});

			return data;
		}
	});

}(jQuery));<|endoftext|>


<commit_before>// This module defines a reusable Chord Diagram using D3.
define(function(require) {

  // Load D3 v4 via our custom bundle build output.
  var d3 = require("../d3/d3.min");

  // Constructor function for a Chord Diagram.
  // Accepts a container `div` DOM element.
  return function ChordDiagram(div){

    // Configuration parameters.
    var width = 450,
        height = 450,
        outerPadding = 50,
        arcThickness = 20,
        padAngle = 0.07,
        outerRadius = width / 2 - outerPadding,
        innerRadius = outerRadius - arcThickness,
        labelPadding = 10,
        defaultOpacity = 0.6,
        selectedRibbon = null,
        hoveredChordGroup = null,
        data = null,
        onSelectedRibbonChangeCallback = function (){};

    // These "column" variables represent keys in the row objects of the input table.
    // Kibana's `tabify` happens to give us column names "1", "2", and "3".
    // These correspond to the Schemas defined for the plugin.
    var chordWeightColumn = "1",
        chordSourceColumn = "2",
        chordDestinationColumn = "3";

    // Accessor functions for columns.
    var weight = function (d){ return d[chordWeightColumn]; },
        source = function (d){ return d[chordSourceColumn]; },
        destination = function (d){ return d[chordDestinationColumn]; };

    // D3 Local objects for DOM-local storage of label angles and
    // whether or not labels should be flipped upside-down.
    var angle = d3.local(),
        flip = d3.local();

    // DOM Elements.
    var svg = d3.select(div).append("svg")
          .attr("width", width)
          .attr("height", height),
        g = svg.append("g"),
        backgroundRect = g.append("rect")
          .attr("width", width)
          .attr("height", height)
          .attr("fill", "none")
          .style("pointer-events", "all"),
        ribbonsG = g.append("g")
          .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"),
        chordGroupsG = g.append("g")
          .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

    // D3 layouts, shapes and scales.
    var ribbon = d3.ribbon()
          .radius(innerRadius),
        chord = d3.chord()
          .padAngle(padAngle),
        color = d3.scaleOrdinal()
          .range(d3.schemeCategory20),
        arc = d3.arc()
          .innerRadius(innerRadius)
          .outerRadius(outerRadius);

    // Clear the selected ribbon when clicking on
    // any area other than on a ribbon.
    backgroundRect.on("mousedown", function (){
      my.selectedRibbon(null);
    });

    // Renders the given data as a chord diagram.
    function my(){

      // Use the data passed into the .data() accessor.
      if(data){

        // Pre-process the data and calculate the Chord Diagram layout.
        var matrix = generateMatrix(data),
            chords = chord(matrix);

        // Use alphanumerically sorted source and destination names
        // for the color scale domain for consistent colors across refreshes.
        color.domain(matrix.names.slice().sort());

        // Render the ribbons of the Chord Diagram (the connecting fibers inside the circle).
        var ribbons = ribbonsG
          .selectAll("path")
            .data(chords);
        ribbons = ribbons.enter().append("path").merge(ribbons);
        ribbons
          .attr("d", ribbon)
          .style("fill", function(d) {
            return color(d.source.index);
          })
          .style("stroke", "black")
          .style("stroke-opacity", 0.2)
          .call(setRibbonOpacity)
          .on("mousedown", function (d){
            my.selectedRibbon({
              sourceIndex: d.source.index,
              targetIndex: d.target.index,
              source: matrix.names[d.source.index],
              destination: matrix.names[d.target.index]
            });
          });
        ribbons.exit().remove();

        // Scaffold the chord groups.
        var chordGroups = chordGroupsG.selectAll("g").data(chords.groups);
        var chordGroupsEnter = chordGroups.enter().append("g");
        chordGroupsEnter.append("text");
        chordGroupsEnter.append("path");
        chordGroups.exit().remove();
        chordGroups = chordGroups.merge(chordGroupsEnter);

        // Add labels
        chordGroups
          .select("text")
            .each(function(d) {
              angle.set(this, (d.startAngle + d.endAngle) / 2);
              flip.set(this, angle.get(this) > Math.PI);
            })
            .attr("transform", function(d) {
              return [
                "rotate(" + (angle.get(this) / Math.PI * 180 - 90) + ")",
                "translate(" + (outerRadius + labelPadding) + ")",
                flip.get(this) ? "rotate(180)" : ""
              ].join("");
            })
            .attr("text-anchor", function(d) {
              return flip.get(this) ? "end" : "start";
            })
            .attr("alignment-baseline", "central")
            .text(function(d) {
              return matrix.names[d.index];
            })
            .style("cursor", "default")
            .call(chordGroupHover);

        // Render the chord group arcs.
        chordGroups
          .select("path")
            .attr("d", arc)
            .style("fill", function(group) {
              return color(group.index);
            })
            .call(chordGroupHover);

        // Sets up hover interaction to highlight a chord group.
        // Used for both the arcs and the text labels.
        function chordGroupHover(selection){
          selection
            .on("mouseover", function (group){
              hoveredChordGroup = group;
              setRibbonOpacity(ribbons);
            })
            .on("mouseout", function (){
              hoveredChordGroup = null;
              setRibbonOpacity(ribbons);
            });
        }

        // Sets the opacity values for all ribbons.
        function setRibbonOpacity(selection){
          selection
            .transition().duration(500)
            .style("opacity", function (d){

              // If there is a currently selected ribbon,
              if(selectedRibbon){

                // show the selected chord in full color,
                if(
                  (selectedRibbon.sourceIndex === d.source.index) &&
                  (selectedRibbon.targetIndex === d.target.index)
                ){
                  return defaultOpacity;
                } else {

                  // and show all others faded out.
                  return 0.1;
                }
              } else {

                // If there is no currently selected ribbon,
                // then if there is a hovered chord group,
                if(hoveredChordGroup){

                  // show the ribbons connected to the hovered chord group in full color,
                  if(
                    (d.source.index === hoveredChordGroup.index) ||
                    (d.target.index === hoveredChordGroup.index)
                  ){
                    return defaultOpacity;
                  } else {

                    // and show all others faded out.
                    return 0.1;
                  }
                } else {

                  // Otherwise show all ribbons with slight transparency.
                  return defaultOpacity;
                }
              }
            });
        }
      }
    }

    // Generates a matrix (2D array) from the given data, which is expected to
    // have fields {origin, destination, count}. The matrix data structure is required
    // for use with the D3 Chord layout.
    function generateMatrix(data){
      var indices = {},
          matrix = [],
          names = [],
          n = 0, i, j;

      function recordIndex(name){
        if( !(name in indices) ){
          indices[name] = n++;
          names.push(name);
        }
      }

      data.forEach(function (d){
        recordIndex(source(d));
        recordIndex(destination(d));
      });

      for(i = 0; i < n; i++){
        matrix.push([]);
        for(j = 0; j < n; j++){
          matrix[i].push(0);
        }
      }

      data.forEach(function (d){
        i = indices[source(d)];
        j = indices[destination(d)];
        matrix[i][j] = weight(d);
      });

      matrix.names = names;

      return matrix;
    }

    my.onSelectedRibbonChange = function (callback){
      onSelectedRibbonChangeCallback = callback;
    };

    // Gets or sets the selected ribbon object,
    // which can be expected to be null
    // or an object with the following fields:
    //
    //  * sourceIndex - The matrix index of the source chord group.
    //  * targetIndex - The matrix index of the destination chord group.
    //  * source - The source name (data value).
    //  * destination - The destination name (data value).
    my.selectedRibbon = function (_){
      if(typeof _ !== "undefined"){
        selectedRibbon = _;
        onSelectedRibbonChangeCallback();
        my();
      } else {
        return selectedRibbon;
      }
    };

    my.data = function (_){
      data = _;
    }

    return my;
  }
});
<commit_msg>Use name as color scale input. Really closes #56<commit_after>// This module defines a reusable Chord Diagram using D3.
define(function(require) {

  // Load D3 v4 via our custom bundle build output.
  var d3 = require("../d3/d3.min");

  // Constructor function for a Chord Diagram.
  // Accepts a container `div` DOM element.
  return function ChordDiagram(div){

    // Configuration parameters.
    var width = 450,
        height = 450,
        outerPadding = 50,
        arcThickness = 20,
        padAngle = 0.07,
        outerRadius = width / 2 - outerPadding,
        innerRadius = outerRadius - arcThickness,
        labelPadding = 10,
        defaultOpacity = 0.6,
        selectedRibbon = null,
        hoveredChordGroup = null,
        data = null,
        onSelectedRibbonChangeCallback = function (){};

    // These "column" variables represent keys in the row objects of the input table.
    // Kibana's `tabify` happens to give us column names "1", "2", and "3".
    // These correspond to the Schemas defined for the plugin.
    var chordWeightColumn = "1",
        chordSourceColumn = "2",
        chordDestinationColumn = "3";

    // Accessor functions for columns.
    var weight = function (d){ return d[chordWeightColumn]; },
        source = function (d){ return d[chordSourceColumn]; },
        destination = function (d){ return d[chordDestinationColumn]; };

    // D3 Local objects for DOM-local storage of label angles and
    // whether or not labels should be flipped upside-down.
    var angle = d3.local(),
        flip = d3.local();

    // DOM Elements.
    var svg = d3.select(div).append("svg")
          .attr("width", width)
          .attr("height", height),
        g = svg.append("g"),
        backgroundRect = g.append("rect")
          .attr("width", width)
          .attr("height", height)
          .attr("fill", "none")
          .style("pointer-events", "all"),
        ribbonsG = g.append("g")
          .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"),
        chordGroupsG = g.append("g")
          .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

    // D3 layouts, shapes and scales.
    var ribbon = d3.ribbon()
          .radius(innerRadius),
        chord = d3.chord()
          .padAngle(padAngle),
        color = d3.scaleOrdinal()
          .range(d3.schemeCategory20),
        arc = d3.arc()
          .innerRadius(innerRadius)
          .outerRadius(outerRadius);

    // Clear the selected ribbon when clicking on
    // any area other than on a ribbon.
    backgroundRect.on("mousedown", function (){
      my.selectedRibbon(null);
    });

    // Renders the given data as a chord diagram.
    function my(){

      // Use the data passed into the .data() accessor.
      if(data){

        // Pre-process the data and calculate the Chord Diagram layout.
        var matrix = generateMatrix(data),
            chords = chord(matrix);

        // Use alphanumerically sorted source and destination names
        // for the color scale domain for consistent colors across refreshes.
        color.domain(matrix.names.slice().sort());

        // Render the ribbons of the Chord Diagram (the connecting fibers inside the circle).
        var ribbons = ribbonsG
          .selectAll("path")
            .data(chords);
        ribbons = ribbons.enter().append("path").merge(ribbons);
        ribbons
          .attr("d", ribbon)
          .style("fill", function(d) {
            return color(matrix.names[d.source.index]);
          })
          .style("stroke", "black")
          .style("stroke-opacity", 0.2)
          .call(setRibbonOpacity)
          .on("mousedown", function (d){
            my.selectedRibbon({
              sourceIndex: d.source.index,
              targetIndex: d.target.index,
              source: matrix.names[d.source.index],
              destination: matrix.names[d.target.index]
            });
          });
        ribbons.exit().remove();

        // Scaffold the chord groups.
        var chordGroups = chordGroupsG.selectAll("g").data(chords.groups);
        var chordGroupsEnter = chordGroups.enter().append("g");
        chordGroupsEnter.append("text");
        chordGroupsEnter.append("path");
        chordGroups.exit().remove();
        chordGroups = chordGroups.merge(chordGroupsEnter);

        // Add labels
        chordGroups
          .select("text")
            .each(function(d) {
              angle.set(this, (d.startAngle + d.endAngle) / 2);
              flip.set(this, angle.get(this) > Math.PI);
            })
            .attr("transform", function(d) {
              return [
                "rotate(" + (angle.get(this) / Math.PI * 180 - 90) + ")",
                "translate(" + (outerRadius + labelPadding) + ")",
                flip.get(this) ? "rotate(180)" : ""
              ].join("");
            })
            .attr("text-anchor", function(d) {
              return flip.get(this) ? "end" : "start";
            })
            .attr("alignment-baseline", "central")
            .text(function(d) {
              return matrix.names[d.index];
            })
            .style("cursor", "default")
            .call(chordGroupHover);

        // Render the chord group arcs.
        chordGroups
          .select("path")
            .attr("d", arc)
            .style("fill", function(group) {
              return color(group.index);
            })
            .call(chordGroupHover);

        // Sets up hover interaction to highlight a chord group.
        // Used for both the arcs and the text labels.
        function chordGroupHover(selection){
          selection
            .on("mouseover", function (group){
              hoveredChordGroup = group;
              setRibbonOpacity(ribbons);
            })
            .on("mouseout", function (){
              hoveredChordGroup = null;
              setRibbonOpacity(ribbons);
            });
        }

        // Sets the opacity values for all ribbons.
        function setRibbonOpacity(selection){
          selection
            .transition().duration(500)
            .style("opacity", function (d){

              // If there is a currently selected ribbon,
              if(selectedRibbon){

                // show the selected chord in full color,
                if(
                  (selectedRibbon.sourceIndex === d.source.index) &&
                  (selectedRibbon.targetIndex === d.target.index)
                ){
                  return defaultOpacity;
                } else {

                  // and show all others faded out.
                  return 0.1;
                }
              } else {

                // If there is no currently selected ribbon,
                // then if there is a hovered chord group,
                if(hoveredChordGroup){

                  // show the ribbons connected to the hovered chord group in full color,
                  if(
                    (d.source.index === hoveredChordGroup.index) ||
                    (d.target.index === hoveredChordGroup.index)
                  ){
                    return defaultOpacity;
                  } else {

                    // and show all others faded out.
                    return 0.1;
                  }
                } else {

                  // Otherwise show all ribbons with slight transparency.
                  return defaultOpacity;
                }
              }
            });
        }
      }
    }

    // Generates a matrix (2D array) from the given data, which is expected to
    // have fields {origin, destination, count}. The matrix data structure is required
    // for use with the D3 Chord layout.
    function generateMatrix(data){
      var indices = {},
          matrix = [],
          names = [],
          n = 0, i, j;

      function recordIndex(name){
        if( !(name in indices) ){
          indices[name] = n++;
          names.push(name);
        }
      }

      data.forEach(function (d){
        recordIndex(source(d));
        recordIndex(destination(d));
      });

      for(i = 0; i < n; i++){
        matrix.push([]);
        for(j = 0; j < n; j++){
          matrix[i].push(0);
        }
      }

      data.forEach(function (d){
        i = indices[source(d)];
        j = indices[destination(d)];
        matrix[i][j] = weight(d);
      });

      matrix.names = names;

      return matrix;
    }

    my.onSelectedRibbonChange = function (callback){
      onSelectedRibbonChangeCallback = callback;
    };

    // Gets or sets the selected ribbon object,
    // which can be expected to be null
    // or an object with the following fields:
    //
    //  * sourceIndex - The matrix index of the source chord group.
    //  * targetIndex - The matrix index of the destination chord group.
    //  * source - The source name (data value).
    //  * destination - The destination name (data value).
    my.selectedRibbon = function (_){
      if(typeof _ !== "undefined"){
        selectedRibbon = _;
        onSelectedRibbonChangeCallback();
        my();
      } else {
        return selectedRibbon;
      }
    };

    my.data = function (_){
      data = _;
    }

    return my;
  }
});
<|endoftext|>


<commit_before>// The WEBSOCKET server and port the bot should connect to.
// Most of the time this isn't the same as the URL, check the `Request URL` of
// the websocket.
// If you really don't know how to do this... Run `node getserver.js URL`.
// Fill in the URL of the client where `URL` is.
// For example: `node getserver.js http://example-server.psim.us/`
exports.server = 'cbc.pokecommunity.com';
exports.port = 8000;

// This is the server id.
// To know this one, you should check where the AJAX call 'goes' to when you
// log in.
// For example, on the Smogon server, it will say somewhere in the URL
// ~~showdown, meaning that the server id is 'showdown'.
// If you really don't know how to check this... run the said script above.
exports.serverid = 'pokecommunity';

// The nick and password to log in with
// If no password is required, leave pass empty
exports.nick = 'PokeCommBot';
exports.pass = 'filler';

// The rooms that should be joined.
// Joining Smogon's Showdown's Lobby is not allowed.
exports.rooms = ['lobby'];

// Any private rooms that should be joined.
// Private rooms will be moderated differently (since /warn doesn't work in them).
// The bot will also avoid leaking the private rooms through .seen
exports.privaterooms = ['staff'];

// The character text should start with to be seen as a command.
// Note that using / and ! might be 'dangerous' since these are used in
// Showdown itself.
// Using only alphanumeric characters and spaces is not allowed.
exports.commandcharacter = '.';

// The default rank is the minimum rank that can use a command in a room when
// no rank is specified in settings.json
exports.defaultrank = '%';

// Whether this file should be watched for changes or not.
// If you change this option, the server has to be restarted in order for it to
// take effect.
exports.watchconfig = true;

// Secondary websocket protocols should be defined here, however, Showdown
// doesn't support that yet, so it's best to leave this empty.
exports.secprotocols = [];

// What should be logged?
// 0 = error, ok, info, debug, recv, send
// 1 = error, ok, info, debug, cmdr, send
// 2 = error, ok, info, debug (recommended for development)
// 3 = error, ok, info (recommended for production)
// 4 = error, ok
// 5 = error
exports.debuglevel = 3;

// Users who can use all commands regardless of their rank. Be very cautious
// with this, especially on servers other than main.
exports.excepts = [];

// Whitelisted users are those who the bot will not enforce moderation for.
exports.whitelist = ['PokeCommBot'];

// Add a link to the help for the bot here. When there is a link here, .help and .guide
// will link to it.
exports.botguide = 'http://www.pokecommunity.com/showthread.php?t=289012#bot';

// This allows the bot to act as an automated moderator. If enabled, the bot will
// mute users who send 6 lines or more in 6 or fewer seconds for 7 minutes. NOTE: THIS IS
// BY NO MEANS A PERFECT MODERATOR OR SCRIPT. It is a bot and so cannot think for itself or
// exercise moderator discretion. In addition, it currently uses a very simple method of
// determining who to mute and so may miss people who should be muted, or mute those who
// shouldn't. Use with caution.
exports.allowmute = true;

// The punishment values system allows you to customise how you want the bot to deal with
// rulebreakers. Spamming has a points value of 2, all caps has a points value of 1, etc.
exports.punishvals = {
	1: 'warn',
	2: 'mute',
	3: 'hourmute',
	4: 'roomban',
	5: 'ban'
};

<commit_msg>Update config-example.js<commit_after>// The WEBSOCKET server and port the bot should connect to.
// Most of the time this isn't the same as the URL, check the `Request URL` of
// the websocket.
// If you really don't know how to do this... Run `node getserver.js URL`.
// Fill in the URL of the client where `URL` is.
// For example: `node getserver.js http://example-server.psim.us/`
exports.server = 'cbc.pokecommunity.com';
exports.port = 8000;

// This is the server id.
// To know this one, you should check where the AJAX call 'goes' to when you
// log in.
// For example, on the Smogon server, it will say somewhere in the URL
// ~~showdown, meaning that the server id is 'showdown'.
// If you really don't know how to check this... run the said script above.
exports.serverid = 'pokecommunity';

// The nick and password to log in with
// If no password is required, leave pass empty
exports.nick = 'PokeCommBot';
exports.pass = 'filler';

// The rooms that should be joined.
// Joining Smogon's Showdown's Lobby is not allowed.
exports.rooms = ['chat', 'lobby'];

// Any private rooms that should be joined.
// Private rooms will be moderated differently (since /warn doesn't work in them).
// The bot will also avoid leaking the private rooms through .seen
exports.privaterooms = ['staff'];

// The character text should start with to be seen as a command.
// Note that using / and ! might be 'dangerous' since these are used in
// Showdown itself.
// Using only alphanumeric characters and spaces is not allowed.
exports.commandcharacter = '.';

// The default rank is the minimum rank that can use a command in a room when
// no rank is specified in settings.json
exports.defaultrank = '%';

// Whether this file should be watched for changes or not.
// If you change this option, the server has to be restarted in order for it to
// take effect.
exports.watchconfig = true;

// Secondary websocket protocols should be defined here, however, Showdown
// doesn't support that yet, so it's best to leave this empty.
exports.secprotocols = [];

// What should be logged?
// 0 = error, ok, info, debug, recv, send
// 1 = error, ok, info, debug, cmdr, send
// 2 = error, ok, info, debug (recommended for development)
// 3 = error, ok, info (recommended for production)
// 4 = error, ok
// 5 = error
exports.debuglevel = 3;

// Users who can use all commands regardless of their rank. Be very cautious
// with this, especially on servers other than main.
exports.excepts = [];

// Whitelisted users are those who the bot will not enforce moderation for.
exports.whitelist = ['PokeCommBot'];

// Add a link to the help for the bot here. When there is a link here, .help and .guide
// will link to it.
exports.botguide = 'http://www.pokecommunity.com/showthread.php?t=289012#bot';

// This allows the bot to act as an automated moderator. If enabled, the bot will
// mute users who send 6 lines or more in 6 or fewer seconds for 7 minutes. NOTE: THIS IS
// BY NO MEANS A PERFECT MODERATOR OR SCRIPT. It is a bot and so cannot think for itself or
// exercise moderator discretion. In addition, it currently uses a very simple method of
// determining who to mute and so may miss people who should be muted, or mute those who
// shouldn't. Use with caution.
exports.allowmute = true;

// The punishment values system allows you to customise how you want the bot to deal with
// rulebreakers. Spamming has a points value of 2, all caps has a points value of 1, etc.
exports.punishvals = {
	1: 'warn',
	2: 'mute',
	3: 'hourmute',
	4: 'roomban',
	5: 'ban'
};

<|endoftext|>


<commit_before>/**
 * Copyright (c) 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * Facebook, Inc. ("Facebook") owns all right, title and interest, including
 * all intellectual property and other proprietary rights, in and to the React
 * Native CustomComponents software (the "Software").  Subject to your
 * compliance with these terms, you are hereby granted a non-exclusive,
 * worldwide, royalty-free copyright license to (1) use and copy the Software;
 * and (2) reproduce and distribute the Software as part of your own software
 * ("Your Software").  Facebook reserves all rights not expressly granted to
 * you in this license agreement.
 *
 * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
 * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
 * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @providesModule VirtualizedSectionList
 * @flow
 */
'use strict';

const React = require('React');
const View = require('View');
const VirtualizedList = require('VirtualizedList');

const invariant = require('invariant');
const warning = require('warning');

import type {Viewable} from 'ViewabilityHelper';
import type {Props as VirtualizedListProps} from 'VirtualizedList';

type Item = any;
type SectionItem = any;

type SectionBase = {
  // Must be provided directly on each section.
  data: Array<SectionItem>,
  key: string,

  // Optional props will override list-wide props just for this section.
  ItemComponent?: ?ReactClass<{item: SectionItem, index: number}>,
  SeparatorComponent?: ?ReactClass<*>,
  keyExtractor?: (item: SectionItem) => string,

  // TODO: support more optional/override props
  // FooterComponent?: ?ReactClass<*>,
  // HeaderComponent?: ?ReactClass<*>,
  // onViewableItemsChanged?: ({viewableItems: Array<Viewable>, changed: Array<Viewable>}) => void,

  // TODO: support recursive sections
  // SectionHeaderComponent?: ?ReactClass<{section: SectionBase}>,
  // sections?: ?Array<Section>;
};

type RequiredProps<SectionT: SectionBase> = {
  sections: Array<SectionT>,
};

type OptionalProps<SectionT: SectionBase> = {
  /**
   * Rendered after the last item in the last section.
   */
  FooterComponent?: ?ReactClass<*>,
  /**
   * Default renderer for every item in every section.
   */
  ItemComponent: ReactClass<{item: Item, index: number}>,
  /**
   * Rendered at the top of each section. In the future, a sticky option will be added.
   */
  SectionHeaderComponent?: ?ReactClass<{section: SectionT}>,
  /**
   * Rendered at the bottom of every Section, except the very last one, in place of the normal
   * SeparatorComponent.
   */
  SectionSeparatorComponent?: ?ReactClass<*>,
  /**
   * Rendered at the bottom of every Item except the very last one in the last section.
   */
  SeparatorComponent?: ?ReactClass<*>,
  /**
   * Warning: Virtualization can drastically improve memory consumption for long lists, but trashes
   * the state of items when they scroll out of the render window, so make sure all relavent data is
   * stored outside of the recursive `ItemComponent` instance tree.
   */
  enableVirtualization?: ?boolean,
  keyExtractor: (item: Item, index: number) => string,
  onEndReached?: ?({distanceFromEnd: number}) => void,
  /**
   * If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make
   * sure to also set the `refreshing` prop correctly.
   */
  onRefresh?: ?Function,
  /**
   * Called when the viewability of rows changes, as defined by the
   * `viewablePercentThreshold` prop.
   */
  onViewableItemsChanged?: ?({viewableItems: Array<Viewable>, changed: Array<Viewable>}) => void,
  /**
   * Set this true while waiting for new data from a refresh.
   */
  refreshing?: ?boolean,
  /**
   * This is an optional optimization to minimize re-rendering items.
   */
  shouldItemUpdate: (
    prevProps: {item: Item, index: number},
    nextProps: {item: Item, index: number}
  ) => boolean,
};

export type Props<SectionT> =
  RequiredProps<SectionT> &
  OptionalProps<SectionT> &
  VirtualizedListProps;

type DefaultProps = (typeof VirtualizedList.defaultProps) & {data: Array<Item>};
type State = {childProps: VirtualizedListProps};

/**
 * Right now this just flattens everything into one list and uses VirtualizedList under the
 * hood. The only operation that might not scale well is concatting the data arrays of all the
 * sections when new props are received, which should be plenty fast for up to ~10,000 items.
 */
class VirtualizedSectionList<SectionT: SectionBase>
  extends React.PureComponent<DefaultProps, Props<SectionT>, State>
{
  props: Props<SectionT>;

  state: State;

  static defaultProps: DefaultProps = {
    ...VirtualizedList.defaultProps,
    data: [],
  };

  _keyExtractor = (item: Item, index: number) => {
    const info = this._subExtractor(index);
    return (info && info.key) || String(index);
  };

  _subExtractor(
    index: number,
  ): ?{
    section: SectionT,
    key: string, // Key of the section or combined key for section + item
    index: ?number, // Relative index within the section
  } {
    let itemIndex = index;
    const defaultKeyExtractor = this.props.keyExtractor;
    for (let ii = 0; ii < this.props.sections.length; ii++) {
      const section = this.props.sections[ii];
      const keyExtractor = section.keyExtractor || defaultKeyExtractor;
      const key = keyExtractor(section, ii);
      itemIndex -= 1; // The section itself is an item
      if (itemIndex >= section.data.length) {
        itemIndex -= section.data.length;
      } else if (itemIndex === -1) {
        return {section, key, index: null};
      } else {
        return {
          section,
          key: key + ':' + keyExtractor(section.data[itemIndex], itemIndex),
          index: itemIndex,
        };
      }
    }
  }

  _convertViewable = (viewable: Viewable): ?Viewable => {
    invariant(viewable.index != null, 'Received a broken Viewable');
    const info = this._subExtractor(viewable.index);
    if (!info) {
      return null;
    }
    const keyExtractor = info.section.keyExtractor || this.props.keyExtractor;
    return {
      ...viewable,
      index: info.index,
      key: keyExtractor(viewable.item, info.index),
      section: info.section,
    };
  };

  _onViewableItemsChanged = (
    {viewableItems, changed}: {viewableItems: Array<Viewable>, changed: Array<Viewable>}
  ) => {
    if (this.props.onViewableItemsChanged) {
      this.props.onViewableItemsChanged({
        viewableItems: viewableItems.map(this._convertViewable, this).filter(Boolean),
        changed: changed.map(this._convertViewable, this).filter(Boolean),
      });
    }
  }

  _isItemSticky = (item, index) => {
    const info = this._subExtractor(index);
    return info && info.index == null;
  };

  _renderItem = ({item, index}: {item: Item, index: number}) => {
    const info = this._subExtractor(index);
    if (!info) {
      return null;
    } else if (info.index == null) {
      return <this.props.SectionHeaderComponent section={info.section} />;
    } else {
      const ItemComponent = info.section.ItemComponent || this.props.ItemComponent;
      const SeparatorComponent = info.section.SeparatorComponent || this.props.SeparatorComponent;
      const {SectionSeparatorComponent} = this.props;
      const [shouldRenderSectionSeparator, shouldRenderItemSeparator] =
        this._shouldRenderSeparators(index, info);
      return (
        <View>
          <ItemComponent item={item} index={info.index} />
          {shouldRenderItemSeparator ? <SeparatorComponent /> : null}
          {shouldRenderSectionSeparator ? <SectionSeparatorComponent /> : null}
        </View>
      );
    }
  };

  _shouldRenderSeparators(index: number, info?: ?Object): [boolean, boolean] {
    info = info || this._subExtractor(index);
    if (!info) {
      return [false, false];
    }
    const SeparatorComponent = info.section.SeparatorComponent || this.props.SeparatorComponent;
    const {SectionSeparatorComponent} = this.props;
    const lastItemIndex = this.state.childProps.getItemCount() - 1;
    let shouldRenderSectionSeparator = false;
    if (SectionSeparatorComponent) {
      shouldRenderSectionSeparator =
        info.index === info.section.data.length - 1 && index < lastItemIndex;
    }
    const shouldRenderItemSeparator = SeparatorComponent && !shouldRenderSectionSeparator &&
      index < lastItemIndex;
    return [shouldRenderSectionSeparator, shouldRenderItemSeparator];
  }

  _shouldItemUpdate = (prev, next) => {
    const {shouldItemUpdate} = this.props;
    if (!shouldItemUpdate || shouldItemUpdate(prev, next)) {
      return true;
    }
    if (prev.index !== next.index) {
      const [secSepPrev, itSepPrev] = this._shouldRenderSeparators(prev.index);
      const [secSepNext, itSepNext] = this._shouldRenderSeparators(next.index);
      if (secSepPrev !== secSepNext || itSepPrev !== itSepNext) {
        return true;
      }
    }
    return false;
  }

  _computeState(props: Props<SectionT>): State {
    const itemCount = props.sections.reduce((v, section) => v + section.data.length + 1, 0);
    return {
      childProps: {
        ...props,
        ItemComponent: this._renderItem,
        SeparatorComponent: undefined, // Rendered with ItemComponent
        data: props.sections,
        getItemCount: () => itemCount,
        getItem,
        isItemSticky: this._isItemSticky,
        keyExtractor: this._keyExtractor,
        onViewableItemsChanged:
          props.onViewableItemsChanged ? this._onViewableItemsChanged : undefined,
        shouldItemUpdate: this._shouldItemUpdate,
      },
    };
  }

  constructor(props: Props<SectionT>, context: Object) {
    super(props, context);
    warning(
      !props.stickySectionHeadersEnabled,
      'VirtualizedSectionList: Sticky headers only supported with legacyImplementation for now.'
    );
    this.state = this._computeState(props);
  }

  componentWillReceiveProps(nextProps: Props<SectionT>) {
    this.setState(this._computeState(nextProps));
  }

  render() {
    return <VirtualizedList {...this.state.childProps} />;
  }
}

function getItem(sections: ?Array<Item>, index: number): ?Item {
  if (!sections) {
    return null;
  }
  let itemIdx = index - 1;
  for (let ii = 0; ii < sections.length; ii++) {
    if (itemIdx === -1) {
      return sections[ii]; // The section itself is the item
    } else if (itemIdx < sections[ii].data.length) {
      return sections[ii].data[itemIdx];
    } else {
      itemIdx -= (sections[ii].data.length + 1);
    }
  }
  return null;
}

module.exports = VirtualizedSectionList;
<commit_msg>Clean up separator stuff<commit_after>/**
 * Copyright (c) 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * Facebook, Inc. ("Facebook") owns all right, title and interest, including
 * all intellectual property and other proprietary rights, in and to the React
 * Native CustomComponents software (the "Software").  Subject to your
 * compliance with these terms, you are hereby granted a non-exclusive,
 * worldwide, royalty-free copyright license to (1) use and copy the Software;
 * and (2) reproduce and distribute the Software as part of your own software
 * ("Your Software").  Facebook reserves all rights not expressly granted to
 * you in this license agreement.
 *
 * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
 * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
 * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @providesModule VirtualizedSectionList
 * @flow
 */
'use strict';

const React = require('React');
const View = require('View');
const VirtualizedList = require('VirtualizedList');

const invariant = require('invariant');
const warning = require('warning');

import type {Viewable} from 'ViewabilityHelper';
import type {Props as VirtualizedListProps} from 'VirtualizedList';

type Item = any;
type SectionItem = any;

type SectionBase = {
  // Must be provided directly on each section.
  data: Array<SectionItem>,
  key: string,

  // Optional props will override list-wide props just for this section.
  ItemComponent?: ?ReactClass<{item: SectionItem, index: number}>,
  SeparatorComponent?: ?ReactClass<*>,
  keyExtractor?: (item: SectionItem) => string,

  // TODO: support more optional/override props
  // FooterComponent?: ?ReactClass<*>,
  // HeaderComponent?: ?ReactClass<*>,
  // onViewableItemsChanged?: ({viewableItems: Array<Viewable>, changed: Array<Viewable>}) => void,

  // TODO: support recursive sections
  // SectionHeaderComponent?: ?ReactClass<{section: SectionBase}>,
  // sections?: ?Array<Section>;
};

type RequiredProps<SectionT: SectionBase> = {
  sections: Array<SectionT>,
};

type OptionalProps<SectionT: SectionBase> = {
  /**
   * Rendered after the last item in the last section.
   */
  FooterComponent?: ?ReactClass<*>,
  /**
   * Default renderer for every item in every section.
   */
  ItemComponent: ReactClass<{item: Item, index: number}>,
  /**
   * Rendered at the top of each section. In the future, a sticky option will be added.
   */
  SectionHeaderComponent?: ?ReactClass<{section: SectionT}>,
  /**
   * Rendered at the bottom of every Section, except the very last one, in place of the normal
   * SeparatorComponent.
   */
  SectionSeparatorComponent?: ?ReactClass<*>,
  /**
   * Rendered at the bottom of every Item except the very last one in the last section.
   */
  SeparatorComponent?: ?ReactClass<*>,
  /**
   * Warning: Virtualization can drastically improve memory consumption for long lists, but trashes
   * the state of items when they scroll out of the render window, so make sure all relavent data is
   * stored outside of the recursive `ItemComponent` instance tree.
   */
  enableVirtualization?: ?boolean,
  keyExtractor: (item: Item, index: number) => string,
  onEndReached?: ?({distanceFromEnd: number}) => void,
  /**
   * If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make
   * sure to also set the `refreshing` prop correctly.
   */
  onRefresh?: ?Function,
  /**
   * Called when the viewability of rows changes, as defined by the
   * `viewablePercentThreshold` prop.
   */
  onViewableItemsChanged?: ?({viewableItems: Array<Viewable>, changed: Array<Viewable>}) => void,
  /**
   * Set this true while waiting for new data from a refresh.
   */
  refreshing?: ?boolean,
  /**
   * This is an optional optimization to minimize re-rendering items.
   */
  shouldItemUpdate: (
    prevProps: {item: Item, index: number},
    nextProps: {item: Item, index: number}
  ) => boolean,
};

export type Props<SectionT> =
  RequiredProps<SectionT> &
  OptionalProps<SectionT> &
  VirtualizedListProps;

type DefaultProps = (typeof VirtualizedList.defaultProps) & {data: Array<Item>};
type State = {childProps: VirtualizedListProps};

/**
 * Right now this just flattens everything into one list and uses VirtualizedList under the
 * hood. The only operation that might not scale well is concatting the data arrays of all the
 * sections when new props are received, which should be plenty fast for up to ~10,000 items.
 */
class VirtualizedSectionList<SectionT: SectionBase>
  extends React.PureComponent<DefaultProps, Props<SectionT>, State>
{
  props: Props<SectionT>;

  state: State;

  static defaultProps: DefaultProps = {
    ...VirtualizedList.defaultProps,
    data: [],
  };

  _keyExtractor = (item: Item, index: number) => {
    const info = this._subExtractor(index);
    return (info && info.key) || String(index);
  };

  _subExtractor(
    index: number,
  ): ?{
    section: SectionT,
    key: string, // Key of the section or combined key for section + item
    index: ?number, // Relative index within the section
  } {
    let itemIndex = index;
    const defaultKeyExtractor = this.props.keyExtractor;
    for (let ii = 0; ii < this.props.sections.length; ii++) {
      const section = this.props.sections[ii];
      const keyExtractor = section.keyExtractor || defaultKeyExtractor;
      const key = keyExtractor(section, ii);
      itemIndex -= 1; // The section itself is an item
      if (itemIndex >= section.data.length) {
        itemIndex -= section.data.length;
      } else if (itemIndex === -1) {
        return {section, key, index: null};
      } else {
        return {
          section,
          key: key + ':' + keyExtractor(section.data[itemIndex], itemIndex),
          index: itemIndex,
        };
      }
    }
  }

  _convertViewable = (viewable: Viewable): ?Viewable => {
    invariant(viewable.index != null, 'Received a broken Viewable');
    const info = this._subExtractor(viewable.index);
    if (!info) {
      return null;
    }
    const keyExtractor = info.section.keyExtractor || this.props.keyExtractor;
    return {
      ...viewable,
      index: info.index,
      key: keyExtractor(viewable.item, info.index),
      section: info.section,
    };
  };

  _onViewableItemsChanged = (
    {viewableItems, changed}: {viewableItems: Array<Viewable>, changed: Array<Viewable>}
  ) => {
    if (this.props.onViewableItemsChanged) {
      this.props.onViewableItemsChanged({
        viewableItems: viewableItems.map(this._convertViewable, this).filter(Boolean),
        changed: changed.map(this._convertViewable, this).filter(Boolean),
      });
    }
  }

  _isItemSticky = (item, index) => {
    const info = this._subExtractor(index);
    return info && info.index == null;
  };

  _renderItem = ({item, index}: {item: Item, index: number}) => {
    const info = this._subExtractor(index);
    if (!info) {
      return null;
    } else if (info.index == null) {
      return <this.props.SectionHeaderComponent section={info.section} />;
    } else {
      const ItemComponent = info.section.ItemComponent || this.props.ItemComponent;
      const SeparatorComponent = this._getSeparatorComponent(index, info);
      return (
        <View>
          <ItemComponent item={item} index={info.index} />
          {SeparatorComponent && <SeparatorComponent />}
        </View>
      );
    }
  };

  _getSeparatorComponent(index: number, info?: ?Object): ?ReactClass<*> {
    info = info || this._subExtractor(index);
    if (!info) {
      return null;
    }
    const SeparatorComponent = info.section.SeparatorComponent || this.props.SeparatorComponent;
    const {SectionSeparatorComponent} = this.props;
    const lastItemIndex = this.state.childProps.getItemCount() - 1;
    if (SectionSeparatorComponent &&
        info.index === info.section.data.length - 1 &&
        index < lastItemIndex) {
      return SectionSeparatorComponent;
    }
    if (SeparatorComponent && index < lastItemIndex) {
      return SeparatorComponent;
    }
    return null;
  }

  _shouldItemUpdate = (prev, next) => {
    const {shouldItemUpdate} = this.props;
    if (!shouldItemUpdate || shouldItemUpdate(prev, next)) {
      return true;
    }
    return this._getSeparatorComponent(prev.index) !== this._getSeparatorComponent(next.index);
  }

  _computeState(props: Props<SectionT>): State {
    const itemCount = props.sections.reduce((v, section) => v + section.data.length + 1, 0);
    return {
      childProps: {
        ...props,
        ItemComponent: this._renderItem,
        SeparatorComponent: undefined, // Rendered with ItemComponent
        data: props.sections,
        getItemCount: () => itemCount,
        getItem,
        isItemSticky: this._isItemSticky,
        keyExtractor: this._keyExtractor,
        onViewableItemsChanged:
          props.onViewableItemsChanged ? this._onViewableItemsChanged : undefined,
        shouldItemUpdate: this._shouldItemUpdate,
      },
    };
  }

  constructor(props: Props<SectionT>, context: Object) {
    super(props, context);
    warning(
      !props.stickySectionHeadersEnabled,
      'VirtualizedSectionList: Sticky headers only supported with legacyImplementation for now.'
    );
    this.state = this._computeState(props);
  }

  componentWillReceiveProps(nextProps: Props<SectionT>) {
    this.setState(this._computeState(nextProps));
  }

  render() {
    return <VirtualizedList {...this.state.childProps} />;
  }
}

function getItem(sections: ?Array<Item>, index: number): ?Item {
  if (!sections) {
    return null;
  }
  let itemIdx = index - 1;
  for (let ii = 0; ii < sections.length; ii++) {
    if (itemIdx === -1) {
      return sections[ii]; // The section itself is the item
    } else if (itemIdx < sections[ii].data.length) {
      return sections[ii].data[itemIdx];
    } else {
      itemIdx -= (sections[ii].data.length + 1);
    }
  }
  return null;
}

module.exports = VirtualizedSectionList;
<|endoftext|>


<commit_before>// (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP

import React from 'react';
import DocsArticle from '../../DocsArticle';
import Example from '../Example';
import Distribution from 'grommet/components/Distribution';
import Legend from 'grommet/components/Legend';

Distribution.displayName = 'Distribution';

const series = [
  {label: 'First', value: 40, colorIndex: 'graph-1'},
  {label: 'Second', value: 30, colorIndex: 'accent-2'},
  {label: 'Third', value: 20, colorIndex: 'unset'},
  {label: 'Fourth', value: 10, colorIndex: 'graph-1'}
];

const clickableSeries = [
  {label: 'First', value: 40, onClick: function () {
    alert('You\'ve clicked on 40');
  }},
  {label: 'Second', value: 30, onClick: function () {
    alert('You\'ve clicked on 30');
  }},
  {label: 'Third', value: 20, onClick: function () {
    alert('You\'ve clicked on 20');
  }},
  {label: 'Fourth', value: 10, onClick: function () {
    alert('You\'ve clicked on 10');
  }}
];

const iconSeries = [
  {label: 'Female', value: 60, icon: {
    width: 36, height: 36,
    svgElement: (
      <g fill="none" strokeWidth={1}>
        <path d="M20,36 L20,32"></path>
        <path d="M24,13 C21.2,13 19,15.2 19,18 C19,22 15.9,23.9 15.9,23.9 C15.9,23.9 16.6,26 20,26 L21,26 C18.2,26 16,28.2 16,31 L16,36"></path>
        <path d="M28,32 L28,36"></path>
        <path d="M32,36 L32,31 C32,28.2 29.9,26 27.1,26 L27,26 C30.4,26 32.1,23.9 32.1,23.9 C32.1,23.9 29,22 29,18 C29,15.2 26.7,13 24,13"></path>
      </g>
    )}
  },
  {label: 'Male', value: 40, icon: {
    width: 36, height: 36,
    svgElement: (
      <g fill="none" strokeWidth={1}>
        <circle cx="24" cy="18" r="5"></circle>
        <path d="M33,36 L33,31 C33,26.6 29.4,23 25,23 L23,23 C18.6,23 15,26.6 15,31 L15,36"></path>
        <path d="M20,36 L20,31"></path>
        <path d="M28,36 L28,31"></path>
      </g>
    )}
  }
];

const THRESHOLDS = [
  { label: '90% or more', value: 90, colorIndex: 'accent-1' },
  { label: '40% or more', value: 40, colorIndex: 'graph-1' },
  { label: 'less than 40%', value: 0, colorIndex: 'unset' }
];

const MultipleAxisLabel = ({ text, value, unit }) => (

      {value} {unit}

    {text}

);

const multipleAxisSeries = [
  {
    label: <MultipleAxisLabel text="First" value={57} unit="% cpu" />,
    value: 5.3,
    colorIndex: 'graph-1'
  }, {
    label: <MultipleAxisLabel text="Second" value={39} unit="% cpu" />,
    value: 4.7,
    colorIndex: 'unset'
  }, {
    label: <MultipleAxisLabel text="Third" value={96} unit="% cpu" />,
    value: 2.2,
    colorIndex: 'accent-1'
  }, {
    label: <MultipleAxisLabel text="Forth" value={25} unit="% cpu" />,
    value: 1.9,
    colorIndex: 'unset'
  }, {
    label: <MultipleAxisLabel text="Fifth" value={50} unit="% cpu" />,
    value: 3.9,
    colorIndex: 'graph-1'
  }, {
    label: <MultipleAxisLabel text="Sixth" value={81} unit="% cpu" />,
    value: 1.4,
    colorIndex: 'graph-1'
  }, {
    label: <MultipleAxisLabel text="Seventh" value={13} unit="% cpu" />,
    value: 2.1,
    colorIndex: 'unset'
  }, {
    label: <MultipleAxisLabel text="Eighth" value={99} unit="% cpu" />,
    value: 1,
    colorIndex: 'accent-1'
  }
];

export default () => {

  return (
    <DocsArticle title="Distribution" colorIndex="neutral-3">

Shows a graphic of relatively sized items.
        If colorIndex properties are not specified per series item, they
        will be automatically asigned.
        One way to use a Distribution is to compare items across
        two attributes. One attribute determining the area and another
        determining the color.
        The guidance for coloring items is to use colorIndex 'unset'
        for less important items, neutral or graph colors for normal
        items and accent colors for items to call attention to. Status
        colorIndex values can be used as well.

        {"<Distribution series={[...]} />"}

Options

full        true|false

Whether the height should fill its container.

legend      true|false

Whether to show a legend.

legendTotal true|false

Whether to show a total in the legend.

series      {"[{value: , label: , labelValue: , colorIndex: , onClick: , icon: }, ...]"}

An array of objects describing the
            data. All properties except value are
            optional. labelValue is used as the
            visible value. If labelValue is not
            set, the value is displayed instead.

size         small|medium|large

The height of the Distribution. Defaults to medium.

units       {"{string}"}

Optional units to display next to the value label.

Examples

        <Example name="Basic" code={
          <Distribution series={series} a11yTitleId='distribution-title-1'
            a11yDescId='distribution-desc-1' />
        } />
        <Example name="Legend" code={
          <Distribution legend={true} series={series}
            a11yTitleId='distribution-title-2' a11yDescId='distribution-desc-2' />
        } />
        <Example name="Small" code={
          <Distribution size="small" series={series}
            a11yTitleId='distribution-title-3' a11yDescId='distribution-desc-3' />
        } />
        <Example name="Large" code={
          <Distribution size="large" series={series}
            a11yTitleId='distribution-title-4' a11yDescId='distribution-desc-4' />
        } />
        <Example name="onClick" code={
          <Distribution series={clickableSeries}
            a11yTitleId='distribution-title-5' a11yDescId='distribution-desc-5' />
        } />
        <Example name="Multiple Axis" code={

            <Distribution full={true} units="GB" series={multipleAxisSeries}
              a11yTitleId='distribution-title-6' a11yDescId='distribution-desc-6' />
            <Legend
              series={
                THRESHOLDS.map(threshold => ({
                  label: threshold.label,
                  colorIndex: threshold.colorIndex
                }))
              }
            />

        } />
        <Example name="Icon" code={
          <Distribution series={iconSeries} units="%"
            a11yTitleId='distribution-title-7' a11yDescId='distribution-desc-7' />
        } overrides={['svgElement']}/>
        <Example name="Loading" code={
          <Distribution a11yTitleId='distribution-title-8'
            a11yDescId='distribution-desc-8' />
        } />

    </DocsArticle>
  );
};
<commit_msg>Generate multiple axis series data<commit_after>// (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP

import React from 'react';
import DocsArticle from '../../DocsArticle';
import Example from '../Example';
import Distribution from 'grommet/components/Distribution';
import Legend from 'grommet/components/Legend';

Distribution.displayName = 'Distribution';

const series = [
  {label: 'First', value: 40, colorIndex: 'graph-1'},
  {label: 'Second', value: 30, colorIndex: 'accent-2'},
  {label: 'Third', value: 20, colorIndex: 'unset'},
  {label: 'Fourth', value: 10, colorIndex: 'graph-1'}
];

const clickableSeries = [
  {label: 'First', value: 40, onClick: function () {
    alert('You\'ve clicked on 40');
  }},
  {label: 'Second', value: 30, onClick: function () {
    alert('You\'ve clicked on 30');
  }},
  {label: 'Third', value: 20, onClick: function () {
    alert('You\'ve clicked on 20');
  }},
  {label: 'Fourth', value: 10, onClick: function () {
    alert('You\'ve clicked on 10');
  }}
];

const iconSeries = [
  {label: 'Female', value: 60, icon: {
    width: 36, height: 36,
    svgElement: (
      <g fill="none" strokeWidth={1}>
        <path d="M20,36 L20,32"></path>
        <path d="M24,13 C21.2,13 19,15.2 19,18 C19,22 15.9,23.9 15.9,23.9 C15.9,23.9 16.6,26 20,26 L21,26 C18.2,26 16,28.2 16,31 L16,36"></path>
        <path d="M28,32 L28,36"></path>
        <path d="M32,36 L32,31 C32,28.2 29.9,26 27.1,26 L27,26 C30.4,26 32.1,23.9 32.1,23.9 C32.1,23.9 29,22 29,18 C29,15.2 26.7,13 24,13"></path>
      </g>
    )}
  },
  {label: 'Male', value: 40, icon: {
    width: 36, height: 36,
    svgElement: (
      <g fill="none" strokeWidth={1}>
        <circle cx="24" cy="18" r="5"></circle>
        <path d="M33,36 L33,31 C33,26.6 29.4,23 25,23 L23,23 C18.6,23 15,26.6 15,31 L15,36"></path>
        <path d="M20,36 L20,31"></path>
        <path d="M28,36 L28,31"></path>
      </g>
    )}
  }
];

const THRESHOLDS = [
  { label: '90% or more', value: 90, colorIndex: 'accent-1' },
  { label: '40% or more', value: 40, colorIndex: 'graph-1' },
  { label: 'less than 40%', value: 0, colorIndex: 'unset' }
];

const MultipleAxisLabel = ({ text, value, unit }) => (

      {value} {unit}

    {text}

);

const generateSeries = (label) => {
  const areaValue = Math.round((Math.random() * 8 + 2) * 10) / 10;
  const colorValue = Math.floor(Math.random() * 99) + 1;
  let colorIndex;
  for (let i = 0; i < THRESHOLDS.length; i++) {
    if (colorValue >= THRESHOLDS[i].value) {
      colorIndex = THRESHOLDS[i].colorIndex;
      break;
    }
  }

  return {
    colorIndex,
    label: <MultipleAxisLabel text={label} value={colorValue} unit="% cpu" />,
    value: areaValue
  };
};

const multipleAxisSeries = ['First', 'Second', 'Third', 'Forth', 'Fifth', 'Sixth', 'Seventh', 'Eighth'].map(generateSeries);

export default () => {

  return (
    <DocsArticle title="Distribution" colorIndex="neutral-3">

Shows a graphic of relatively sized items.
        If colorIndex properties are not specified per series item, they
        will be automatically asigned.
        One way to use a Distribution is to compare items across
        two attributes. One attribute determining the area and another
        determining the color.
        The guidance for coloring items is to use colorIndex 'unset'
        for less important items, neutral or graph colors for normal
        items and accent colors for items to call attention to. Status
        colorIndex values can be used as well.

        {"<Distribution series={[...]} />"}

Options

full        true|false

Whether the height should fill its container.

legend      true|false

Whether to show a legend.

legendTotal true|false

Whether to show a total in the legend.

series      {"[{value: , label: , labelValue: , colorIndex: , onClick: , icon: }, ...]"}

An array of objects describing the
            data. All properties except value are
            optional. labelValue is used as the
            visible value. If labelValue is not
            set, the value is displayed instead.

size         small|medium|large

The height of the Distribution. Defaults to medium.

units       {"{string}"}

Optional units to display next to the value label.

Examples

        <Example name="Basic" code={
          <Distribution series={series} a11yTitleId='distribution-title-1'
            a11yDescId='distribution-desc-1' />
        } />
        <Example name="Legend" code={
          <Distribution legend={true} series={series}
            a11yTitleId='distribution-title-2' a11yDescId='distribution-desc-2' />
        } />
        <Example name="Small" code={
          <Distribution size="small" series={series}
            a11yTitleId='distribution-title-3' a11yDescId='distribution-desc-3' />
        } />
        <Example name="Large" code={
          <Distribution size="large" series={series}
            a11yTitleId='distribution-title-4' a11yDescId='distribution-desc-4' />
        } />
        <Example name="onClick" code={
          <Distribution series={clickableSeries}
            a11yTitleId='distribution-title-5' a11yDescId='distribution-desc-5' />
        } />
        <Example name="Multiple Axis" code={

            <Distribution full={true} units="GB" series={multipleAxisSeries}
              a11yTitleId='distribution-title-6' a11yDescId='distribution-desc-6' />
            <Legend
              series={
                THRESHOLDS.map(threshold => ({
                  label: threshold.label,
                  colorIndex: threshold.colorIndex
                }))
              }
            />

        } />
        <Example name="Icon" code={
          <Distribution series={iconSeries} units="%"
            a11yTitleId='distribution-title-7' a11yDescId='distribution-desc-7' />
        } overrides={['svgElement']}/>
        <Example name="Loading" code={
          <Distribution a11yTitleId='distribution-title-8'
            a11yDescId='distribution-desc-8' />
        } />

    </DocsArticle>
  );
};
<|endoftext|>


<commit_before>var _ = require('underscore');
var Backbone = require('backbone');
var WindshaftFiltersCollection = require('./filters/collection');
var WindshaftFiltersBoundingBoxFilter = require('./filters/bounding_box');
var WindshaftDashboardInstance = require('./dashboard-instance');

var WindshaftDashboard = function(options) {
  BOUNDING_BOX_FILTER_WAIT = 500;

  this.layerGroup = options.layerGroup;
  this.layers = new Backbone.Collection(options.layers);
  this.widgets = options.widgets;
  this.map = options.map;
  this.client = options.client;
  this.statTag = options.statTag;
  this.configGenerator = options.configGenerator;

  this.instance = new WindshaftDashboardInstance();

  this.map.bind('change:center change:zoom', _.debounce(this._boundingBoxChanged, BOUNDING_BOX_FILTER_WAIT), this);

  this.layers.bind('change', this._layerChanged, this);
  this.widgets.bind('change:filter', this._filterChanged, this);

  this._createInstance();
};

WindshaftDashboard.prototype._createInstance = function(options) {
  var options = options || {};

  var dashboardConfig = this.configGenerator.generate({
    layers: this.layers.models,
    widgets: this.widgets
  });

  var filtersFromVisibleLayers = this.widgets.chain()
    .filter(function(w) { return w.layer.isVisible() })
    .map(function(w) { return w.filter })
    .compact() // not all widgets have filters
    .value();

  var filters = new WindshaftFiltersCollection(filtersFromVisibleLayers);

  this.client.instantiateMap({
    mapDefinition: dashboardConfig,
    filters: filters.toJSON(),
    success: function(dashboardInstance) {

      // Update the dashboard instance with the attributes of the new one
      this.instance.set(dashboardInstance.toJSON());

      // TODO: Set the URL of the attributes service once it's available
      this.layerGroup && this.layerGroup.set({
        baseURL: dashboardInstance.getBaseURL(),
        urls: dashboardInstance.getTiles('mapnik')
      });

      // update other kind of layers too
      this.layers.each(function(layer, layerIndex) {
        layer.set('meta', dashboardInstance.getLayerMeta(layerIndex));
        if (layer.get('type') === 'torque') {
          layer.set('urls', dashboardInstance.getTiles('torque'));
        }
      });

      this._updateWidgetURLs({
        layerId: options.layerId
      });

    }.bind(this),
    error: function(error) {
      console.log('Error creating dashboard instance: ' + error);
    }
  });

  return this.instance;
};

WindshaftDashboard.prototype._boundingBoxChanged = function() {
  if (this.instance.isLoaded()) {
    this._updateWidgetURLs();
  }
};

WindshaftDashboard.prototype._updateWidgetURLs = function(options) {
  options = options || {};
  var boundingBoxFilter = new WindshaftFiltersBoundingBoxFilter(this.map.getViewBounds());
  var boundingBox = boundingBoxFilter.toString();
  var layerId = options.layerId;

  this.widgets.each(function(widget) {
    var url = this.instance.getWidgetURL({
      widgetId: widget.get('id'),
      protocol: 'http'
    });

    var layerMeta = widget.layer.get('meta') || {};
    var extraAttrs = {};
    if (layerMeta.steps && layerMeta.column_type && _.isNumber(layerMeta.start) && _.isNumber(layerMeta.end)) {
      extraAttrs = {
        bins: layerMeta.steps,
        columnType: layerMeta.column_type,
        start: layerMeta.start  / 1000,
        end:  layerMeta.end / 1000
      };
    }

    widget.set(_.extend({
      'url': url,
      'boundingBox': boundingBox
    }, extraAttrs), {
      silent: layerId && layerId !== widget.layer.get('id')
    });
  }, this);
};

WindshaftDashboard.prototype._filterChanged = function(w) {
  this._createInstance({
    layerId: w.layer.get('id')
  });
};

WindshaftDashboard.prototype._layerChanged = function(layer) {
  var layerId = layer.get('id');
  this._createInstance({
    layerId: layerId
  });
};

module.exports = WindshaftDashboard;
<commit_msg>Set meta only for torque layers for now<commit_after>var _ = require('underscore');
var Backbone = require('backbone');
var WindshaftFiltersCollection = require('./filters/collection');
var WindshaftFiltersBoundingBoxFilter = require('./filters/bounding_box');
var WindshaftDashboardInstance = require('./dashboard-instance');

var WindshaftDashboard = function(options) {
  BOUNDING_BOX_FILTER_WAIT = 500;

  this.layerGroup = options.layerGroup;
  this.layers = new Backbone.Collection(options.layers);
  this.widgets = options.widgets;
  this.map = options.map;
  this.client = options.client;
  this.statTag = options.statTag;
  this.configGenerator = options.configGenerator;

  this.instance = new WindshaftDashboardInstance();

  this.map.bind('change:center change:zoom', _.debounce(this._boundingBoxChanged, BOUNDING_BOX_FILTER_WAIT), this);

  this.layers.bind('change', this._layerChanged, this);
  this.widgets.bind('change:filter', this._filterChanged, this);

  this._createInstance();
};

WindshaftDashboard.prototype._createInstance = function(options) {
  var options = options || {};

  var dashboardConfig = this.configGenerator.generate({
    layers: this.layers.models,
    widgets: this.widgets
  });

  var filtersFromVisibleLayers = this.widgets.chain()
    .filter(function(w) { return w.layer.isVisible() })
    .map(function(w) { return w.filter })
    .compact() // not all widgets have filters
    .value();

  var filters = new WindshaftFiltersCollection(filtersFromVisibleLayers);

  this.client.instantiateMap({
    mapDefinition: dashboardConfig,
    filters: filters.toJSON(),
    success: function(dashboardInstance) {

      // Update the dashboard instance with the attributes of the new one
      this.instance.set(dashboardInstance.toJSON());

      // TODO: Set the URL of the attributes service once it's available
      this.layerGroup && this.layerGroup.set({
        baseURL: dashboardInstance.getBaseURL(),
        urls: dashboardInstance.getTiles('mapnik')
      });

      // update other kind of layers too
      this.layers.each(function(layer, layerIndex) {
        if (layer.get('type') === 'torque') {
          layer.set('meta', dashboardInstance.getLayerMeta(layerIndex));
          layer.set('urls', dashboardInstance.getTiles('torque'));
        }
      });

      this._updateWidgetURLs({
        layerId: options.layerId
      });

    }.bind(this),
    error: function(error) {
      console.log('Error creating dashboard instance: ' + error);
    }
  });

  return this.instance;
};

WindshaftDashboard.prototype._boundingBoxChanged = function() {
  if (this.instance.isLoaded()) {
    this._updateWidgetURLs();
  }
};

WindshaftDashboard.prototype._updateWidgetURLs = function(options) {
  options = options || {};
  var boundingBoxFilter = new WindshaftFiltersBoundingBoxFilter(this.map.getViewBounds());
  var boundingBox = boundingBoxFilter.toString();
  var layerId = options.layerId;

  this.widgets.each(function(widget) {
    var url = this.instance.getWidgetURL({
      widgetId: widget.get('id'),
      protocol: 'http'
    });

    var layerMeta = widget.layer.get('meta') || {};
    var extraAttrs = {};
    if (layerMeta.steps && layerMeta.column_type && _.isNumber(layerMeta.start) && _.isNumber(layerMeta.end)) {
      extraAttrs = {
        bins: layerMeta.steps,
        columnType: layerMeta.column_type,
        start: layerMeta.start  / 1000,
        end:  layerMeta.end / 1000
      };
    }

    widget.set(_.extend({
      'url': url,
      'boundingBox': boundingBox
    }, extraAttrs), {
      silent: layerId && layerId !== widget.layer.get('id')
    });
  }, this);
};

WindshaftDashboard.prototype._filterChanged = function(w) {
  this._createInstance({
    layerId: w.layer.get('id')
  });
};

WindshaftDashboard.prototype._layerChanged = function(layer) {
  var layerId = layer.get('id');
  this._createInstance({
    layerId: layerId
  });
};

module.exports = WindshaftDashboard;
<|endoftext|>


<commit_before>// (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP

import React, { Component } from 'react';
import Chart,
  { Layers, Base, Grid, Bar, Marker, MarkerLabel, HotSpots, Area, Axis, Range }
  from 'grommet/components/chart/Chart';
import Box from 'grommet/components/Box';
import Value from 'grommet/components/Value';
import Label from 'grommet/components/Label';
import Example from '../../../Example';

const VALUES = [
  100, 95, 80, 82, 75, 70, 60, 55, 0, 15, 40, 50,
  100, 95, 80, 82, 75, 70, 60, 55, 0, 15, 40, 50,
  100, 95, 80, 82, 75, 70, 60, 55, 0, 15, 40, 50,
  100, 95, 80, 82, 75, 70, 60, 55, 0, 15, 40, 50,
  100, 95, 80, 82, 75, 70, 60, 55, 0, 15, 40, 50
];

const UNIT_AXIS_LABELS = [
  { index: 0, label: 'June' },
  { index: 1, label: 'July' },
  { index: 2, label: 'August' }
];

export default class Chart16 extends Component {

  constructor () {
    super();
    this._onRangeActive = this._onRangeActive.bind(this);
    const active = { start: 10, end: 18 };
    this.state = this._stateFromActive(active);
  }

  _stateFromActive (active) {
    const activeValues = VALUES.slice(active.start, active.end + 1);
    return {
      active: active,
      activeValues: activeValues,
      index: (activeValues.length - 1)
    };
  }

  _onRangeActive (active) {
    this.setState(this._stateFromActive(active));
  }

  _dateForIndex (index) {
    const axisIndex =
      Math.max(0, Math.min(
        Math.floor(index / 30), UNIT_AXIS_LABELS.length - 1));
    const label = UNIT_AXIS_LABELS[axisIndex];
    const day = (index % 30) + 1;
    return `${label.label} ${day}`;
  }

  render () {
    const { size } = this.props;
    const { active, activeValues, index } = this.state;

    let markerLabel;
    if (index || 0 === index) {
      markerLabel = (
        <Box direction="row" pad={{ between: 'small' }}
          align="center">
          <Label className="secondary">
            {this._dateForIndex(active.start + index)}
          </Label>
          <Value value={activeValues[index]} />
        </Box>
      );
    }

    let activeAxis = [
      { index: 0, label: this._dateForIndex(active.start) },
      { index: 1, label: this._dateForIndex(active.end) }
    ];

    return (
      <Example code={
        <Box>
          <Chart vertical={true}>
            <Axis ticks={true} tickAlign="end" count={3}
              labels={UNIT_AXIS_LABELS} />
            <Base height="xsmall" width="large" />
            <Layers>
              <Area values={VALUES} />
              <Range count={VALUES.length}
                active={active} onActive={this._onRangeActive} />
            </Layers>
          </Chart>
          <Chart vertical={true}>
            <MarkerLabel count={activeValues.length} index={index}
              label={markerLabel} />
            <Base height={size} width="large" />
            <Layers>
              <Grid rows={3} />
              <Marker vertical={true} colorIndex="graph-2"
                count={activeValues.length} index={index} />
              <Bar values={activeValues} activeIndex={index} />
              <HotSpots count={activeValues.length}
                activeIndex={index}
                onActive={(index) => this.setState({
                  index: (undefined === index ?
                    (activeValues.length - 1) : index)
                })} />
            </Layers>
            <Axis ticks={true} count={2} labels={activeAxis} />
          </Chart>
        </Box>
      } />
    );
  }

};
<commit_msg>Tweaked one chart example.<commit_after>// (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP

import React, { Component } from 'react';
import Chart,
  { Layers, Base, Grid, Bar, Marker, MarkerLabel, HotSpots, Area, Axis, Range }
  from 'grommet/components/chart/Chart';
import Box from 'grommet/components/Box';
import Value from 'grommet/components/Value';
import Label from 'grommet/components/Label';
import Example from '../../../Example';

const VALUES = [
  100, 95, 80, 82, 75, 70, 60, 55, 0, 15, 40, 50,
  100, 95, 80, 82, 75, 70, 60, 55, 0, 15, 40, 50,
  100, 95, 80, 82, 75, 70, 60, 55, 0, 15, 40, 50,
  100, 95, 80, 82, 75, 70, 60, 55, 0, 15, 40, 50,
  100, 95, 80, 82, 75, 70, 60, 55, 0, 15, 40, 50
];

const UNIT_AXIS_LABELS = [
  { index: 0, label: 'June' },
  { index: 1, label: 'July' },
  { index: 2, label: 'August' }
];

export default class Chart16 extends Component {

  constructor () {
    super();
    this._onRangeActive = this._onRangeActive.bind(this);
    const active = { start: 10, end: 18 };
    this.state = this._stateFromActive(active);
  }

  _stateFromActive (active) {
    const activeValues = VALUES.slice(active.start, active.end + 1);
    return {
      active: active,
      activeValues: activeValues,
      index: (activeValues.length - 1)
    };
  }

  _onRangeActive (active) {
    this.setState(this._stateFromActive(active));
  }

  _dateForIndex (index) {
    const axisIndex =
      Math.max(0, Math.min(
        Math.floor(index / 30), UNIT_AXIS_LABELS.length - 1));
    const label = UNIT_AXIS_LABELS[axisIndex];
    const day = (index % 30) + 1;
    return `${label.label} ${day}`;
  }

  render () {
    const { size } = this.props;
    const { active, activeValues, index } = this.state;

    let markerLabel;
    if (index || 0 === index) {
      markerLabel = (
        <Box direction="row" pad={{ between: 'small' }}
          align="center">
          <Label className="secondary">
            {this._dateForIndex(active.start + index)}
          </Label>
          <Value value={activeValues[index]} />
        </Box>
      );
    }

    let activeAxis = [
      { index: 0, label: this._dateForIndex(active.start) },
      { index: 1, label: this._dateForIndex(active.end) }
    ];

    return (
      <Example code={
        <Box>
          <Chart vertical={true}>
            <Axis ticks={true} tickAlign="end" count={3}
              labels={UNIT_AXIS_LABELS} />
            <Base height="xxsmall" width="large" />
            <Layers>
              <Area values={VALUES} />
              <Range count={VALUES.length}
                active={active} onActive={this._onRangeActive} />
            </Layers>
          </Chart>
          <Chart vertical={true}>
            <MarkerLabel count={activeValues.length} index={index}
              label={markerLabel} />
            <Base height={size} width="large" />
            <Layers>
              <Grid rows={3} />
              <Marker vertical={true} colorIndex="graph-2"
                count={activeValues.length} index={index} />
              <Bar values={activeValues} activeIndex={index} />
              <HotSpots count={activeValues.length}
                activeIndex={index}
                onActive={(index) => this.setState({
                  index: (undefined === index ?
                    (activeValues.length - 1) : index)
                })} />
            </Layers>
            <Axis ticks={true} count={2} labels={activeAxis} />
          </Chart>
        </Box>
      } />
    );
  }

};
<|endoftext|>