<|start_filename|>src/js/charts/cb-xy/parse-xy.js<|end_filename|>
var clone = require("lodash/clone");
var map = require("lodash/map");
var assign = require("lodash/assign");
var each = require("lodash/each");
var filter = require("lodash/filter");

var dataBySeries = require("../../util/parse-data-by-series");
var help = require("../../util/helper");
var SessionStore = require("../../stores/SessionStore");

var scaleNames = ["primaryScale", "secondaryScale"];

/**
 * see [ChartConfig#parser](#chartconfig/parser)
 * @see ChartConfig#parser
 * @instance
 * @memberof xy_config
 */
function parseXY(config, _chartProps, callback, parseOpts) {
	// Build chart settings from defaults or provided settings

	parseOpts = parseOpts || {};
	// clone so that we aren't modifying original
	// this can probably be avoided by applying new settings differently
	var chartProps = JSON.parse(JSON.stringify(_chartProps));
	var bySeries = dataBySeries(chartProps.input.raw, {
		checkForDate: true,
		type: chartProps.input.type
	});

	var labels = chartProps._annotations.labels;
	var allColumn = true;
	// check if either scale contains columns, as we'll need to zero the axis
	var _primaryColumn = false;
	var _secondaryColumn = false;
	var _scaleComputed = {};

	each(scaleNames, function(name) {
		_scaleComputed[name] = {
			data: [],
			hasColumn: false,
			count: 0
		};
	});

	var chartSettings = map(bySeries.series, function(dataSeries, i) {
		var settings;

		if (chartProps.chartSettings[i]) {
			settings = chartProps.chartSettings[i];
		} else {
			settings = clone(config.defaultProps.chartProps.chartSettings[0]);
			settings.colorIndex = i;
		}

		if (parseOpts.columnsChanged) {
			settings.label = dataSeries.name;
		} else {
			settings.label = settings.label || dataSeries.name;
		}

		var values = map(dataSeries.values, function(d) {
			return +d.value;
		});

		// add data points to relevant scale
		if (settings.altAxis === false) {

			var _computed = _scaleComputed.primaryScale;
			_computed.data = _computed.data.concat(values);
			_computed.count += 1;
			if (settings.type == "column") {
				_computed.hasColumn = true;
			}

		} else {

			var _computed = _scaleComputed.secondaryScale;
			_computed.data = _computed.data.concat(values);
			_computed.count += 1;
			if (settings.type == "column") {
				_computed.hasColumn = true;
			}

		}

		return settings;

	});

	labels.values = map(bySeries.series, function(dataSeries, i) {
		if (labels.values[i]) {
			return assign({}, { name: chartSettings[i].label}, labels.values[i]);
		} else {
			return { name: dataSeries.name };
		}
	});

	var maxPrecision = 5;
	var factor = Math.pow(10, maxPrecision);

	var scale = {};
	var mobileScale = {};

	// Calculate domain and tick values for any scales that exist
	each(scaleNames, function(name) {
		var _computed = _scaleComputed[name];
		if (_computed.count > 0) {
			var currScale = chartProps.scale[name] || clone(config.defaultProps.chartProps.scale.primaryScale);
			var domain = help.computeScaleDomain(currScale, _computed.data, {
				nice: true,
				minZero: _computed.hasColumn
			});
			assign(currScale, domain);

			var ticks;
			if (name === "primaryScale") {
				ticks = currScale.ticks;
			} else {
				ticks = scale.primaryScale.ticks;
			}

			currScale.tickValues = help.exactTicks(currScale.domain, ticks);
			each(currScale.tickValues, function(v) {
				var tickPrecision = help.precision(Math.round(v*factor)/factor);
				if (tickPrecision > currScale.precision) {
					currScale.precision = tickPrecision;
				}
			});

			scale[name] = currScale;

			if (chartProps.mobile) {
				if (chartProps.mobile.scale) {
					var currMobile = chartProps.mobile.scale[name];
					if (currMobile) {
						var domain = help.computeScaleDomain(currMobile, _computed.data, {
							nice: true,
							minZero: _computed.hasColumn
						});
						assign(currMobile, domain);

						var ticks = (name == "primaryScale") ? currMobile.ticks : scale.primaryScale.ticks;
						currMobile.tickValues = help.exactTicks(currMobile.domain, ticks);
						each(currMobile.tickValues, function(v) {
							var tickPrecision = help.precision(Math.round(v*factor)/factor);
							if (tickPrecision > currMobile.precision) {
								currMobile.precision = tickPrecision;
							}
						});
					}
				}
			} else {
				chartProps.mobile = {};
			}
		}
	});

	// If there is only one primary and >0 secondary, color the left axis
	if (_scaleComputed.primaryScale.count === 1 && _scaleComputed.secondaryScale.count > 0) {
		scale.primaryScale.colorIndex = filter(chartSettings, function(series) {
			return (series.altAxis === false);
		})[0].colorIndex;
	} else {
		scale.primaryScale.colorIndex = null;
	}
	// If there is only one secondary and >0 primary, color the right axis
	if (_scaleComputed.secondaryScale.count === 1 && _scaleComputed.primaryScale.count > 0) {
		scale.secondaryScale.colorIndex = filter(chartSettings, function(series) {
			return (series.altAxis === true);
		})[0].colorIndex;
	} else if (_scaleComputed.secondaryScale.count > 0) {
		scale.secondaryScale.colorIndex = null;
	}

	// create the data structure for the renederer based on input
	if (bySeries.hasDate) {
		scale.hasDate = bySeries.hasDate;
		scale.dateSettings = chartProps.scale.dateSettings || clone(config.defaultProps.chartProps.scale.dateSettings);
		scale.dateSettings.inputTZ = scale.dateSettings.inputTZ || SessionStore.get("nowOffset")
	}

	if (bySeries.isNumeric) {
		scale.isNumeric = bySeries.isNumeric;
		_computed = {
			//TODO look at entries for all series not just the first
			data: bySeries.series[0].values.map(function(d){return +d.entry}),
			hasColumn: false,
			count: 0
		};

		var currScale = chartProps.scale.numericSettings || clone(config.defaultProps.chartProps.scale.numericSettings);
		var domain = help.computeScaleDomain(currScale, _computed.data, {
			nice: true,
			minZero: false
		});

		assign(currScale, domain);

		currScale.ticks = currScale.ticks || help.suggestTickNum(currScale.domain);

		var ticks = currScale.ticks;
		currScale.tickValues = help.exactTicks(currScale.domain, ticks);

		each(currScale.tickValues, function(v) {
			var tickPrecision = help.precision(Math.round(v*factor)/factor);
			if (tickPrecision > currScale.precision) {
				currScale.precision = tickPrecision;
			}
		});

		scale.numericSettings = currScale;

		if (chartProps.mobile) {
			if (chartProps.mobile.scale) {
				var currMobile = chartProps.mobile.scale.numericSettings;
				if (currMobile) {
					var domain = help.computeScaleDomain(currMobile, _computed.data, {
						nice: true,
						minZero: false
					});
					assign(currMobile, domain);

					var ticks = currMobile.ticks;
					currMobile.tickValues = help.exactTicks(currMobile.domain, ticks);
					each(currMobile.tickValues, function(v) {
						var tickPrecision = help.precision(Math.round(v*factor)/factor);
						if (tickPrecision > currMobile.precision) {
							currMobile.precision = tickPrecision;
						}
					});
				}
				chartProps.mobile.scale.numericSettings = currMobile;
			}
		} else {
			chartProps.mobile = {};
		}


	}

	var newChartProps = assign(chartProps, {
		chartSettings: chartSettings,
		scale: scale,
		input: bySeries.input,
		data: bySeries.series,
		_numSecondaryAxis: _scaleComputed.secondaryScale.count
	});

	if (callback) {
		callback(newChartProps);
	} else {
		return newChartProps;
	}

}


module.exports = parseXY;


<|start_filename|>src/js/actions/ChartServerActions.js<|end_filename|>
var Dispatcher = require("../dispatcher/dispatcher");

/**
 * ### ChartServerActions
 * Send data from some external API, usually localStorage in our case
*/
var ChartServerActions = {

	/**
	* Update the whole chart model
	* @param {Object} model
	* @param {object} model.chartProps
	* @param {object} model.metadata
	*/
	receiveModel: function(chartModel) {
		Dispatcher.handleServerAction({
			eventName: "receive-model",
			model: chartModel
		});
	}

};

module.exports = ChartServerActions;


<|start_filename|>test/helper.js<|end_filename|>
var test = require("tape");
var help = require("../src/js/util/helper");
var util = require("./util/util");

function isInteger(x) {
	return x % 1 === 0;
}

test("helper: exact ticks", function(t) {
	t.plan(2);

	var et = help.exactTicks;
	var domain_no_zero = [util.randInt(1, 10), util.randInt(11, 1000)];
	var domain_pass_zero = [util.randInt(-1000, -500), util.randInt(500, 1000)];
	var numTicks = 5;

	t.equal(et(domain_no_zero, numTicks).length, numTicks, "length of tick array matches number given");

	var passZero = et(domain_pass_zero, numTicks);
	t.ok((passZero.indexOf(0) > -1), "domain that passes zero contains tick of zero");
	t.end();
});

test("helper: round to precision", function(t) {
	t.plan(3);

	var rtp = help.roundToPrecision;
	var rand_float = util.randInt(1, 200) + Math.random();
	var precision_float = util.randInt(1, 4);
	var decimalPoints;

	t.equal(rtp(0, util.randInt(1, 4)), "0", "if passed 0 return \"0\" regardless of precision");

	t.ok(isInteger(rtp(rand_float, 0)), "precision 0 returns an integer");

	// using this method instead of ours as we have to test ours
	digits_after_dec = rtp(rand_float, precision_float).split(".")[1].length;
	t.equal(digits_after_dec, precision_float, "number of digits following decimal point matches argument");
});

test("helper: compute scale domain", function(t) {
	t.plan(6);

	var csd = help.computeScaleDomain;
	var scaleObj = {};
	var expect;
	var processScale;

	var no_opts = [1, 25, 50, 75, 99];
	expect = { domain: [1, 99], custom: false };
	t.deepEqual(csd(scaleObj, no_opts), expect, "returns [ data[0], data[data.length - 1] ] if no options given");

	var nice = [1, 25, 50, 75, 99];
	expect = { domain: [0, 100], custom: false };
	t.deepEqual(csd(scaleObj, nice, { nice: true }), expect, "returns niced values if { nice: true } set");

	var force_zero = [10, 20, 30, 40];
	t.equal(csd(scaleObj, force_zero, { minZero: true }).domain[0], 0, "sets domain[0] to 0 if { minZero: true } set");

	var negative = [-50, -25, 0, 25, 50];
	t.equal(csd(scaleObj, negative, { minZero: true }).domain[0], -50, "sets domain[0] to negative number even if { minZero: true } set");

	scaleObj = { domain: [5, 95], custom: true };
	processScale = csd(scaleObj, nice, { nice: true });
	t.ok(processScale.custom, "respects custom values even if { nice: true } set");

	scaleObj = { domain: [0, 100], custom: false };
	var new_data = [0, 100, 200, 300, 400, 500];
	expect = { domain: [0, 500], custom: false };
	processScale = csd(scaleObj, new_data, { nice: true });
	t.deepEqual(processScale, expect, "updates domain if new data extent entered and scale is not custom");

	t.end();
});

test("helper: detect precision", function(t) {
	t.plan(1);
	var p = help.precision;
	var rand_float = util.randInt(1, 10) + Math.random();
	var rand_precision = util.randInt(1, 6);
	var to_fixed = parseFloat(rand_float.toFixed(rand_precision));
	digits_after_dec = to_fixed.toString().split(".")[1].length;
	t.equal(p(to_fixed), digits_after_dec, "precision returns same value passed to toFixed");
	t.end();
});

test("helper: precision of NaN is 0", function(t) {
	t.plan(1);
	var p = help.precision;
	t.equal(p(0 / 0), 0);
	t.end();
});

test("helper: convert timezone strings", function(t) {
	t.plan(8);
	var p = help.TZOffsetToMinutes
	t.equal(p("Z"),0,"Z timezone returns offset of 0 mintues")
	t.equal(p("-00:00"), 0, "+0000 tz offset returns 0 minutes")
	t.equal(p("+00:00"), 0, "+0000 tz offset returns 0 minutes")
	t.equal(p("-08:00"), -480, "-0800 tz offset returns -480 minutes")
	t.equal(p("+05:00"), 300, "+0500 tz offset returns 600 minutes")
	t.equal(p("+04:30"), 270, "+0430 tz offset returns 270 minutes")
	t.equal(p("-04:30"), -270, "-0430 tz offset returns -270 minutes")
	t.equal(p("+10:00"), 600, "+1030 tz offset returns 600 minutes")
	t.end();
})


<|start_filename|>src/js/stores/ErrorStore.js<|end_filename|>
var assign = require("lodash/assign");
var clone = require("lodash/clone");
var some = require("lodash/some");
var filter = require("lodash/filter");
var EventEmitter = require("events").EventEmitter;

/* Flux dispatcher */
var Dispatcher = require("../dispatcher/dispatcher");

var errorNames = require("../util/error-names");
var validateDataInput = require("../util/validate-data-input");
var ChartPropertiesStore = require("./ChartPropertiesStore");

/* Singleton that houses errors */
var _errors = { valid: true, messages: [] };
var CHANGE_EVENT = "change";

/**
 * ### ErrorStore.js
 * Store for errors/warnings to users about the bad/dumb things they are
 * probably doing
*/
var ErrorStore = assign({}, EventEmitter.prototype, {

	emitChange: function() {
		this.emit(CHANGE_EVENT);
	},

	addChangeListener: function(callback) {
		this.on(CHANGE_EVENT, callback);
	},

	removeChangeListener: function(callback) {
		this.removeListener(CHANGE_EVENT, callback);
	},

	/**
	 * get
	 * @param k
	 * @return {any} - Return value at key `k`
	 * @instance
	 * @memberof ErrorStore
	 */
	get: function(k) {
		return _errors[k];
	},

	/**
	 * getAll
	 * @return {object} - Return all errors
	 * @instance
	 * @memberof ErrorStore
	 */
	getAll: function() {
		return clone(_errors);
	},

	/**
	 * clear
	 * Set errors to empty
	 * @instance
	 * @memberof ErrorStore
	 */
	clear: function() {
		_errors = {};
	}

});

/* Respond to actions coming from the dispatcher */
function registeredCallback(payload) {
	var action = payload.action;
	var chartProps;
	var error_messages;
	var input_errors;

	switch(action.eventName) {
		/* *
		* Data input updated or reparse called
		* */
		case "update-data-input":
		case "update-and-reparse":

			Dispatcher.waitFor([ChartPropertiesStore.dispatchToken]);
			chartProps = ChartPropertiesStore.getAll();

			error_messages = [];
			input_errors = validateDataInput(chartProps);
			error_messages = error_messages.concat(input_errors);

			_errors.messages = error_messages.map(function(err_name) {
				return errorNames[err_name];
			});

			var isInvalid = some(_errors.messages, { type: "error" } );
			_errors.valid = !isInvalid;

			ErrorStore.emitChange();
			break;

		default:
			// do nothing
	}

	return true;

}

/* Respond to actions coming from the dispatcher */
ErrorStore.dispatchToken = Dispatcher.register(registeredCallback);
module.exports = ErrorStore;


<|start_filename|>test/validate-chart-model.js<|end_filename|>
var test = require("tape");

var validateChartModel = require("../src/js/util/validate-chart-model");
var sample_model = require("./util/sample_model.json");

var incorrect_json = {
	chart: {
		data: [0,1,2,3,4,5]
	},
	meta: {
		title: "Incorrect"
	}
};

test("validate chart model", function(t) {
	t.plan(3);

	t.throws(function() {
		validateChartModel("NOT JSON");
	}, TypeError, "Throw if chart model is not valid JSON");

	t.throws(function() {
		validateChartModel(incorrect_json);
	}, TypeError, "Throw if chart model is JSON but not a valid Chartbuilder model");

	var validated = validateChartModel(JSON.stringify(sample_model));
	t.deepEqual(validated, sample_model, "Validator returns model if valid.");
});


<|start_filename|>src/js/util/grid-utils.js<|end_filename|>
var React = require("react");
var ordinal = require("d3").scale.ordinal;
var map = require("lodash/map");
var assign = require("lodash/assign");
var defaults = require("lodash/defaults");
var range = require("lodash/range");

var gridScaleDefaults = {
	xInnerPadding: 0.1,
	xOuterPadding: 0,
	yInnerPadding: 0.1,
	yOuterPadding: 0
};
/**
 * make_mults
 *
 * @param components { array? of base components that data will be passed to }
 * @param data { data for each chart }
 * @param gridOpts {grid option }
 * @param howToRender { func that tells how to evaluate the components?? }
 * @returns {[chart components]}
 */
function make_mults(Outer, outerProps, data, gridScales, renderDataFunc) {
	var colDomain = gridScales.cols.domain();
	var numCols = colDomain[colDomain.length - 1] + 1;
	// only render number of grid blocks that are selected
	var numCharts = gridScales.cols.domain().length * gridScales.rows.domain().length;

	var grid_dimensions = {
		width: gridScales.cols.rangeBand(),
		height: gridScales.rows.rangeBand()
	};

	return map(data.slice(0, numCharts), function(d, i) {
		var pos = {
			col: i % numCols,
			row: (i === 0) ? 0 : Math.floor( i / numCols )
		};

		var gridProps = assign({}, outerProps, {
			key: i,
			translate: [ gridScales.cols(pos.col), gridScales.rows(pos.row) ],
			dimensions: grid_dimensions
		});

		return Outer(gridProps, renderDataFunc(d, i));
	});
}

function create_grid_scales(gridOpts, outerRange, _opts) {
	var colDomain = range(gridOpts.cols);
	var rowDomain = range(gridOpts.rows);
	var opts = defaults(_opts, gridScaleDefaults);

	return {
		cols: ordinal().domain(colDomain)
					.rangeBands(outerRange.x, opts.xInnerPadding, opts.xOuterPadding),
		rows: ordinal().domain(rowDomain)
					.rangeBands(outerRange.y, opts.yInnerPadding, opts.yOuterPadding)
	}
}

module.exports = {
	makeMults: make_mults,
	createGridScales: create_grid_scales
};


<|start_filename|>src/js/util/ChartbuilderLocalStorageAPI.js<|end_filename|>
// Get data from and save to local storage

var ChartServerActions = require("../actions/ChartServerActions");
var defaultInput = require("../config/default-input");
var chartConfig = require("../charts/chart-type-configs");
//var testInput = require("../../../test/util/test-input");

module.exports = {
	defaultChart: function() {
		var default_model = chartConfig.xy.defaultProps;
		default_model.chartProps.input = {
			//raw: testInput.init_data_time
			raw: defaultInput
		};

		ChartServerActions.receiveModel(default_model);
	},

	getChart: function() {
		ChartServerActions.receiveModel(JSON.parse(localStorage.getItem("model")));
	},

	saveChart: function(model) {
		localStorage.setItem("model", JSON.stringify(model));
	}
};



<|start_filename|>src/js/config/default-input.js<|end_filename|>
/**
 * Tabular data that is loaded in when Chartbuilder loads initially
 * @name default_input
 * @memberof config
 * @static
 */
var default_input = [
	"date	Juice	Travel",
	"2000-01-01	106.3	49.843099",
	"2000-02-01	106.0	49.931931",
	"2000-03-01	105.4	61.478163",
	"2000-04-01	101.8	58.981617",
	"2000-05-01	95.9	61.223861",
	"2000-06-01	94.1	65.601574",
	"2000-07-01	102.0	67.89832",
	"2000-08-01	98.5	67.028338",
	"2000-09-01	105.1	56.441629",
	"2000-10-01	99.0	58.83421",
	"2000-11-01	97.7	56.283261",
	"2000-12-01	88.2	55.38028",
	"2001-01-01	89.1	53.129922",
	"2001-02-01	87.8	49.992995",
	"2001-03-01	89.1	62.323049",
	"2001-04-01	97.5	59.801567",
	"2001-05-01	97.7	60.246478",
	"2001-06-01	97.7	64.987598",
	"2001-07-01	104.8	68.57341",
	"2001-08-01	103.8	69.003617",
	"2001-09-01	99.4	39.106905",
	"2001-10-01	105.3	44.271037",
	"2001-11-01	103.8	45.245063",
	"2001-12-01	104.2	48.167518",
	"2002-01-01	98.9	46.587854",
	"2002-02-01	91.6	45.157584",
	"2002-03-01	91.4	57.423396",
	"2002-04-01	90.2	53.013179",
	"2002-05-01	103.7	55.66357",
	"2002-06-01	99.1	60.22415",
	"2002-07-01	96.1	63.8285",
	"2002-08-01	102.0	63.657944",
	"2002-09-01	102.9	49.270915",
	"2002-10-01	101.9	54.019748",
	"2002-11-01	98.9	50.106815",
	"2002-12-01	97.3	56.656595",
	"2003-01-01	90.8	51.197174",
	"2003-02-01	89.6	47.040806",
	"2003-03-01	82.8	56.76658",
	"2003-04-01	88.6	51.857453",
	"2003-05-01	93.9	54.335598",
	"2003-06-01	98.9	60.2729",
	"2003-07-01	94.5	65.962215",
	"2003-08-01	93.6	64.989766",
	"2003-09-01	95.0	52.12148",
	"2003-10-01	94.9	56.724551",
	"2003-11-01	92.8	54.128776",
	"2003-12-01	93.0	58.739845",
	"2004-01-01	90.6	53.979787",
	"2004-02-01	92.0	53.179693",
	"2004-03-01	91.8	64.035864",
	"2004-04-01	95.7	62.340117",
	"2004-05-01	97.6	62.530704",
	"2004-06-01	105.9	68.866397",
	"2004-07-01	102.6	73.335889",
	"2004-08-01	102.6	70.961522",
	"2004-09-01	102.0	57.881041",
	"2004-10-01	110.1	63.021143",
	"2004-11-01	116.2	59.453942",
	"2004-12-01	108.3	62.68031",
	"2005-01-01	102.3	59.629608",
	"2005-02-01	98.5	55.795165",
	"2005-03-01	91.8	70.595861",
	"2005-04-01	92.9	65.145551",
	"2005-05-01	95.4	68.268899",
	"2005-06-01	96.3	72.952959",
	"2005-07-01	95.6	77.432998",
	"2005-08-01	94.5	73.778918",
	"2005-09-01	94.1	62.046798",
	"2005-10-01	94.9	63.640895",
	"2005-11-01	95.1	61.518984",
	"2005-12-01	95.1	64.28934",
	"2006-01-01	93.5	61.035027",
	"2006-02-01	93.2	56.729212",
	"2006-03-01	94.8	70.799794",
	"2006-04-01	99.6	68.120559",
	"2006-05-01	97.3	69.352606",
	"2006-06-01	100.9	74.099238",
	"2006-07-01	109.0	77.798621",
	"2006-08-01	111.2	74.227051",
	"2006-09-01	115.9	62.345744",
	"2006-10-01	110.6	66.082164",
	"2006-11-01	105.9	63.548732",
	"2006-12-01	110.9	65.980782",
	"2007-01-01	110.3	63.016014",
	"2007-02-01	114.0	57.793832",
	"2007-03-01	115.0	72.700241",
	"2007-04-01	128.3	69.836157",
	"2007-05-01	123.4	71.933108",
	"2007-06-01	127.3	76.926452",
	"2007-07-01	127.0	80.98834",
	"2007-08-01	130.5	79.439827",
	"2007-09-01	132.5	65.913314",
	"2007-10-01	131.8	69.445349",
	"2007-11-01	130.7	66.250428",
	"2007-12-01	126.1	67.782533",
	"2008-01-01	130.2	64.667105",
	"2008-02-01	130.9	61.504425",
	"2008-03-01	130.9	74.575531",
	"2008-04-01	134.4	68.906882",
	"2008-05-01	135.5	72.72575",
	"2008-06-01	140.1	76.162104",
	"2008-07-01	137.6	79.707545",
	"2008-08-01	143.3	77.300568",
	"2008-09-01	135.5	61.198749",
	"2008-10-01	124.5	65.018204",
	"2008-11-01	120.7	58.512623",
	"2008-12-01	124.9	63.503416",
	"2009-01-01	120.9	58.373786",
	"2009-02-01	119.3	53.50658",
	"2009-03-01	118.9	66.027342",
	"2009-04-01	116.7	65.166301",
	"2009-05-01	114.5	65.868255",
	"2009-06-01	115.2	71.350228",
	"2009-07-01	114.8	77.136799",
	"2009-08-01	119.4	74.614772",
	"2009-09-01	123.7	61.435354",
	"2009-10-01	124.3	64.27474",
	"2009-11-01	120.6	59.011015",
	"2009-12-01	122.3	63.231672",
	"2010-01-01	128.2	59.651062",
	"2010-02-01	127.3	53.240066",
	"2010-03-01	125.4	68.30709",
	"2010-04-01	133.9	64.953251",
	"2010-05-01	131.9	68.850905",
	"2010-06-01	132.2	74.474551",
	"2010-07-01	128.9	79.304441",
	"2010-08-01	126.5	76.741308",
	"2010-09-01	124.0	65.341293",
	"2010-10-01	128.5	69.071626",
	"2010-11-01	132.1	63.334293",
	"2010-12-01	131.5	65.797943",
	"2011-01-01	133.9	61.630362",
	"2011-02-01	131.4	55.391206",
	"2011-03-01	131.8	70.158268",
	"2011-04-01	131.8	67.683559",
	"2011-05-01	129.9	71.711448",
	"2011-06-01	127.9	76.05791",
	"2011-07-01	127.3	81.423231",
	"2011-08-01	131.7	77.247896",
	"2011-09-01	122.9	66.345318",
	"2011-10-01	123.9	68.177919",
	"2011-11-01	127.3	63.492941",
	"2011-12-01	135.7	66.595644",
	"2012-01-01	130.5	61.940192"
].join("\n");

var numeric = [
	"name 	name1	name2",
"1	0.357216663	0.486292816",
"2	0.123239533	0.001011741",
"4	0.68064382	0.271155864",
"8	0.694160979	0.681558882",
"16	0.209978847	0.306421625",
"32	0.066388111	0.946670922"
].join("\n");

module.exports = default_input;


<|start_filename|>src/js/charts/chart-type-configs.js<|end_filename|>
// Object containing chart types and their respective configs
var chart_type_configs = {
	xy: require("./cb-xy/xy-config"),
	chartgrid: require("./cb-chart-grid/chart-grid-config"),
};

module.exports = chart_type_configs;


<|start_filename|>test/util/util.js<|end_filename|>
// Helpers for test data

var _ = require("lodash");

function randInt(min, max) {
	return Math.floor(Math.random() * (max - min + 1)) + min;
}

function randArrElement(arr) {
	return arr[Math.floor(Math.random() * arr.length)];
}

function flattenLengthsToTotal(arr) {
	return _.reduce(arr, function(total, d) {
		return total + d.length;
	}, 0);
}

module.exports = {
	randInt: randInt,
	randArrElement: randArrElement,
	flattenLengthsToTotal: flattenLengthsToTotal
};


<|start_filename|>test/index.js<|end_filename|>
require("./chart-metadata-store");
require("./chart-properties-store");
require("./helper");
require("./parse-input");
require("./date-parsers");
require("./date-frequencies");
require("./timezone-adjustments");
require("./auto-date-interval");
require("./validate-input");
require("./catch-chart-mistakes");


<|start_filename|>gulp/publish.js<|end_filename|>
var gulp = require('gulp'),
	rename = require('gulp-rename'),
	awspublish = require('gulp-awspublish');

const debugMode = false;

gulp.task('publish',['build'], function(done) {

	var aws;
	try {
		aws = require('./aws-config.json').AWS;
	}
	catch (ex) {
		console.log('>>> no config.json found', ex);
		done();
	}

	var publisher = awspublish.create({
		region: aws.Region,
		params: {Bucket: aws.Bucket},
		accessKeyId: aws.Credentials.AccessKeyId,
		secretAccessKey: aws.Credentials.SecretAccessKey
	});

	return gulp.src(['build/**/*.{html,css,js}'])
		.pipe(rename(function (path) {
			if(aws.FolderPath) {
				path.dirname = '/'+ aws.FolderPath +'/'+ path.dirname;
			}
		}))
		.pipe(awspublish.gzip())
		.pipe(publisher.publish({}, {simulate: debugMode, createOnly: false}))
		.pipe(publisher.cache())
		.pipe(awspublish.reporter());
});


<|start_filename|>src/js/config/chart-sizes.js<|end_filename|>
/**
 * Default dimensions for non-responsive chart sizes.
 * @name chart_sizes
 * @memberof config
 * @static
 */
var chart_sizes = {
	medium: {
		width: 640,
		height: 390
	},
	spotLong: {
		width: 320,
		height: 390
	},
	spotSmall: {
		width: 320,
		height: 250
	}
};

module.exports = chart_sizes;


<|start_filename|>test/catch-chart-mistakes.js<|end_filename|>
var test = require("tape");
var _ = require("lodash");

var catchChartMistakes = require("../src/js/util/catch-chart-mistakes");

test("catch common chart mistakes", function(t) {
	t.plan(8);

	var evenTicks = {
		domain: [-2.0, 0],
		tickValues: [-2.0, -1.5, -1.0, -1.5, 0]
	};

	t.ok(catchChartMistakes.axisTicksEven(evenTicks), "negative scale with even ticks passes");

	evenTicks = {
		domain: [1.0, 2.0],
		tickValues: [1.0, 1.2, 1.4, 1.6, 1.8, 2.0]
	};

	t.ok(catchChartMistakes.axisTicksEven(evenTicks), "small scale with even ticks passes");

	evenTicks = {
		domain: [0, 400],
		tickValues: [0, 100, 200, 300, 400]
	};

	t.ok(catchChartMistakes.axisTicksEven(evenTicks), "medium scale with even ticks passes");

	evenTicks = {
		domain: [50000, 200000],
		tickValues: [50000, 75000, 100000, 125000, 150000, 175000, 200000]
	};

	t.ok(catchChartMistakes.axisTicksEven(evenTicks), "large scale with even ticks passes");

	var unevenTicks = {
		domain: [0, -35],
		tickValues: [0, -7, -14, -21, -28, -35]
	};

	t.notOk(catchChartMistakes.axisTicksEven(unevenTicks), "negative scale with uneven ticks fails");

	unevenTicks = {
		domain: [0, 0.3],
		tickValues: [0, 0.075, 0.150, 0.225, 0.3]
	};

	t.notOk(catchChartMistakes.axisTicksEven(unevenTicks), "small scale with uneven ticks fails");

	unevenTicks = {
		domain: [0, 60],
		tickValues: [0, 12, 24, 36, 48, 60]
	};

	t.notOk(catchChartMistakes.axisTicksEven(unevenTicks), "medium scale with uneven ticks fails");

	unevenTicks = {
		domain: [5000, 200000],
		tickValues: [50000, 87500, 125000, 162500, 200000]
	};

	t.notOk(catchChartMistakes.axisTicksEven(unevenTicks), "large scale with uneven ticks fails");

	t.end();
});


<|start_filename|>test/chart-metadata-store.js<|end_filename|>
var test = require("tape");
var chartGenerators = require("./chart-generators");
var ChartServerActions = require("../src/js/actions/ChartServerActions");
var ChartViewActions = require("../src/js/actions/ChartViewActions");
var ChartMetadataStore = require("../src/js/stores/ChartMetadataStore");
var registeredCallback;

function setupStore(callback) {
	ChartMetadataStore.clear();
	callback(ChartMetadataStore);
}

test("Chart metadata store", function(t) {
	t.plan(3);

	setupStore(function(store) {
		t.deepEqual(store.getAll(), {}, "initial store returns an empty object");
	});

	setupStore(function(store) {
		ChartViewActions.updateMetadata("test_key", "test_value");
		t.equal(store.get("test_key"), "test_value", "set a single prop and get the same value from the store");
	});

	setupStore(function(store) {
		var randChart = chartGenerators.randChart();
		ChartServerActions.receiveModel(randChart);
		var _all = store.getAll();
		t.deepEqual(_all, randChart.metadata, "load random model into the metadata store");
		t.end();
	});

});


<|start_filename|>src/js/components/mixins/NumericScaleMixin.js<|end_filename|>
var clone = require("lodash/clone");
var map = require("lodash/map");
var reduce = require("lodash/reduce");
var help = require("../../util/helper.js");

/**
 * ### Mixin for renderers that require construction of a numeric scale
 * @instance
 * @memberof renderers
 */
var NumericScaleMixin = {

	/**
	 * generateNumericScale
	 * Create a numeric scale given data, scale, and dimensions settings
	 * @param props
	 * @return {object} - `{ numericTicks: ...,  domain: ..., numericFormatter: ...}`
	 */
	generateNumericScale: function(props) {
		// TODO: auto-generate some kind of "good" numeric scale? For now just
		// return the settings
		var cur = props.chartProps.scale.numericSettings;

		return {
			custom: cur.custom,
			domain: cur.domain,
			precision: cur.precision,
			prefix: cur.prefix,
			suffix: cur.suffix,
			tickValues: cur.tickValues,
			ticks: cur.ticks
		}
	}
};

module.exports = NumericScaleMixin;


<|start_filename|>test/chart-generators/generate-xy.js<|end_filename|>
// Generate a util XY chart

var faker = require("faker");
var _ = require("lodash");
var util = require("../util/util");

var defaultInput = require("../util/test-input");
var parseXY = require("../../src/js/charts/cb-xy/parse-xy");
var xyConfig = require("../../src/js/charts/cb-xy/xy-config");
var inputKeys = Object.keys(defaultInput);

var options = {
	type: ["line", "column", "scatterPlot"],
	axis: ["left", "right"]
};

var defaultOpts = {
	size: "auto"
};

function generateXY(_opts) {
	_opts = _opts || {};
	var opts = _.defaults(_opts, defaultOpts);

	var randChartProps = {
		scale: {
			primaryScale: {
				prefix: faker.finance.currencySymbol(),
				suffix: " " + faker.hacker.noun() + "s",
				ticks: util.randInt(3, 8),
				precision: 0
			},
			secondaryScale: {
				precision: 0
			}
		},
		input: {
			raw: defaultInput.init_data_ordinal,
			status: "VALID",
			valid: true
		},
		chartSettings: [],
		data: [],
		extraPadding: {
			top: 0,
			right: 0,
			bottom: 0,
			left: 0
		},
		_annotations: {
			labels: {
				hasDragged: false,
				values: []
			}
		},
		_dateScale: {
			dateFrequency: "auto",
			dateFormat: "M"
		},
		mobile: {}
	};

	var randMetadata = {
		id: null,
		chartType: "xy",
		title: faker.company.catchPhrase(),
		source: "Data: " + faker.company.companyName(),
		credit: faker.address.country() + " | " + faker.internet.domainName(),
		size: opts.size
	};

	var chartProps = parseXY(xyConfig, randChartProps);
	var anyDragged = false;

	_.each(chartProps.data, function(d, i) {
		var randSetting = {
			type: util.randArrElement(options.type),
			colorIndex: util.randInt(0, 9),
			altAxis: false
		};

		chartProps.chartSettings[i] = randSetting;
	});

	chartProps._annotations.labels.hasDragged = anyDragged;
	chartProps = parseXY(xyConfig, chartProps);

	return {
		chartProps: chartProps,
		metadata: randMetadata
	};
}

module.exports = generateXY;


<|start_filename|>src/js/charts/renderers.js<|end_filename|>
/**
 * @name renderers
 */

// Chart renderer
module.exports = {
	xy: require("../components/chart-xy/XYRenderer.jsx"),
	chartgrid: require("../components/chart-grid/ChartGridRenderer.jsx")
};


<|start_filename|>src/js/util/series-utils.js<|end_filename|>
var createFactory  = require("react").createFactory;
var BarGroup       = require("../components/series/BarGroup.jsx");
var LineMarkSeries = require("../components/series/LineMarkSeries.jsx");
var LineSeries     = require("../components/series/LineSeries.jsx");
var MarkSeries     = require("../components/series/MarkSeries.jsx");

var series_components = {
	"line": createFactory(LineSeries),
	"lineMark": createFactory(LineMarkSeries),
	"column": createFactory(BarGroup),
	"scatterPlot": createFactory(MarkSeries)
};

function create_series(type, props) {
	return series_components[type](props);
}

module.exports = {
	createSeries: create_series
};


<|start_filename|>gulp/config.js<|end_filename|>
var path = require('path');

var dirs = {
	build: './build',
	tmp: './.tmp',
	src: './src',
	dist: './dist'
};

var paths = {
	src: {
		img: dirs.src + '/img',
		htdocs: dirs.src + '/htdocs',
		js: dirs.src + '/js',
		styl: dirs.src + '/styl',
		fonts: dirs.src + '/fonts',
		assets: dirs.src + '/assets'
	},
	tmp: {
		css: dirs.tmp + '/css',
		img: dirs.tmp + '/img',
		js: dirs.tmp + '/js',
		fonts: dirs.tmp + '/fonts',
		assets: dirs.tmp + '/assets'
	},
	build: {
		css: dirs.build + '/css',
		img: dirs.build + '/img',
		js: dirs.build + '/js',
		fonts: dirs.build + '/fonts',
		assets: dirs.build + '/assets'
	},
	dist: {
		css: dirs.dist + '/css'
	}
};

var server = {
	port: '8080',
	root: path.resolve('./'),
};

module.exports = {
	dirs: dirs,
	paths: paths,
	server: server
};


<|start_filename|>test/chart-properties-store.js<|end_filename|>
var test = require("tape");
var chartGenerators = require("./chart-generators");
var ChartServerActions = require("../src/js/actions/ChartServerActions");
var ChartViewActions = require("../src/js/actions/ChartViewActions");
var ChartPropertiesStore = require("../src/js/stores/ChartPropertiesStore");

function setupStore(callback) {
	ChartPropertiesStore.clear();
	callback(ChartPropertiesStore);
}

test("Chart properties store", function(t) {
	t.plan(3);

	setupStore(function(store) {
		t.deepEqual(store.getAll(), {}, "initial store returns an empty object");
	});

	setupStore(function(store) {
		ChartViewActions.updateChartProp("test_key", "test_value");
		t.equal(store.get("test_key"), "test_value", "set a single prop and get the same value from the store");
	});

	setupStore(function(store) {
		var randChart = chartGenerators.randChart();

		ChartServerActions.receiveModel(randChart);
		var _all = store.getAll();
		t.ok(_all.hasOwnProperty("data"), "load model into the properties store and parse input");
	});

	t.end();

});


<|start_filename|>test/timezone-adjustments.js<|end_filename|>
var test = require("tape");

var testInput = require("./util/test-input");
require("sugar-date");

var parseDelimitedInput = require("../src/js/util/parse-delimited-input.js");
var parseUtils = require("../src/js/util/parse-utils.js");
var cast = parseDelimitedInput._cast_data;

var separators = {
	decimal: ".",
	thousands: ","
};

var stripChars = [
	"$",
	"£",
	"€",
	"%"
];

var _stripCharsStr = stripChars.concat([separators.thousands]).reduce(function(a, b) {
	return a.concat(parseUtils.escapeRegExp(b));
}, []).join("|");
var stripCharsRegex = new RegExp(_stripCharsStr, "g");

test("timezone: tz adjustments", function(t) {
	t.plan(3);

	var input = [
		"date-col	val",
		"2016-02-02T12:00:00-0800	1",
		"2016-02-02T12:00:01-0800	1",
		"2016-02-02T12:00:02-0800	1",
		"2016-02-02T12:00:03-0800	1"
	].join("\n");

	var opts = {
		type: "date",
		inputTZ: "Z",
		delimiter: parseUtils.detectDelimiter(input)
	};

	var entries = cast(input, ["date-col", "val"], stripCharsRegex, opts).entries;
	var expected = [
		Date.create("2016-02-02T12:00:00-0800"),
		Date.create("2016-02-02T12:00:01-0800"),
		Date.create("2016-02-02T12:00:02-0800"),
		Date.create("2016-02-02T12:00:03-0800")
	];

	t.deepEqual(entries, expected, "dates that already have a timezone are left alone");

	input = [
		"date-col	val",
		"Feb. 2 2016 12:00:00	1",
		"Feb. 2 2016 12:00:01	1",
		"Feb. 2 2016 12:00:02	1",
		"Feb. 2 2016 12:00:03	1"
	].join("\n");

	opts = {
		type: "date",
		inputTZ: null,
		delimiter: parseUtils.detectDelimiter(input)
	};

	expected = [
		Date.create("2016-02-02T12:00:00"),
		Date.create("2016-02-02T12:00:01"),
		Date.create("2016-02-02T12:00:02"),
		Date.create("2016-02-02T12:00:03")
	];

	entries = cast(input, ["date-col", "val"], stripCharsRegex, opts).entries;
	t.deepEqual(entries, expected,"dates that don't have a timezone are adjusted to local when there is no inputTZ");


	opts = {
		type: "date",
		inputTZ: "-0500",
		delimiter: parseUtils.detectDelimiter(input)
	};

	expected = [
		Date.create("2016-02-02T12:00:00-0500"),
		Date.create("2016-02-02T12:00:01-0500"),
		Date.create("2016-02-02T12:00:02-0500"),
		Date.create("2016-02-02T12:00:03-0500")
	];

	entries = cast(input, ["date-col", "val"], stripCharsRegex, opts).entries;

	t.deepEqual(entries, expected,"dates that don't have a timezone are adjusted to the inputTZ");

	t.end();
});



<|start_filename|>src/js/util/parse-config-values.js<|end_filename|>
function convertConfig(item, info, emSize, chartWidth) {
	var out;
	var o;

	if(!info) {
		info = {};
		info.em = emSize;
		info.width = chartWidth;
	}

	if(item === null) {
		out = item;
	}
	else if(Array.isArray(item)) {
		out = [];
		for (var i = item.length - 1; i >= 0; i--) {
			out[i] = convertConfig(item[i], info);
		}
	}
	else if (typeof item === "object") {
		out = {};
		for(var prop in item) {
			out[prop] = convertConfig(item[prop], info);
		}
	}
	else {
		out = item;

		if(typeof item === "string") {
			var floated = parseFloat(item);
			if(floated + "em" == item || floated + "rem" == item) {
				out = info.em * floated;
			}
			else if(floated + "%" == item) {
				out = info.width * floated/100;
			}
			else if(floated + "px" == item) {
				out = floated;
			}
		}
	}

	return out;

}



module.exports = convertConfig;


<|start_filename|>src/js/util/validate-chart-model.js<|end_filename|>
/**
 * Test whether a string is a valid Chartbuilder model
 * @param {string} modelStr - unparsed string of chart model JSON
 * @returns {object or null} parsed - parsed object
 * @static
 * @memberof helper
*/
function validate_chart_model(modelStr) {
	var parsed;

	try {
		parsed = JSON.parse(modelStr);
	} catch (e) {
		throw new TypeError("Chart model is not valid JSON");
	}

	var isValidChartModel = (parsed.hasOwnProperty("chartProps") && parsed.hasOwnProperty("metadata"));
	if (isValidChartModel) {
		return parsed;
	} else {
		throw new TypeError("Not a valid Chartbuilder model");
	}

}

module.exports = validate_chart_model;


<|start_filename|>test/auto-date-interval.js<|end_filename|>
var test = require("tape");
require("sugar-date"); // sugar is used for date parsing
var processDates = require("../src/js/util/process-dates");
var auto = processDates.autoDateFormatAndFrequency;
var width = 640;
var minDate;
var maxDate;
var ff;

test("auto date interval:", function(t) {
	t.plan(4);

	minDate = new Date(2010, 0, 1);
	maxDate = new Date(2010, 0, 2);
	ff = auto(minDate, maxDate, "auto", width);
	t.equal(ff.format, "h", "return hours for proper interval");

	minDate = new Date(2010, 0, 1);
	maxDate = new Date(2010, 2, 1);
	ff = auto(minDate, maxDate, "auto", width);
	t.equal(ff.format, "M1d", "return months/days for proper interval");

	minDate = new Date(2010, 0, 1);
	maxDate = new Date(2010, 12, 1);
	ff = auto(minDate, maxDate, "auto", width);
	t.equal(ff.format, "M", "return months for proper interval");

	minDate = new Date(2010, 0, 1);
	maxDate = new Date(2015, 12, 1);
	ff = auto(minDate, maxDate, "auto", width);
	t.equal(ff.format, "yy", "return years for proper interval");

	t.end();
});


<|start_filename|>src/js/util/catch-chart-mistakes.js<|end_filename|>
var map = require("lodash/map");
var some = require("lodash/some");
var reduce = require("lodash/reduce");
var sizeof = require("sizeof");

var MAX_BYTES = 240000; // warning for too much data at 240k for chartProps
var INTERVAL_BASE_VALS = [1, 2, 2.5, 5, 10, 25]; // used to determine "good" tick intervals
var MAX_TICKS = 8;

function too_much_data(chartProps) {
	return (sizeof.sizeof(chartProps) > MAX_BYTES);
}

function axis_ticks_even(scale) {
	var range = (scale.domain[1] - scale.domain[0]);
	var minimum = range / MAX_TICKS;
	var digits = Math.floor(range).toString().length;
	var multiplier = Math.pow(10, (digits - 2));

	var acceptable_intervals = reduce(INTERVAL_BASE_VALS, function(prev, curr) {
		var mult = curr * multiplier;

		if (mult >= minimum) {
			prev = prev.concat([mult]);
		}

		return prev;
	}, []);

	var are_ticks_even = some(acceptable_intervals, function(inter) {
		return all_modulo(scale.tickValues, inter);
	});

	return are_ticks_even;
}

function no_prefix_suffix(scale) {
	return (scale.prefix === "" && scale.suffix === "");
}

// Determine if all tickValues are modulo some interval value
function all_modulo(tickValues, interval) {

	// we can't modulo-check decimals so we need to multiply by 10^Ndecimals
	var maxDecimals = reduce(tickValues, function(prevMax, tick) {
		if ((tick % 1) !== 0) {
			return Math.max(prevMax, tick.toString().split(".")[1].length);
		} else {
			return prevMax;
		}
	}, 0);

	var decimalOffset = Math.pow(10, maxDecimals);
	interval = interval * decimalOffset;

	return reduce(tickValues, function(prev, curr) {
		return prev && ((curr * decimalOffset) % interval === 0);
	}, true);

}

module.exports = {
	axisTicksEven: axis_ticks_even,
	tooMuchData: too_much_data,
	noPrefixSuffix: no_prefix_suffix
};


<|start_filename|>src/js/stores/SessionStore.js<|end_filename|>
require("sugar-date");
var assign = require("lodash/assign");
var EventEmitter = require("events").EventEmitter;

/* Flux dispatcher */
var Dispatcher = require("../dispatcher/dispatcher");
var now = new Date();

var _session = {
	separators: detectNumberSeparators(),
	emSize: 10,
	width: 640,
	timerOn: (localStorage.hasOwnProperty("model") === true),
	nowOffset: getTZOffset(now),
	now: now
};

var CHANGE_EVENT = "change";

/**
 * ### SessionStore
 * Flux store for the current session, ie session data that persits regardless
 * interaction. Includes locale-aware settings and are unrelated to the actual
 * rendering of the chart.
*/
var SessionStore = assign({}, EventEmitter.prototype, {

	emitChange: function() {
		this.emit(CHANGE_EVENT);
	},

	addChangeListener: function(callback) {
		this.on(CHANGE_EVENT, callback);
	},

	removeChangeListener: function(callback) {
		this.removeListener(CHANGE_EVENT, callback);
	},

	/**
	 * get
	 * @return {any} - Return value at key `k`
	 * @instance
	 * @memberof SessionStore
	 */
	get: function(k) {
		return _session[k];
	},

	/**
	 * getAll
	 * @return {object} - Return all session data
	 * @instance
	 * @memberof SessionStore
	 */
	getAll: function() {
		return _session;
	}

});

function registeredCallback(payload) {
	var action = payload.action;

	switch(action.eventName) {
		case "start-timer":
			_session.timerOn = true;
			SessionStore.emitChange();
			break;

		case "stop-timer":
			_session.timerOn = false;
			SessionStore.emitChange();
			break;

		case "update-session":
			_session[action.key] = action.value;
			SessionStore.emitChange();
			break;

		default:
			// do nothing
	}

	return true;
}

// Get thousands and decimal separators based on locale
function detectNumberSeparators() {
	var n = 1000.50;
	var l = n.toLocaleString();
	var s = n.toString();
	var o = {
		decimal: l.substring(5,6),
		thousands: l.substring(1,2)
	};

	if (l.substring(5,6) == s.substring(5,6)) {
		o.decimal = ".";
	}
	if (l.substring(1,2) == s.substring(1,2)) {
		o.thousands = ",";
	}

	return o;
}

function getTZOffset(date) {
	var _offset = date.getUTCOffset().split("");
	_offset.splice(3, 0, ":")
	return _offset.join("");
}

SessionStore.dispatchToken = Dispatcher.register(registeredCallback);

module.exports = SessionStore;


<|start_filename|>test/chart-generators/generate-chart-grid.js<|end_filename|>
// Generate a util chart grid

var faker = require("faker");
var _ = require("lodash");
var util = require("../util/util");

var defaultInput = require("../util/test-input");
var parseChartGrid = require("../../src/js/charts/cb-chart-grid/parse-chart-grid");
var chartGridConfig = require("../../src/js/charts/cb-chart-grid/chart-grid-config");
var inputKeys = Object.keys(defaultInput);

var defaultOpts = {
	type: "bar",
	size: "auto"
};

function generateChartGrid(_opts) {
	_opts = _opts || {};
	var opts = _.defaults(_opts, defaultOpts);

	var randChartProps = {
		scale: {
			primaryScale: {
				prefix: faker.finance.currencySymbol(),
				suffix: " " + faker.hacker.noun() + "s",
			}
		},
		input: {
			raw: defaultInput.init_data_ordinal,
			status: "VALID",
			valid: true
		},
		chartSettings: [],
		data: [],
		extraPadding: {
			top: 0,
			right: 0,
			bottom: 0,
			left: 0
		},
		_grid: {
			type: opts.type
		},
		numColors: 11,
		mobile: {}
	};

	var randMetadata = {
		id: null,
		chartType: "chartgrid",
		title: faker.company.catchPhrase(),
		source: "Data: " + faker.company.companyName(),
		credit: faker.address.country() + " | " + faker.internet.domainName(),
		size: opts.size
	};

	var chartProps = parseChartGrid(chartGridConfig, randChartProps);
	var anyDragged = false;

	return {
		chartProps: chartProps,
		metadata: randMetadata
	};
}

module.exports = generateChartGrid;


<|start_filename|>test/date-frequencies.js<|end_filename|>
var test = require("tape");
var _ = require("lodash");
require("sugar-date");

var processDates = require("../src/js/util/process-dates");
var dateFrequencies = processDates.dateFrequencies;
var hours_ms = 3600000;
var days_ms = 86400000;
var months_ms = 2628000000;
var years_ms = 31536000000;

var minDate;
var maxDate;
var target_gap;
var freqs;
var interval;
var f;

test("date frequencies", function(t) {
	// subtract 2 because auto is tested separately
	t.plan(Object.keys(dateFrequencies).length - 2);

	f = dateFrequencies["1h"];
	target_gap = hours_ms;
	minDate = new Date(1999, 11, 31, 22, 0, 0);
	maxDate = new Date(2000, 0, 1, 24, 0, 0);
	freqs = f(minDate, maxDate);
	interval = check_ms_interval(freqs, target_gap);

	t.ok(interval, "All 1hr intervals match target date gap");

	f = dateFrequencies["2h"];
	target_gap = hours_ms * 2;
	minDate = new Date(1999, 11, 31, 22, 0, 0);
	maxDate = new Date(2000, 0, 1, 24, 0, 0);
	freqs = f(minDate, maxDate);
	interval = check_ms_interval(freqs, target_gap);

	f = dateFrequencies["3h"];
	target_gap = hours_ms * 3;
	minDate = new Date(1999, 11, 31, 22, 0, 0);
	maxDate = new Date(2000, 0, 1, 24, 0, 0);
	freqs = f(minDate, maxDate);
	interval = check_ms_interval(freqs, target_gap);

	t.ok(interval, "All 3hr intervals match target date gap");

	f = dateFrequencies["4h"];
	target_gap = hours_ms * 4;
	minDate = new Date(1999, 11, 31, 22, 0, 0);
	maxDate = new Date(2000, 0, 1, 24, 0, 0);
	freqs = f(minDate, maxDate);
	interval = check_ms_interval(freqs, target_gap);

	t.ok(interval, "All 4hr intervals match target date gap");

	f = dateFrequencies["6h"];
	target_gap = hours_ms * 6;
	minDate = new Date(1999, 11, 31, 22, 0, 0);
	maxDate = new Date(2000, 0, 1, 24, 0, 0);
	freqs = f(minDate, maxDate);
	interval = check_ms_interval(freqs, target_gap);

	t.ok(interval, "All 6hr intervals match target date gap");

	f = dateFrequencies["1d"];
	target_gap = days_ms;
	minDate = new Date(1999, 0, 1, 0);
	maxDate = new Date(2000, 0, 2, 1);
	freqs = f(minDate, maxDate);
	interval = check_ms_interval(freqs, target_gap);

	t.ok(interval, "All 1d intervals match target date gap");

	f = dateFrequencies["1w"];
	target_gap = days_ms * 7;
	minDate = new Date(1999, 0, 1);
	maxDate = new Date(2000, 0, 1);
	freqs = f(minDate, maxDate);
	interval = check_ms_interval(freqs, target_gap);

	t.ok(interval, "All 1w intervals match target date gap");

	f = dateFrequencies["1m"];
	target_gap = 1;
	minDate = new Date(1999, 0, 1);
	maxDate = new Date(2005, 0, 1);
	freqs = f(minDate, maxDate);
	interval = check_month_interval(freqs, target_gap);

	t.ok(interval, "All 1m intervals match target date gap");

	f = dateFrequencies["3m"];
	target_gap = 3;
	minDate = new Date(1999, 0, 1);
	maxDate = new Date(2005, 0, 1);
	freqs = f(minDate, maxDate);
	interval = check_month_interval(freqs, target_gap);

	t.ok(interval, "All 3m intervals match target date gap");

	f = dateFrequencies["6m"];
	target_gap = 6;
	minDate = new Date(1999, 0, 1);
	maxDate = new Date(2005, 0, 1);
	freqs = f(minDate, maxDate);
	interval = check_month_interval(freqs, target_gap);

	t.ok(interval, "All 6m intervals match target date gap");

	f = dateFrequencies["1y"];
	target_gap = 1;
	minDate = new Date(1999, 0, 1);
	maxDate = new Date(2015, 0, 1);
	freqs = f(minDate, maxDate);
	interval = check_year_interval(freqs, target_gap);

	t.ok(interval, "All 1y intervals match target date gap");

	f = dateFrequencies["2y"];
	target_gap = 2;
	minDate = new Date(1999, 0, 1);
	maxDate = new Date(2015, 0, 1);
	freqs = f(minDate, maxDate);
	interval = check_year_interval(freqs, target_gap);

	t.ok(interval, "All 2y intervals match target date gap");

	f = dateFrequencies["5y"];
	target_gap = 5;
	minDate = new Date(1999, 0, 1);
	maxDate = new Date(2015, 0, 1);
	freqs = f(minDate, maxDate);
	interval = check_year_interval(freqs, target_gap);

	t.ok(interval, "All 5y intervals match target date gap");

	f = dateFrequencies["10y"];
	target_gap = 10;
	minDate = new Date(1999, 0, 1);
	maxDate = new Date(2015, 0, 1);
	freqs = f(minDate, maxDate);
	interval = check_year_interval(freqs, target_gap);

	t.ok(interval, "All 10y intervals match target date gap");

	f = dateFrequencies["20y"];
	target_gap = 20;
	minDate = new Date(1999, 0, 1);
	maxDate = new Date(2015, 0, 1);
	freqs = f(minDate, maxDate);
	interval = check_year_interval(freqs, target_gap);

	t.ok(interval, "All 20y intervals match target date gap");

	f = dateFrequencies["100y"];
	target_gap = 100;
	minDate = new Date(1599, 0, 1);
	maxDate = new Date(2015, 0, 1);
	freqs = f(minDate, maxDate);
	interval = check_year_interval(freqs, target_gap);

	t.ok(interval, "All 100y intervals match target date gap");
});

function check_ms_interval(arr, gap) {
	return _.reduce(arr, function(a, b, i) {
		if (i === 0) {
			return gap;
		}
		return (arr[i] - arr[i - 1]) === gap;
	}, gap);
}

function check_month_interval(arr, gap) {
	return _.reduce(arr, function(a, b, i) {
		if (i === 0) {
			return gap;
		}
		return (arr[i].getMonth() - arr[i - 1].getMonth()) === gap;
	}, gap);
}

function check_year_interval(arr, gap) {
	return _.reduce(arr, function(a, b, i) {
		if (i === 0) {
			return gap;
		}
		return (arr[i].getYear() - arr[i - 1].getYear()) === gap;
	}, gap);
}



<|start_filename|>src/js/components/mixins/ChartEditorMixin.js<|end_filename|>
var React = require("react");
var update = require("react-addons-update");

// Flux actions
var ChartViewActions = require("../../actions/ChartViewActions");

/**
 * ### Functions common to chart editors
 * @instance
 * @memberof editors
 */
var ChartEditorMixin = {

	/**
	 * _handlePropUpdate
	 * Initiate a flux action that updates a prop, that doesn't require reparsing
	 * @param k - `chartProp` key
	 * @param v - `chartProp` value
	 */
	_handlePropUpdate: function(k, v) {
		ChartViewActions.updateChartProp(k, v);
	},

	/**
	 * _handlePropAndReparse
	 * Initiate a flux action that updates a prop and then triggers a reparse
	 * @param k - `chartProp` key
	 * @param v - `chartProp` value
	 */
	_handlePropAndReparse: function(k, v) {
		ChartViewActions.updateAndReparse(k, v);
	},

	/**
	 * _handleStateUpdate
	 * Update a key in the editor component's state
	 * @param k - `this.state` key
	 * @param v - `this.state` value
	 */
	_handleStateUpdate: function(k, v) {
		var newValue = {};
		newValue[k] = v;
		this.setState(update(this.state, { $merge: newValue }));
	},

};

module.exports = ChartEditorMixin;


<|start_filename|>src/js/util/scale-utils.js<|end_filename|>
var d3 = require("d3");
var d3scale = require("d3-scale");
var clone = require("lodash/clone");
var assign = require("lodash/assign");
var reduce = require("lodash/reduce");
var map = require("lodash/map");
var processDates = require("./process-dates");
var help = require("./helper");

var scale_types = {
	"linear": _linearScale,
	"ordinal": _ordinalScale,
	"time": _timeScale
};

var defaultPadding = {
	inner: 0.2,
	outer: 0.1
};

/**
 * generateScale
 *
 * @param type
 * @param scaleOptions
 * @param data
 * @param range
 * @returns {scale: d3 scale, tickValues: array, tickFormat: format func}
 */
function generate_scale(type, scaleOptions, data, range, additionalOpts) {
	if (!scaleOptions) return {};
	return scale_types[type](scaleOptions, data, range, additionalOpts);
}

function _timeScale(scaleOptions, data, range) {
	// Return the ticks used for a time scale based on the time span and settings
	var formatAndFreq = {};
	var dateFormat = scaleOptions.dateFormat;
	var dateFrequency = scaleOptions.dateFrequency;

	// grab the first series to get the first/last dates
	var firstSeries = data[0].values;
	// make sure dates are in chronological order
	var dateRange = [
		firstSeries[0].entry,
		firstSeries[firstSeries.length - 1].entry
	].sort(d3.ascending);

	var minDate = dateRange[0];
	var maxDate = dateRange[1];

	if (dateFrequency === "auto" || dateFormat === "auto") {
		var autoSettings = processDates.autoDateFormatAndFrequency(minDate, maxDate, dateFormat, range[1]);
	}

	if (dateFrequency !== "auto") {
		var freqSettings = processDates.dateFrequencies[dateFrequency];
		formatAndFreq.frequency = freqSettings(minDate, maxDate);
	} else {
		formatAndFreq.frequency = autoSettings.frequency;
	}
	if (dateFormat !== "auto") {
		formatAndFreq.format = dateFormat;
	} else {
		formatAndFreq.format = autoSettings.format;
	}

	// use min/max dates if our auto calc comes up with empty array
	if (formatAndFreq.frequency.length === 0) {
		formatAndFreq.frequency = [minDate, maxDate];
	}

	return {
		scale: d3.time.scale().range(range).domain([minDate, maxDate]),
		tickValues: formatAndFreq.frequency,
		tickFormat: processDates.dateParsers[formatAndFreq.format]
	};
}

function _linearScale(scaleOptions, data, range) {
	return {
		scale: d3.scale.linear().domain(scaleOptions.domain).range(range),
		tickValues: scaleOptions.tickValues,
		tickFormat: function(d) {
			return help.roundToPrecision(d, scaleOptions.precision);
		}
	};
}

function _ordinalScale(scaleOptions, data, range, _padding) {
	var padding = assign({}, defaultPadding, _padding);

	var entries = map(data[0].values, function(value) {
		return value.entry;
	});

	var scale = d3scale.scaleBand()
		.domain(entries)
		.range(range)
		.paddingInner(padding.inner)
		.paddingOuter(padding.outer);

	return {
		scale: scale,
		tickValues: entries,
	};
}

//TODO: make this keyed funcs that accept a `type` param
//(like "ordinal", "time", "linear") so that we dont have to check every time
function _ordinalAdjust(scale, value) {
	var isOrdinal = scale.hasOwnProperty("bandwidth");
	if (isOrdinal) {
		return scale(value) + scale.bandwidth() / 2;
	} else {
		return scale(value);
	}
}

/**
 * get_tick_widths
 *
 * @param scaleOptions object
 * @param font string
 * @returns {width: [number], max: number}
 */
function get_tick_widths(scaleOptions, font) {
	if (!scaleOptions) return { width: [], max: 0};

	var numTicks = scaleOptions.tickValues.length - 1;
	var formattedTicks = reduce(scaleOptions.tickValues, function(prev, tick, i) {
		if (i === numTicks) {
			return prev.concat([
				scaleOptions.prefix,
				help.roundToPrecision(tick, scaleOptions.precision),
				scaleOptions.suffix
			].join(""));
		} else {
			return prev.concat(help.roundToPrecision(tick, scaleOptions.precision));
		}
	}, []);

	var widths = map(formattedTicks, function(text) {
		return help.computeTextWidth(text, font);
	});

	return {
		widths: widths,
		max: d3.max(widths.slice(0, -1)) // ignore the top tick
	};
}

module.exports = {
	generateScale: generate_scale,
	getTickWidths: get_tick_widths,
	ordinalAdjust: _ordinalAdjust
};


<|start_filename|>src/js/util/parse-utils.js<|end_filename|>
var tabRegex = /[^\t]/gi;
var datePattern = /^(date|time|year)$/i;

function escapeRegExp(str) {
	return str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}

// Detect delimiter of input
// algorithm: if numtabs >= numrows then TSV, else CSV
function detectDelimiter(input) {
	var numRows = input.split(/\r\n|\r|\n/).length;
	var numTabs = input.replace(tabRegex, "").length;

	if (numTabs >= numRows - 1) {
		return "\t";
	} else {
		return ",";
	}
}

function matchDatePattern(str) {
	return datePattern.test(str);
}

module.exports = {
	escapeRegExp: escapeRegExp,
	detectDelimiter: detectDelimiter,
	matchDatePattern: matchDatePattern
};


<|start_filename|>src/js/actions/ChartViewActions.js<|end_filename|>
/* Flux dispatcher */
var Dispatcher = require("../dispatcher/dispatcher");

/**
 * ### ChartViewActions
 * Send data from React views to Flux dispatcher, and on to the stores
*/
var ChartViewActions = {

	/**
	* Update all chart props
	* @param {Object} - `chartProps`
	*/
	updateAllChartProps: function(newChartProps) {
		Dispatcher.handleViewAction({
			eventName: "update-all-chart-props",
			chartProps: newChartProps
		});
	},

	/**
	* Update a single chart prop
	* @param {string} key - The key used to identify this property
	* @param {object} newProp - The property's value
	*/
	updateChartProp: function(key, newProp) {
		Dispatcher.handleViewAction({
			eventName: "update-chart-prop",
			key: key,
			newProp: newProp
		});
	},

	/**
	* Update a single chart prop and reparse the input
	* @param {string} key - The key used to identify this property
	* @param {object} newProp - The property's value
	*/
	updateAndReparse: function(key, newProp) {
		Dispatcher.handleViewAction({
			eventName: "update-and-reparse",
			newProp: newProp,
			key: key
		});
	},

	/**
	* Update a data input and reparse it
	* @param {string} key - The key used to identify this property
	* @param {object} newInput - The property's value
	*/
	updateInput: function(key, newInput) {
		Dispatcher.handleViewAction({
			eventName: "update-data-input",
			newProp: newInput,
			key: key
		});
	},

	/**
	* Update metadata
	* @param {object} newMetadata - The property's value
	*/
	updateMetadata: function(k, v) {
		Dispatcher.handleViewAction({
			eventName: "update-metadata",
			key: k,
			value: v
		});
	},

	updateSession: function(k, v) {
		Dispatcher.handleViewAction({
			eventName: "update-session",
			key: k,
			value: v
		})
	},

	startTimer: function() {
		Dispatcher.handleViewAction({
			eventName: "start-timer"
		});
	},

	stopTimer: function() {
		Dispatcher.handleViewAction({
			eventName: "stop-timer"
		});
	},
};

module.exports = ChartViewActions;


<|start_filename|>test/jsx/index-jsx.js<|end_filename|>
require("./chartbuilder.jsx");
require("./xy-renderer.jsx");
require("./chart-grid-bar.jsx");
require("./chart-grid-xy.jsx");


<|start_filename|>test/chart-generators/index.js<|end_filename|>
var util = require("../util/util");
var generateXY = require("./generate-xy");
var generateChartGrid = require("./generate-chart-grid");

var chartGenerators = {
	xy: generateXY,
	chartgrid: generateChartGrid
};

function randChart() {
	var generator = util.randArrElement(Object.keys(chartGenerators));
	return chartGenerators[generator]();
}

module.exports = {
	xy: generateXY,
	chartgrid: generateChartGrid,
	randChart: randChart
};
