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

import joist from './joist.js';

/**
 * Given an array of all possible screens that a sim can have, select and order them according to the relevant query
 * parameters. This also will create a homeScreen if needed, and specify the initialScreen for startup.
 *
 * @author Chris Klusendorf (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 * @author Sam Reid (PhET Interactive Simulations)
 *
 * @param {Object[]} allSimScreens - all of the screens declared by the sim
 * @param {boolean} homeScreenQueryParameter - from phet.chipper.queryParameters.homeScreen
 * @param {boolean} homeScreenQueryParameterProvided
 * @param {number} initialScreenIndex - from phet.chipper.queryParameters.initialScreen
 * @param {boolean} initialScreenQueryParameterProvided
 * @param {number[]} screensQueryParameter - from phet.chipper.queryParameters.screens
 * @param {boolean} screensQueryParameterProvided
 * @param {function( selectedSimScreens ):HomeScreen} createHomeScreen
 * @returns {{homeScreen:Object, initialScreen:Object, selectedSimScreens:Object[], screens:Object[]}}
 * @throws Error if incompatible data is provided
 */
const selectScreens = ( allSimScreens,
                        homeScreenQueryParameter,
                        homeScreenQueryParameterProvided,
                        initialScreenIndex,
                        initialScreenQueryParameterProvided,
                        screensQueryParameter,
                        screensQueryParameterProvided,
                        createHomeScreen ) => {

  if ( allSimScreens.length === 1 ) {

    // Problems related to query parameters throw errors instead of assertions (so they are not stripped out)
    if ( homeScreenQueryParameterProvided && homeScreenQueryParameter ) {
      throw new Error( 'cannot specify homeScreen=true for single-screen sims' );
    }
  }

  // the sim screens for this runtime, accounting for specifying a subset with `?screens`
  let selectedSimScreens;

  // The screens to be included, and their order, may be specified via a query parameter.
  // For documentation, see the schema for phet.chipper.queryParameters.screens in initialize-globals.js.
  // TODO: Use QueryStringMachine to validate instead, see https://github.com/phetsims/joist/issues/599
  if ( screensQueryParameterProvided ) {
    //REVIEW: Shouldn't we just see if the result is the default value of `null`, instead of checking if it was
    //REVIEW: provided? See https://github.com/phetsims/joist/issues/614
    selectedSimScreens = screensQueryParameter.map( userIndex => {
      const screenIndex = userIndex - 1; // screens query parameter is 1-based
      if ( screenIndex < 0 || screenIndex > allSimScreens.length - 1 ) {
        throw new Error( 'invalid screen index: ' + userIndex );
      }
      return allSimScreens[ screenIndex ];
    } );
  }
  else {
    selectedSimScreens = allSimScreens;
  }

  // Specifying ?homeScreen=false creates a simulation with no homescreen, and hence is incompatible with ?initialScreen=0,
  // which specifies to show the home screen. Note that the default value of initialScreen:0 is ignored when there is
  // no home screen.
  if ( initialScreenQueryParameterProvided && initialScreenIndex === 0 && homeScreenQueryParameter === false ) {
    // TODO: Use QueryStringMachine to validate instead, see https://github.com/phetsims/joist/issues/599
    throw new Error( 'cannot specify initialScreen=0 when home screen is disabled with homeScreen=false' );
  }

  // For a single screen simulation (whether the simulation only declares one screen, or whether the user has specified
  // a single screen via ?screens), there is no home screen, and hence ?initialScreen=0 which requests to show the homescreen
  // on startup should fail.
  if ( initialScreenQueryParameterProvided && initialScreenIndex === 0 && selectedSimScreens.length === 1 ) {
    throw new Error( 'cannot specify initialScreen=0 when one screen is specified with screens=n' );
  }

  const screens = selectedSimScreens.slice();

  let homeScreen = null;

  // If a sim has multiple screens and the query parameter homeScreen=false is not provided, add a HomeScreen
  if ( selectedSimScreens.length > 1 && homeScreenQueryParameter ) {
    homeScreen = createHomeScreen( selectedSimScreens );
    screens.unshift( homeScreen );
  }

  // The first screen for the sim, can be the HomeScreen if applicable
  let initialScreen;
  if ( homeScreen && initialScreenIndex === 0 ) {

    // If the home screen is supplied, then it is at index 0, so use the query parameter value directly (because the
    // query parameter is 1-based). If `?initialScreen` is 0 then there is no offset to apply.
    initialScreen = homeScreen;
  }
  else if ( initialScreenIndex === 0 ) {

    // There is no home screen and the initialScreen query parameter was not supplied, so we select the first sim screen.
    initialScreen = selectedSimScreens[ 0 ];
  }
  else {

    // If the home screen is not supplied, then the first sim screen is at index 0, so subtract 1 from the query parameter.
    initialScreen = allSimScreens[ initialScreenIndex - 1 ];
  }

  if ( screens.indexOf( initialScreen ) === -1 ) {
    throw new Error( 'screen not found: ' + initialScreenIndex );
  }

  return {
    homeScreen: homeScreen,
    initialScreen: initialScreen,
    selectedSimScreens: selectedSimScreens,
    screens: screens
  };
};

joist.register( 'selectScreens', selectScreens );
export default selectScreens;<commit_msg>doc, https://github.com/phetsims/joist/issues/603<commit_after>// Copyright 2020, University of Colorado Boulder

import joist from './joist.js';

/**
 * Given an array of all possible screens that a sim can have, select and order them according to the relevant query
 * parameters. This also will create a homeScreen if needed, and specify the initialScreen for startup.
 *
 * Parameters suffixed with "Provided" will be true if the that query parameter was actually in the URL, as opposed to
 * the value of the query parameter being the default.
 *
 * @author Chris Klusendorf (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 * @author Sam Reid (PhET Interactive Simulations)
 *
 * @param {Object[]} allSimScreens - all of the screens declared by the sim
 * @param {boolean} homeScreenQueryParameter - from phet.chipper.queryParameters.homeScreen
 * @param {boolean} homeScreenQueryParameterProvided
 * @param {number} initialScreenIndex - from phet.chipper.queryParameters.initialScreen
 * @param {boolean} initialScreenQueryParameterProvided
 * @param {number[]} screensQueryParameter - from phet.chipper.queryParameters.screens
 * @param {boolean} screensQueryParameterProvided
 * @param {function( selectedSimScreens ):HomeScreen} createHomeScreen
 * @returns {{homeScreen:Object, initialScreen:Object, selectedSimScreens:Object[], screens:Object[]}}
 * @throws Error if incompatible data is provided
 */
const selectScreens = ( allSimScreens,
                        homeScreenQueryParameter,
                        homeScreenQueryParameterProvided,
                        initialScreenIndex,
                        initialScreenQueryParameterProvided,
                        screensQueryParameter,
                        screensQueryParameterProvided,
                        createHomeScreen ) => {

  if ( allSimScreens.length === 1 ) {

    // Problems related to query parameters throw errors instead of assertions (so they are not stripped out)
    if ( homeScreenQueryParameterProvided && homeScreenQueryParameter ) {
      throw new Error( 'cannot specify homeScreen=true for single-screen sims' );
    }
  }

  // the sim screens for this runtime, accounting for specifying a subset with `?screens`
  let selectedSimScreens;

  // The screens to be included, and their order, may be specified via a query parameter.
  // For documentation, see the schema for phet.chipper.queryParameters.screens in initialize-globals.js.
  // TODO: Use QueryStringMachine to validate instead, see https://github.com/phetsims/joist/issues/599
  if ( screensQueryParameterProvided ) {
    //REVIEW: Shouldn't we just see if the result is the default value of `null`, instead of checking if it was
    //REVIEW: provided? See https://github.com/phetsims/joist/issues/614
    selectedSimScreens = screensQueryParameter.map( userIndex => {
      const screenIndex = userIndex - 1; // screens query parameter is 1-based
      if ( screenIndex < 0 || screenIndex > allSimScreens.length - 1 ) {
        throw new Error( 'invalid screen index: ' + userIndex );
      }
      return allSimScreens[ screenIndex ];
    } );
  }
  else {
    selectedSimScreens = allSimScreens;
  }

  // Specifying ?homeScreen=false creates a simulation with no homescreen, and hence is incompatible with ?initialScreen=0,
  // which specifies to show the home screen. Note that the default value of initialScreen:0 is ignored when there is
  // no home screen.
  if ( initialScreenQueryParameterProvided && initialScreenIndex === 0 && homeScreenQueryParameter === false ) {
    // TODO: Use QueryStringMachine to validate instead, see https://github.com/phetsims/joist/issues/599
    throw new Error( 'cannot specify initialScreen=0 when home screen is disabled with homeScreen=false' );
  }

  // For a single screen simulation (whether the simulation only declares one screen, or whether the user has specified
  // a single screen via ?screens), there is no home screen, and hence ?initialScreen=0 which requests to show the homescreen
  // on startup should fail.
  if ( initialScreenQueryParameterProvided && initialScreenIndex === 0 && selectedSimScreens.length === 1 ) {
    throw new Error( 'cannot specify initialScreen=0 when one screen is specified with screens=n' );
  }

  const screens = selectedSimScreens.slice();

  let homeScreen = null;

  // If a sim has multiple screens and the query parameter homeScreen=false is not provided, add a HomeScreen
  if ( selectedSimScreens.length > 1 && homeScreenQueryParameter ) {
    homeScreen = createHomeScreen( selectedSimScreens );
    screens.unshift( homeScreen );
  }

  // The first screen for the sim, can be the HomeScreen if applicable
  let initialScreen;
  if ( homeScreen && initialScreenIndex === 0 ) {

    // If the home screen is supplied, then it is at index 0, so use the query parameter value directly (because the
    // query parameter is 1-based). If `?initialScreen` is 0 then there is no offset to apply.
    initialScreen = homeScreen;
  }
  else if ( initialScreenIndex === 0 ) {

    // There is no home screen and the initialScreen query parameter was not supplied, so we select the first sim screen.
    initialScreen = selectedSimScreens[ 0 ];
  }
  else {

    // If the home screen is not supplied, then the first sim screen is at index 0, so subtract 1 from the query parameter.
    initialScreen = allSimScreens[ initialScreenIndex - 1 ];
  }

  if ( screens.indexOf( initialScreen ) === -1 ) {
    throw new Error( 'screen not found: ' + initialScreenIndex );
  }

  return {
    homeScreen: homeScreen,
    initialScreen: initialScreen,
    selectedSimScreens: selectedSimScreens,
    screens: screens
  };
};

joist.register( 'selectScreens', selectScreens );
export default selectScreens;<|endoftext|>


<commit_before>//
// RIGHT GRAPH
//

// INITIAL VARIABLES

var data_right = [87.5, 75, 75, 62.5, 62.5, 50],
	data_right_name = ['','','','','','Python','Mongo','MySQL','HTML & CSS','JavaScript','Hive'],
	data_right_colors = ['91e0f2','91e0f2','91e0f2','f9d595','f9d595','91e0f2'],
	data_skill_names = ["","Rookie","Geek","Ninja","Master"],
	data_legend = ['','','','','','','','','','','','Back-End','Front-end'],
	data_legend_colors = ['91e0f2','f9d595']
	data_legend_x = [.08, .04]
	margin = {top: 30, right: 10, bottom: 30, left: 10},
	width = parseInt(d3.select('#right-graph').style('width'), 10),
	width = width - margin.left - margin.right,
	height = 480, // placeholder
	barHeight = 60,
	spacing = 3;

// SCALES

var widthScale = d3.scale.linear()
					.domain([0,100])
					.range([0,width]);

var axisName = d3.scale.ordinal()
				.domain(data_skill_names)
				.rangePoints([0,width]);

var y = d3.scale.linear().range([375, 0]);

// AXES

var xAxis = d3.svg.axis()
			.scale(axisName)
		    .tickSize(0)
		    .tickSubdivide(true);

var x2Axis = d3.svg.axis()
	.scale(widthScale)
	.tickSize(-420)
	.tickValues([25,50,75,100]);

var yAxis = d3.svg.axis()
	.scale(y)
	.orient("left")
	.ticks(0)
	.tickSize(0);

// CANVAS & DRAWING AXES

var canvas = d3.select("#right-graph").append("svg")
	.attr("width", (width + margin.left + margin.right) + 'px')
	.attr("height", height)
	.append("g")
	.attr("transform", 'translate(' + [margin.left, margin.top] + ')');

canvas.append('g')
    .attr('class', 'grid')
    .attr("transform", "translate(0," + 375 + ")")
    .call(x2Axis.orient('bottom'))

canvas.append('g')
    .attr('class', 'x axis top')
    .call(xAxis.orient('top'))
    .selectAll("text")
    .attr("y", -10)
    .attr("x", -10)
    .style("text-anchor", "end");

// BARS AND BAR TEXT

var bars = canvas.append('g').selectAll(".bar")
	.data(data_right)
	.enter()
		.append("rect")
		.attr("width", function(d){ return widthScale(d);})
		.attr("height", 40)
		.attr("y", function(d, i){ return i*barHeight+20;})
		.attr("x", 0)
		.attr('fill', function(d, i){ return "#"+data_right_colors[i];})
		.attr("class", "bar");

canvas.append('g').selectAll('text')
	.data(data_right_name)
	.enter()
	.append('text')
	.text(function(d) { return d; })
	.attr("x", 15)
	.attr("y", function(d, i){ return (i-5)*barHeight+46;});

canvas.append('g')
	.attr('class', 'y axis left')
	.call(yAxis.orient('left'))
	.attr("x", 10);

// LEGEND

var key1 = canvas.append('g').selectAll(".legend")
	.data(data_legend_x)
	.enter()
		.append("rect")
		.attr("width", 30)
		.attr("height", 30)
		.attr("y", 400)
		.attr("x", function(d, i){
			if (i == 0) {
				return widthScale(width*d-16);
			}
			if (i == 1){
				return widthScale(width*d+42);
			}
		})
		.attr('fill', function(d, i){ return "#"+data_legend_colors[i];})
		.attr("class", "legend");

canvas.append('g').selectAll('.legendtext')
	.data(data_legend)
	.enter()
	.append('text')
	.text(function(d) { return d; })
	.attr("x", function(d, i) {
			if (i == 11 ) {
				return widthScale(width*data_legend_x[i-11]-16)+35;
			}
			if (i == 12 ) {
				return widthScale(width*data_legend_x[i-11]+42)+35;
			}
		})
	.attr("y", 420)
	.attr("class", "legendtext");

//
// LEFT GRAPH
//

// INITIAL VARIABLES

var data_left = [87.5, 87.5, 75, 62.5, 62.5, 50],
	data_left_name = ['','','','','','Linear Optimization','Excel','Machine Learning','R','Google OR-Tools','Tableau'],
	data_left_colors = ['f9b495','b1f2e3','f9b495','b1f2e3','b1f2e3','b1f2e3'],
	data_skill_names1 = ["Master","Ninja","Geek","Rookie",""],
	data_legend1 = ['','','','','','','','','','','','Math','Algorithm Dev.'],
	data_legend_colors1 = ['f9b495','b1f2e3']
	data_legend_x1 = [.08, .04]
	margin1 = {top: 30, right: 10, bottom: 30, left: 10},
	width1 = parseInt(d3.select('#left-graph').style('width'), 10),
	width1 = width1 - margin1.left - margin1.right,
	height1 = 480, // placeholder
	barHeight1 = 60,
	spacing1 = 3;

// SCALES

var widthScale1 = d3.scale.linear()
					.domain([0,100])
					.range([0,width1]);

var axisName1 = d3.scale.ordinal()
				.domain(data_skill_names1)
				.rangePoints([0,width1]);

var y1 = d3.scale.linear().range([375, 0]);

// AXES

var xAxis1 = d3.svg.axis()
			.scale(axisName1)
		    .tickSize(0)
		    .tickSubdivide(true);

var x2Axis1 = d3.svg.axis()
	.scale(widthScale1)
	.tickSize(-420)
	.tickValues([0,25,50,75]);

var yAxis1 = d3.svg.axis()
	.scale(widthScale1)
	.tickSize(-375)
	.tickValues([100]);

// canvas1 & DRAWING AXES

var canvas1 = d3.select("#left-graph").append("svg")
	.attr("width", (width1 + margin1.left + margin1.right) + 'px')
	.attr("height", height1)
	.append("g")
	.attr("transform", 'translate(' + [margin1.left, margin1.top] + ')');

canvas1.append('g')
    .attr('class', 'grid')
    .attr("transform", "translate(0," + 375 + ")")
    .call(x2Axis1.orient('bottom'))

canvas1.append('g')
    .attr('class', 'x axis top')
    .call(xAxis1.orient('top'))
    .selectAll("text")
    .attr("y", -10)
    .attr("x", 10)
    .style("text-anchor", "start");

// BARS AND BAR TEXT

var graph_bars1 = canvas1.append('g').selectAll(".graph_bars1")
	.data(data_left)
	.enter()
		.append("rect")
		.attr("width", function(d){ return widthScale1(d);})
		.attr("height", 40)
		.attr("y", function(d, i){ return i*barHeight1+20;})
		.attr("x", function(d){ return width1-widthScale1(d);})
		.attr('fill', function(d, i){ return "#"+data_left_colors[i];})
		.attr("class", "graph_bars1");

canvas1.append('g').selectAll('.barttext1')
	.data(data_left_name)
	.enter()
	.append('text')
	.text(function(d) { return d; })
	.attr("x", width1 - 15)
	.style("text-anchor", "end")
	.attr("y", function(d, i){ return (i-5)*barHeight1+46;})
	.attr("class", "barttext1");

canvas1.append('g')
    .attr('class', 'y axis bottom')
    .attr("transform", "translate(0," + 375 + ")")
    .call(yAxis1.orient('bottom'));

// LEGEND

var key1 = canvas1.append('g').selectAll(".legend1")
	.data(data_legend_x1)
	.enter()
		.append("rect")
		.attr("width", 30)
		.attr("height", 30)
		.attr("y", 400)
		.attr("x", function(d, i){
			if (i == 0) {
				return widthScale1(width1*d-16);
			}
			if (i == 1){
				return widthScale1(width1*d+32);
			}
		})
		.attr('fill', function(d, i){ return "#"+data_legend_colors1[i];})
		.attr("class", "legend1");

canvas1.append('g').selectAll('.legendtext1')
	.data(data_legend1)
	.enter()
	.append('text')
	.text(function(d) { return d; })
	.attr("x", function(d, i) {
		if (i == 11 ) {
			return widthScale1(width1*data_legend_x1[i-11]-16)+35;
		}
		if (i == 12 ) {
			return widthScale1(width1*data_legend_x1[i-11]+32)+35;
		}
	})
	.attr("y", 420)
	.attr("class", "legendtext1");

// RESIZE WIDTH, BARS, LEGEND, AXES, GRID

window.onresize = resize;

function resize() {

	//
	// RIGHT GRAPH
	//

    // update width
    width = parseInt(d3.select('#right-graph').style('width'), 10);
    width = width - margin.left - margin.right;

    // resize the canvas
    widthScale.range([0, width]);
    d3.select(canvas.node().parentNode)
        .style('width', (width + margin.left + margin.right) + 'px');

    // resize the bars
    canvas.selectAll(".bar")
        .attr("width", function(d){ return widthScale(d);});

    // resize the legend
    canvas.selectAll(".legend")
        .attr("x", function(d, i){
        	if (i == 0) {
				return widthScale(width*d-16);
			}
			if (i == 1){
				return widthScale(width*d+42);
			}
        });

    canvas.selectAll(".legendtext")
        .attr("x", function(d, i) {
			if (i == 11 ) {
				return widthScale(width*data_legend_x[i-11]-16)+35;
			}
			if (i == 12 ) {
				return widthScale(width*data_legend_x[i-11]+42)+35;
			}
		});

    // resize the top x axis
    axisName.rangePoints([0,width]);
    canvas.select('.x.axis.top')
    	.call(xAxis.orient('top'))
    	.selectAll("text")
        .attr("y", -10)
        .attr("x", -10)
        .style("text-anchor", "end");

    // resize the grid
    canvas.select('.grid')
    .attr("transform", "translate(0," + 375 + ")")
    .call(x2Axis.orient('bottom'))

    //
	// LEFT GRAPH
	//

    // update width
    width1 = parseInt(d3.select('#left-graph').style('width'), 10);
    width1 = width1 - margin1.left - margin1.right;

    // resize the canvas1
    widthScale1.range([0, width1]);
    d3.select(canvas1.node().parentNode)
        .style('width', (width1 + margin1.left + margin1.right) + 'px');

    // resize the graph_bars1
    canvas1.selectAll(".graph_bars1")
        .attr("width", function(d){ return widthScale1(d);})
        .attr("x", function(d){ return width1-widthScale1(d);})

    // resize the legend
    canvas1.selectAll(".legend1")
        .attr("x", function(d, i){
        	if (i == 0) {
				return widthScale1(width1*d-16);
			}
			if (i == 1){
				return widthScale1(width1*d+32);
			}
        });

    canvas1.selectAll(".legendtext1")
        .attr("x", function(d, i) {
		if (i == 11 ) {
			return widthScale1(width1*data_legend_x1[i-11]-16)+35;
		}
		if (i == 12 ) {
			return widthScale1(width1*data_legend_x1[i-11]+32)+35;
		}
	});

    // resize the top x axis
    axisName1.rangePoints([0,width1]);
    canvas1.select('.x.axis.top')
    	.call(xAxis1.orient('top'))
    	.selectAll("text")
        .attr("y", -10)
        .attr("x", 10)
        .style("text-anchor", "start");

    canvas1.select('.y.axis.bottom')
    	.attr("transform", "translate(0," + 375 + ")")
	    .call(yAxis1.orient('bottom'));

    // resize the grid
    canvas1.select('.grid')
	    .attr("transform", "translate(0," + 375 + ")")
	    .call(x2Axis1.orient('bottom'));

	canvas1.selectAll('.barttext1')
		.attr("x", width1 - 15);

}<commit_msg>updated skills graph with more details and improvements<commit_after>//
// RIGHT GRAPH
//

// INITIAL VARIABLES

var data_right = [87.5, 75, 75, 62.5, 62.5, 50],
	data_right_name = ['','','','','','Python','Mongo','MySQL','HTML & CSS','JavaScript','Hive'],
	data_right_colors = ['91e0f2','91e0f2','91e0f2','f9d595','f9d595','91e0f2'],
	data_skill_names = ["","Rookie","Geek","Ninja","Master"],
	data_legend = ['','','','','','','','','','','','Back-End','Front-end'],
	data_legend_colors = ['91e0f2','f9d595']
	data_legend_x = [.08, .04]
	margin = {top: 30, right: 10, bottom: 30, left: 10},
	width = parseInt(d3.select('#right-graph').style('width'), 10),
	width = width - margin.left - margin.right,
	height = 480, // placeholder
	barHeight = 60,
	spacing = 3;

// SCALES

var widthScale = d3.scale.linear()
					.domain([0,100])
					.range([0,width]);

var axisName = d3.scale.ordinal()
				.domain(data_skill_names)
				.rangePoints([0,width]);

var y = d3.scale.linear().range([375, 0]);

// AXES

var xAxis = d3.svg.axis()
			.scale(axisName)
		    .tickSize(0)
		    .tickSubdivide(true);

var x2Axis = d3.svg.axis()
	.scale(widthScale)
	.tickSize(-420)
	.tickValues([25,50,75,100]);

var yAxis = d3.svg.axis()
	.scale(y)
	.orient("left")
	.ticks(0)
	.tickSize(0);

// CANVAS & DRAWING AXES

var canvas = d3.select("#right-graph").append("svg")
	.attr("width", (width + margin.left + margin.right) + 'px')
	.attr("height", height)
	.append("g")
	.attr("transform", 'translate(' + [margin.left, margin.top] + ')');

canvas.append('g')
    .attr('class', 'grid')
    .attr("transform", "translate(0," + 375 + ")")
    .call(x2Axis.orient('bottom'))

canvas.append('g')
    .attr('class', 'x axis top')
    .call(xAxis.orient('top'))
    .selectAll("text")
    .attr("y", -10)
    .attr("x", -10)
    .style("text-anchor", "end");

// BARS AND BAR TEXT

var bars = canvas.append('g').selectAll(".bar")
	.data(data_right)
	.enter()
		.append("rect")
		.attr("width", function(d){ return widthScale(d);})
		.attr("height", 40)
		.attr("y", function(d, i){ return i*barHeight+20;})
		.attr("x", 0)
		.attr('fill', function(d, i){ return "#"+data_right_colors[i];})
		.attr("class", "bar");

canvas.append('g').selectAll('text')
	.data(data_right_name)
	.enter()
	.append('text')
	.text(function(d) { return d; })
	.attr("x", 15)
	.attr("y", function(d, i){ return (i-5)*barHeight+46;});

canvas.append('g')
	.attr('class', 'y axis left')
	.call(yAxis.orient('left'))
	.attr("x", 10);

// LEGEND

var key1 = canvas.append('g').selectAll(".legend")
	.data(data_legend_x)
	.enter()
		.append("rect")
		.attr("width", 30)
		.attr("height", 30)
		.attr("y", 400)
		.attr("x", function(d, i){
			if (i == 0) {
				return widthScale(width*d-16);
			}
			if (i == 1){
				return widthScale(width*d+42);
			}
		})
		.attr('fill', function(d, i){ return "#"+data_legend_colors[i];})
		.attr("class", "legend");

canvas.append('g').selectAll('.legendtext')
	.data(data_legend)
	.enter()
	.append('text')
	.text(function(d) { return d; })
	.attr("x", function(d, i) {
			if (i == 11 ) {
				return widthScale(width*data_legend_x[i-11]-16)+35;
			}
			if (i == 12 ) {
				return widthScale(width*data_legend_x[i-11]+42)+35;
			}
		})
	.attr("y", 420)
	.attr("class", "legendtext");

//
// LEFT GRAPH
//

// INITIAL VARIABLES

var data_left = [87.5, 87.5, 75, 62.5, 50, 50],
	data_left_name = ['','','','','','LP/IP Optimization','Excel','Stochastic Processes','Google OR-Tools','Machine Learning','Tableau'],
	data_left_colors = ['f9b495','b1f2e3','f9b495','b1f2e3','f9b495','b1f2e3'],
	data_skill_names1 = ["Master","Ninja","Geek","Rookie",""],
	data_legend1 = ['','','','','','','','','','','','Math','Algorithm Dev.'],
	data_legend_colors1 = ['f9b495','b1f2e3']
	data_legend_x1 = [.08, .04]
	margin1 = {top: 30, right: 10, bottom: 30, left: 10},
	width1 = parseInt(d3.select('#left-graph').style('width'), 10),
	width1 = width1 - margin1.left - margin1.right,
	height1 = 480, // placeholder
	barHeight1 = 60,
	spacing1 = 3;

// SCALES

var widthScale1 = d3.scale.linear()
					.domain([0,100])
					.range([0,width1]);

var axisName1 = d3.scale.ordinal()
				.domain(data_skill_names1)
				.rangePoints([0,width1]);

var y1 = d3.scale.linear().range([375, 0]);

// AXES

var xAxis1 = d3.svg.axis()
			.scale(axisName1)
		    .tickSize(0)
		    .tickSubdivide(true);

var x2Axis1 = d3.svg.axis()
	.scale(widthScale1)
	.tickSize(-420)
	.tickValues([0,25,50,75]);

var yAxis1 = d3.svg.axis()
	.scale(widthScale1)
	.tickSize(-375)
	.tickValues([100]);

// canvas1 & DRAWING AXES

var canvas1 = d3.select("#left-graph").append("svg")
	.attr("width", (width1 + margin1.left + margin1.right) + 'px')
	.attr("height", height1)
	.append("g")
	.attr("transform", 'translate(' + [margin1.left, margin1.top] + ')');

canvas1.append('g')
    .attr('class', 'grid')
    .attr("transform", "translate(0," + 375 + ")")
    .call(x2Axis1.orient('bottom'))

canvas1.append('g')
    .attr('class', 'x axis top')
    .call(xAxis1.orient('top'))
    .selectAll("text")
    .attr("y", -10)
    .attr("x", 10)
    .style("text-anchor", "start");

// BARS AND BAR TEXT

var graph_bars1 = canvas1.append('g').selectAll(".graph_bars1")
	.data(data_left)
	.enter()
		.append("rect")
		.attr("width", function(d){ return widthScale1(d);})
		.attr("height", 40)
		.attr("y", function(d, i){ return i*barHeight1+20;})
		.attr("x", function(d){ return width1-widthScale1(d);})
		.attr('fill', function(d, i){ return "#"+data_left_colors[i];})
		.attr("class", "graph_bars1");

canvas1.append('g').selectAll('.barttext1')
	.data(data_left_name)
	.enter()
	.append('text')
	.text(function(d) { return d; })
	.attr("x", width1 - 15)
	.style("text-anchor", "end")
	.attr("y", function(d, i){ return (i-5)*barHeight1+46;})
	.attr("class", "barttext1");

canvas1.append('g')
    .attr('class', 'y axis bottom')
    .attr("transform", "translate(0," + 375 + ")")
    .call(yAxis1.orient('bottom'));

// LEGEND

var key1 = canvas1.append('g').selectAll(".legend1")
	.data(data_legend_x1)
	.enter()
		.append("rect")
		.attr("width", 30)
		.attr("height", 30)
		.attr("y", 400)
		.attr("x", function(d, i){
			if (i == 0) {
				return widthScale1(width1*d-16);
			}
			if (i == 1){
				return widthScale1(width1*d+32);
			}
		})
		.attr('fill', function(d, i){ return "#"+data_legend_colors1[i];})
		.attr("class", "legend1");

canvas1.append('g').selectAll('.legendtext1')
	.data(data_legend1)
	.enter()
	.append('text')
	.text(function(d) { return d; })
	.attr("x", function(d, i) {
		if (i == 11 ) {
			return widthScale1(width1*data_legend_x1[i-11]-16)+35;
		}
		if (i == 12 ) {
			return widthScale1(width1*data_legend_x1[i-11]+32)+35;
		}
	})
	.attr("y", 420)
	.attr("class", "legendtext1");

// RESIZE WIDTH, BARS, LEGEND, AXES, GRID

window.onresize = resize;

function resize() {

	//
	// RIGHT GRAPH
	//

    // update width
    width = parseInt(d3.select('#right-graph').style('width'), 10);
    width = width - margin.left - margin.right;

    // resize the canvas
    widthScale.range([0, width]);
    d3.select(canvas.node().parentNode)
        .style('width', (width + margin.left + margin.right) + 'px');

    // resize the bars
    canvas.selectAll(".bar")
        .attr("width", function(d){ return widthScale(d);});

    // resize the legend
    canvas.selectAll(".legend")
        .attr("x", function(d, i){
        	if (i == 0) {
				return widthScale(width*d-16);
			}
			if (i == 1){
				return widthScale(width*d+42);
			}
        });

    canvas.selectAll(".legendtext")
        .attr("x", function(d, i) {
			if (i == 11 ) {
				return widthScale(width*data_legend_x[i-11]-16)+35;
			}
			if (i == 12 ) {
				return widthScale(width*data_legend_x[i-11]+42)+35;
			}
		});

    // resize the top x axis
    axisName.rangePoints([0,width]);
    canvas.select('.x.axis.top')
    	.call(xAxis.orient('top'))
    	.selectAll("text")
        .attr("y", -10)
        .attr("x", -10)
        .style("text-anchor", "end");

    // resize the grid
    canvas.select('.grid')
    .attr("transform", "translate(0," + 375 + ")")
    .call(x2Axis.orient('bottom'))

    //
	// LEFT GRAPH
	//

    // update width
    width1 = parseInt(d3.select('#left-graph').style('width'), 10);
    width1 = width1 - margin1.left - margin1.right;

    // resize the canvas1
    widthScale1.range([0, width1]);
    d3.select(canvas1.node().parentNode)
        .style('width', (width1 + margin1.left + margin1.right) + 'px');

    // resize the graph_bars1
    canvas1.selectAll(".graph_bars1")
        .attr("width", function(d){ return widthScale1(d);})
        .attr("x", function(d){ return width1-widthScale1(d);})

    // resize the legend
    canvas1.selectAll(".legend1")
        .attr("x", function(d, i){
        	if (i == 0) {
				return widthScale1(width1*d-16);
			}
			if (i == 1){
				return widthScale1(width1*d+32);
			}
        });

    canvas1.selectAll(".legendtext1")
        .attr("x", function(d, i) {
		if (i == 11 ) {
			return widthScale1(width1*data_legend_x1[i-11]-16)+35;
		}
		if (i == 12 ) {
			return widthScale1(width1*data_legend_x1[i-11]+32)+35;
		}
	});

    // resize the top x axis
    axisName1.rangePoints([0,width1]);
    canvas1.select('.x.axis.top')
    	.call(xAxis1.orient('top'))
    	.selectAll("text")
        .attr("y", -10)
        .attr("x", 10)
        .style("text-anchor", "start");

    canvas1.select('.y.axis.bottom')
    	.attr("transform", "translate(0," + 375 + ")")
	    .call(yAxis1.orient('bottom'));

    // resize the grid
    canvas1.select('.grid')
	    .attr("transform", "translate(0," + 375 + ")")
	    .call(x2Axis1.orient('bottom'));

	canvas1.selectAll('.barttext1')
		.attr("x", width1 - 15);

}<|endoftext|>


<commit_before>var zmq = require('zmq'),
    sock = zmq.socket('rep');
var r = require('rethinkdb'),
    redis = require('redis').createClient()

r.connect({host:'localhost', port:28015, db:'cointhink'},
  function(err, conn) {

  conn.addListener('error', function(e) {
    console.log("rethinkdb error: "+e)
  })

  conn.addListener('close', function() {
    console.log("rethinkdb closed")
  })

  redis.on("error", function (err) {
        console.log("Redis Error " + err);
  })

  sock.on('message', function(data){
    console.log("REQ: "+data)
    try {
      var message = JSON.parse(data)
      var payload = message.payload
      var fullname = message.username+"/"+message.scriptname

      console.log("rethink load called on "+fullname)
      r.table('scripts').get(fullname).run(conn, function(err, doc){
        if(err){
          console.log('rethink load error: '+err)
          respond({"status":"dberr"})
        } else {
          console.log(fullname+' rethink doc loaded')
          if(doc){
            if(doc.key == message.key){
              var storage = doc.storage
              if(payload.action == 'get'){
                var value = storage[payload.key]
                console.log(fullname+' get '+payload.key+' '+value)
                respond({"status":"ok", "payload":value})
              } else if(payload.action == 'set'){
                console.log(fullname+' set '+payload.key+' '+payload.value)
                storage[payload.key] = payload.value
                r.table('scripts').get(fullname).
                  update({storage:storage}).run(conn, function(status){
                  console.log(fullname+' set '+payload.key+' '+payload.value+' = '+status)
                  respond({"status":"ok", "payload":status})
                })
              } else if(payload.action == 'load'){
                console.log(fullname+' load storage, returning '+JSON.stringify(storage))
                respond({"status":"ok", "payload":storage})
              } else if(payload.action == 'store'){
                console.log(fullname+' store storage '+JSON.stringify(payload.storage))
                if(typeof(payload.storage) == 'object'){
                  r.table('scripts').get(fullname).
                    update({storage:payload.storage}).run(conn, function(status){
                    console.log(fullname+' store storage result '+status)
                    respond({"status":"ok", "payload":status})
                  })
                } else {
                  respond({"status":"err", "msg":"storage must be an object"})
                }
              } else if(payload.action == 'trade'){
                console.log(fullname+' trade '+payload.exchange+' '+payload.market+' '+payload.buysell)
                var hashname = payload.exchange.toLowerCase()+"-ticker-"+
                               payload.market.toUpperCase()+
                               payload.currency.toUpperCase()
                redis.hgetall(hashname, function(err,ticker){
                  console.log('redis return '+JSON.stringify(ticker))
                  var response = trade(payload, doc.inventory, ticker)
                  if(response.status == 'ok'){
                    console.log("prepending trades with "+JSON.stringify(response.payload.trade))
                    r.table('scripts').get(fullname)('trades').
                    prepend(response.payload.trade).run(conn, function(err, trades){
                      console.log('prepend trades done. size '+trades.length)
                      if(err) { console.log('rethink prepend error: '+err) }
                      console.log('updating inventory with '+JSON.stringify(doc.inventory))
                      r.table('scripts').get(fullname).
                      update({inventory:doc.inventory}).run(conn, function(err){
                        console.log('update inventory done')
                        if(err) { console.log('rethink update inventory error: '+err) }
                        var trade_msg = "["+payload.exchange+"] "+payload.buysell+" "+payload.quantity+payload.market+"@"+payload.amount+payload.currency
                        r.table('signals').insert({name:fullname,
                                                   time:(new Date()).toISOString(),
                                                   type:payload.action,
                                                   msg:trade_msg}).run(conn, function(err){if(err)console.log(err)})
                        respond(response)
                      })
                    })
                  } else {
                    respond(response)
                  }
                })
              } else {
                respond({"status":"err", "msg":"unknown action "+payload.action})
              }
            } else {
              console.log(fullname+" bad key!")
              respond({"status":"badkey"})
            }
          } else {
            console.log(fullname+" empty doc!")
            respond({"status":"nodoc"})
          }
        }
      })
    } catch (ex) {
      console.log(ex+' bad JSON "'+data+'"')
      respond({"status":"garbled"})
    }

  })

  sock.connect('tcp://127.0.0.1:3004');
  console.log('storage relay connected to dealer on 3004')

  function respond(payload){
    var data = JSON.stringify(payload)
    console.log('REP: '+data)
    sock.send(data)
  }

  //trade('mtgox','btc',4,'buy','usd',92)
  //trade('mtgox','btc',4,'sell','usd',97)
  //payload: exchange, market, quantity, buysell, currency, amount, cb
  function trade(payload, inventory, ticker){
    var result // return value
    var ticker_age_sec = ((new Date()) - new Date(ticker.now))/1000
    if(ticker_age_sec < 120){
      var price_diff_ratio = (Math.abs(payload.amount - ticker.value))/ticker.value
      if(price_diff_ratio <= 0.01){
        if(payload.buysell == 'buy') {
          var on_hand = inventory[payload.currency]
          if(on_hand){
            var price = (payload.amount * payload.quantity)
            if(on_hand >= price) {
                inventory[payload.currency] -= price
                inventory[payload.market] += payload.quantity
                result = {"status":"ok", payload: {trade:payload, inventory:inventory}}
            } else {
              result = {"status":"err", payload:""+on_hand+payload.currency+" is in sufficient for "+price}
            }
          } else {
            result = {"status":"err", payload:"no "+payload.currency+" in inventory"}
          }
        } else if (payload.buysell == 'sell') {
          var on_hand = inventory[payload.market]
          if(on_hand){
            if(on_hand >= payload.quantity){
              inventory[payload.market] -= payload.quantity
              if(typeof(inventory[payload.currency]) == "undefined") {  inventory[payload.currency] = 0}
              inventory[payload.currency] += (payload.quantity * payload.amount)
              result = {"status":"ok", payload: {trade:payload, inventory:inventory}}
            } else {
              result = {"status":"err", payload:""+on_hand+payload.market+" is in sufficient for "+payload.quantity}
            }
          } else {
            result = {"status":"err", payload:"no "+payload.market+" in inventory"}
          }
        }
      } else {
        result = {"status":"err", payload:"price of "+payload.amount+" is more than 1% away from exchange "+payload.exchange+" price "+ticker.value}
      }
    } else {
      result = {"status":"err", payload:"exchange "+payload.exchange+" price too old ("+ticker_age_sec+" secs). please try again."}
    }

    return result
  }

})
<commit_msg>set command should update storage variable directly<commit_after>var zmq = require('zmq'),
    sock = zmq.socket('rep');
var r = require('rethinkdb'),
    redis = require('redis').createClient()

r.connect({host:'localhost', port:28015, db:'cointhink'},
  function(err, conn) {

  conn.addListener('error', function(e) {
    console.log("rethinkdb error: "+e)
  })

  conn.addListener('close', function() {
    console.log("rethinkdb closed")
  })

  redis.on("error", function (err) {
        console.log("Redis Error " + err);
  })

  sock.on('message', function(data){
    console.log("REQ: "+data)
    try {
      var message = JSON.parse(data)
      var payload = message.payload
      var fullname = message.username+"/"+message.scriptname

      console.log("rethink load called on "+fullname)
      r.table('scripts').get(fullname).run(conn, function(err, doc){
        if(err){
          console.log('rethink load error: '+err)
          respond({"status":"dberr"})
        } else {
          console.log(fullname+' rethink doc loaded')
          if(doc){
            if(doc.key == message.key){
              var storage = doc.storage
              if(payload.action == 'get'){
                var value = storage[payload.key]
                console.log(fullname+' get '+payload.key+' '+value)
                respond({"status":"ok", "payload":value})
              } else if(payload.action == 'set'){
                console.log(fullname+' set '+payload.key+' '+payload.value)
                storage[payload.key] = payload.value
                var update_hash = {}
                update_hash[payload.key] = payload.value
                r.table('scripts').get(fullname).
                  update({storage:update_hash}).run(conn, function(status){
                  console.log(fullname+' set '+payload.key+' '+payload.value+' = '+status)
                  respond({"status":"ok", "payload":status})
                })
              } else if(payload.action == 'load'){
                console.log(fullname+' load storage, returning '+JSON.stringify(storage))
                respond({"status":"ok", "payload":storage})
              } else if(payload.action == 'store'){
                console.log(fullname+' store storage '+JSON.stringify(payload.storage))
                if(typeof(payload.storage) == 'object'){
                  r.table('scripts').get(fullname).
                    update({storage:payload.storage}).run(conn, function(status){
                    console.log(fullname+' store storage result '+status)
                    respond({"status":"ok", "payload":status})
                  })
                } else {
                  respond({"status":"err", "msg":"storage must be an object"})
                }
              } else if(payload.action == 'trade'){
                console.log(fullname+' trade '+payload.exchange+' '+payload.market+' '+payload.buysell)
                var hashname = payload.exchange.toLowerCase()+"-ticker-"+
                               payload.market.toUpperCase()+
                               payload.currency.toUpperCase()
                redis.hgetall(hashname, function(err,ticker){
                  console.log('redis return '+JSON.stringify(ticker))
                  var response = trade(payload, doc.inventory, ticker)
                  if(response.status == 'ok'){
                    console.log("prepending trades with "+JSON.stringify(response.payload.trade))
                    r.table('scripts').get(fullname)('trades').
                    prepend(response.payload.trade).run(conn, function(err, trades){
                      console.log('prepend trades done. size '+trades.length)
                      if(err) { console.log('rethink prepend error: '+err) }
                      console.log('updating inventory with '+JSON.stringify(doc.inventory))
                      r.table('scripts').get(fullname).
                      update({inventory:doc.inventory}).run(conn, function(err){
                        console.log('update inventory done')
                        if(err) { console.log('rethink update inventory error: '+err) }
                        var trade_msg = "["+payload.exchange+"] "+payload.buysell+" "+payload.quantity+payload.market+"@"+payload.amount+payload.currency
                        r.table('signals').insert({name:fullname,
                                                   time:(new Date()).toISOString(),
                                                   type:payload.action,
                                                   msg:trade_msg}).run(conn, function(err){if(err)console.log(err)})
                        respond(response)
                      })
                    })
                  } else {
                    respond(response)
                  }
                })
              } else {
                respond({"status":"err", "msg":"unknown action "+payload.action})
              }
            } else {
              console.log(fullname+" bad key!")
              respond({"status":"badkey"})
            }
          } else {
            console.log(fullname+" empty doc!")
            respond({"status":"nodoc"})
          }
        }
      })
    } catch (ex) {
      console.log(ex+' bad JSON "'+data+'"')
      respond({"status":"garbled"})
    }

  })

  sock.connect('tcp://127.0.0.1:3004');
  console.log('storage relay connected to dealer on 3004')

  function respond(payload){
    var data = JSON.stringify(payload)
    console.log('REP: '+data)
    sock.send(data)
  }

  //trade('mtgox','btc',4,'buy','usd',92)
  //trade('mtgox','btc',4,'sell','usd',97)
  //payload: exchange, market, quantity, buysell, currency, amount, cb
  function trade(payload, inventory, ticker){
    var result // return value
    var ticker_age_sec = ((new Date()) - new Date(ticker.now))/1000
    if(ticker_age_sec < 120){
      var price_diff_ratio = (Math.abs(payload.amount - ticker.value))/ticker.value
      if(price_diff_ratio <= 0.01){
        if(payload.buysell == 'buy') {
          var on_hand = inventory[payload.currency]
          if(on_hand){
            var price = (payload.amount * payload.quantity)
            if(on_hand >= price) {
                inventory[payload.currency] -= price
                inventory[payload.market] += payload.quantity
                result = {"status":"ok", payload: {trade:payload, inventory:inventory}}
            } else {
              result = {"status":"err", payload:""+on_hand+payload.currency+" is in sufficient for "+price}
            }
          } else {
            result = {"status":"err", payload:"no "+payload.currency+" in inventory"}
          }
        } else if (payload.buysell == 'sell') {
          var on_hand = inventory[payload.market]
          if(on_hand){
            if(on_hand >= payload.quantity){
              inventory[payload.market] -= payload.quantity
              if(typeof(inventory[payload.currency]) == "undefined") {  inventory[payload.currency] = 0}
              inventory[payload.currency] += (payload.quantity * payload.amount)
              result = {"status":"ok", payload: {trade:payload, inventory:inventory}}
            } else {
              result = {"status":"err", payload:""+on_hand+payload.market+" is in sufficient for "+payload.quantity}
            }
          } else {
            result = {"status":"err", payload:"no "+payload.market+" in inventory"}
          }
        }
      } else {
        result = {"status":"err", payload:"price of "+payload.amount+" is more than 1% away from exchange "+payload.exchange+" price "+ticker.value}
      }
    } else {
      result = {"status":"err", payload:"exchange "+payload.exchange+" price too old ("+ticker_age_sec+" secs). please try again."}
    }

    return result
  }

})
<|endoftext|>


<commit_before>var app = app || {};

app.LibraryView = Backbone.View.extend({

    initialize: function(options) {
        this.collection.on('add reset remove', function() {
            this.render(this.collection);
        }, this);

        this.itemView = options.itemView;
        this.filter = options.filter || false;
        if (this.filter) {
            this.search(this.filter)
        } else {
            this.render();
        }
    },

    render: function(collection, isFiltered) {
        var numActiveItems = 0,
            totalItems = 0,
            numItemsDisplayed = 0;
        collection = collection || this.collection;
        if (isFiltered && collection.length == this.collection.length) {
            return this;
        }
        this.$el.html('');

        if (!isFiltered) {
            if (collection.length > 0) {
                collection.each(function(item) {
                    this.renderItem(item);
                }, this);
            } else {
                this.$el.html($('#noItemsTemplate').html());
            }
        } else {
            //get the total number of active items
            numActiveItems = this.collection.where({
                active: true
            }).length;
            totalItems = numActiveItems;
            numItemsDisplayed = collection.toArray().length;
            if (numItemsDisplayed < totalItems) {
                this.$el.html('Displaying ' + numItemsDisplayed + ' out of ' + totalItems);
            }
            collection.each(function(item) {
                this.renderItem(item);
            }, this);
        }

        return this;
    },

    renderItem: function(item) {
        var itemView = new this.itemView({
            model: item
        });
        this.$el.append(itemView.render().el);
    },

    search: function(options) {
        var collection = (options && options.collection ? options.collection : this.collection);

        if (!options || options.val == '') {
            this.render(this.collection, false);
        } else {
            this.render(collection.search(options), true);
        }
    }
});
<commit_msg>render optimization<commit_after>var app = app || {};

app.LibraryView = Backbone.View.extend({

    initialize: function(options) {
        this.collection.on('add reset remove', function() {
            this.render(this.collection);
        }, this);

        this.itemView = options.itemView;
        this.filter = options.filter || false;
        if (this.filter) {
            this.search(this.filter)
        } else {
            this.render();
        }
    },

    render: function(collection, isFiltered) {
        var numActiveItems = 0,
            totalItems = 0,
            numItemsDisplayed = 0;
        collection = collection || this.collection,
        html;
        if (isFiltered && collection.length == this.collection.length) {
            return this;
        }
        this.$el.html('');

        if (!isFiltered) {
            if (collection.length > 0) {
                collection.each(function(item) {
                    this.renderItem(item);
                }, this);
                this.$el.append(html);
            } else {
                this.$el.html($('#noItemsTemplate').html());
            }
        } else {
            //get the total number of active items
            numActiveItems = this.collection.where({
                active: true
            }).length;
            totalItems = numActiveItems;
            numItemsDisplayed = collection.toArray().length;
            if (numItemsDisplayed < totalItems) {
                this.$el.html('Displaying ' + numItemsDisplayed + ' out of ' + totalItems);
            }
            collection.each(function(item) {
                this.renderItem(item);
            }, this);
            this.$el.append(html);
        }

        return this;
    },

    renderItem: function(item) {
        var itemView = new this.itemView({
            model: item
        });
        html += itemView.render().el;

    },

    search: function(options) {
        var collection = (options && options.collection ? options.collection : this.collection);

        if (!options || options.val == '') {
            this.render(this.collection, false);
        } else {
            this.render(collection.search(options), true);
        }
    }
});
<|endoftext|>


<commit_before>/*global $,app*/

app.views.results = function (parsedQuery) {
  console.debug('*results*', parsedQuery)

  // Breadcrumbs
  app.hooks.resultsCrumb.find('b').text(parsedQuery.label);
  app.hooks.crumbs.update(app.hooks.resultsCrumb);

  // Search
  app.hooks.searchRight.html(' ');
  app.hooks.searchLeft.removeClass('medium-14').addClass('medium-4').html(' ');
  app.hooks.searchBox.removeClass('medium-10 float-right').addClass('medium-16');

  // Empty both content areas
  app.hooks.content.children().detach();
  app.hooks.belowContent.children().detach();
  app.hooks.aboveContent.children().detach();

  var endpointMap = {
    'address': 'addresses',
    'account': 'account',
    'block': 'block',
    'owner': 'owner',
  };

  var endpoint = endpointMap[parsedQuery.type] + '/',
      isOwnerSearch = false;

  switch (parsedQuery.type) {
    case 'account':
      endpoint += encodeURI(parsedQuery.account);
      break;
    case 'block':
      endpoint += encodeURI(parsedQuery.address);
      break;
    case 'address':
      endpoint += encodeURI(parsedQuery.address);

      if (parsedQuery.unit) {
        endpoint += encodeURI(' UNIT ' + parsedQuery.unit);
      }
      break;
    case 'owner':
      isOwnerSearch = true;
      endpoint += encodeURI(parsedQuery.owner);
      break;
  }

  app.hooks.content.empty(); // Remove loading message
  app.hooks.ownerSearchDisclaimer.addClass('hide');

  if (history.state && app.globals.historyState) {
    render();
  } else {
    app.hooks.content.append(app.hooks.loading);
    getData()
  }

  function getData () {
    var params = {
      // gatekeeperKey: app.config['gatekeeperKey'],
      include_units: null,
      opa_only: null,
    };

    $.ajax( 'https://api.phila.gov/ais/v1/' + endpoint,
      {data: params, dataType: app.config.ajaxType})
      .done(function (aisData) {
        var property, accountNumber, href, withUnit;

        if (!app.globals.historyState) history.state = {};

        // For business reasons, owner searches need to always show on the
        // results page for the disclaimer.
        if (!isOwnerSearch && (
            aisData.type == 'Feature' ||
            aisData.features.length === 1)) {
          // If only one property go straight to property view
          feature = (aisData.features ? aisData.features[0] : aisData);
          attrs = feature.properties;
          accountNumber = attrs.opa_account_num;
          href = '?' + $.param({p: accountNumber});
          withUnit = feature.properties.street_address;

          history.replaceState({
            ais: feature,
            address: withUnit,
          }, withUnit, href);

          app.views.property(accountNumber);
        } else {
          // Fetch market_value, sale data from OPA dataset
          var opaUrl = constructOpaUrl(aisData.features);
          $.ajax(opaUrl, {dataType: app.config.ajaxType})
          .done(function (opaData) {
            var keyedOpaData = keyBy(opaData, 'parcel_number')
            $.each(aisData.features, function (index, feature) {
              $.extend(feature.properties, keyedOpaData[feature.properties.opa_account_num] || {})
            })

            var newState = $.extend({}, history.state);
            // Used for rendering a special owner search disclaimer
            if (isOwnerSearch) {
              aisData = $.extend({isOwnerSearch: true}, aisData);
            }
            newState = aisData

            if (!app.globals.historyState) {
              history.state = newState;
            } else {
              history.replaceState(newState, ''); // Second param not optional in IE10
            }
            render();
          });
        }
      })
      .fail(function () {
        var error = app.config.defaultError;
        history.replaceState({error: error}, '');
        render();
      });
  }

  function constructOpaUrl (features) {
    var accountNumbers = $.map(features, function (feature) {
      return feature.properties.opa_account_num
    })

    var params = {
      $select: [
        'parcel_number',
        'market_value',
        'sale_date',
        'sale_price'
      ].join(','),
      $where: 'parcel_number in ("' + accountNumbers.join('","') + '")'
    };
    return '//data.phila.gov/resource/w7rb-qrn8.json?' + $.param(params)
  }

  function keyBy (items, key) {
    var hash = {}
    for (var i = 0; i < items.length; i++) {
      hash[items[i][key]] = items[i]
    }
    return hash
  }

  function render () {
    var state = history.state;
    if (state.error) return app.hooks.content.text(state.error);
    var features = state.features;
    var total_size = state.total_size;
    var isOwnerSearch = state.isOwnerSearch;

    app.hooks.content.empty(); // Remove loading message

    if (total_size === 0) {
      return app.hooks.content.append(app.hooks.noResults);
    }

    if (isOwnerSearch) {
      renderOwnerSearchDisclaimer();
    }

    // TODO find a place for count
    //app.hooks.count.find('#total').text(state.total);
    //app.hooks.content.append(app.hooks.count);
    app.hooks.resultRows.empty(); // TODO reuse existing result nodes
    if (features) {
      $.each(features, addRow);

      if (total_size > features.length) {
        var seeMoreA = app.hooks.seeMore.find('a');
        seeMoreA.off('click'); // Drop previously created click events
        seeMoreA.on('click', function (e) {

          var params = {
            // gatekeeperKey: app.config['gatekeeperKey'],
            include_units: null,
            opa_only: null,
            page: state.page + 1
          };

          $.ajax('https://api.phila.gov/ais/v1/' + endpoint,
                 {data: params, dataType: app.config.ajaxType})
            .done(function (aisData) {
              // Fetch market_value, sale data from OPA dataset
              var opaUrl = constructOpaUrl(aisData.features);
              $.ajax(opaUrl, {dataType: app.config.ajaxType})
              .done(function (opaData) {
                var keyedOpaData = keyBy(opaData, 'parcel_number')
                $.each(aisData.features, function (index, feature) {
                  $.extend(feature.properties, keyedOpaData[feature.properties.opa_account_num] || {})
                })

                state.features = state.features.concat(aisData.features);
                state.page = aisData.page;
                history.replaceState(state, ''); // Second param not optional in IE10

                $.each(aisData.features, addRow);
                if (state.total_size === state.features.length) app.hooks.seeMore.hide();

                // Remove old Tablesaw data and refresh
                $('[data-hook="results-table"]').table().data("table").destroy();
                $('[data-hook="results-table"]').table().data("table").refresh();
              });
            });
        });
        app.hooks.seeMore.show();
      } else {
        app.hooks.seeMore.hide();
      }
      app.hooks.content.append(app.hooks.results);

      // Update the Tablesaw responsive tables
      $(document).trigger('enhance.tablesaw');
    }
  }

  function renderOwnerSearchDisclaimer() {
    var now = new Date(),
        mins = now.getMinutes(),
        prettyMins = mins < 10 ? ('0' + mins) : mins,
        prettyNow = (now.getMonth() + 1) + '/' + now.getDate() + '/' +  now.getFullYear() +
                    ' ' + now.getHours() + ':' + prettyMins;

    app.hooks.ownerSearchDisclaimer.removeClass('hide');
    app.hooks.ownerSearchDisclaimerDatetime.text(prettyNow);
    app.hooks.ownerSearchDisclaimerQuery.text(parsedQuery.label);

    // Fetch IP & show it
    $.getJSON('https://api.ipify.org?format=json')
      .done(function(response) {
        if(response.ip) {
          app.hooks.ownerSearchDisclaimerIp.text(response.ip);
        }
      })
      .fail(function() {
        app.hooks.ownerSearchDisclaimerIp.text(app.hooks.ownerSearchDisclaimerIp.attr('data-default'));
      });
  }

  function addRow (index, property) {
    var row = app.hooks.resultRow.clone(),
        attrs = property.properties,
        accountNumber = attrs.opa_account_num,
        withUnit = attrs.street_address,
        href = '?' + $.param({p: accountNumber});

    row.append($('').append($('').text(withUnit)));
    row.append($('').text(accounting.formatMoney(attrs.market_value)));
    row.append($('').text(moment(attrs.sale_date).format('M/D/YYYY') + ', ' + accounting.formatMoney(attrs.sale_price)));
    row.append($('').text(attrs.opa_owners && attrs.opa_owners.join(', ')));
    row.append($('').html(''));

    row.on('click', function (e) {
        if (e.ctrlKey || e.altKey || e.shiftKey) return;
        e.preventDefault();
        history.pushState({ais: property, address: withUnit}, withUnit, href);
        window.scroll(0, 0);
        app.views.property(accountNumber);
      });
    app.hooks.resultRows.append(row);
  }
};
<commit_msg>Refactor AIS callback to handle cached results<commit_after>/*global $,app*/

app.views.results = function (parsedQuery) {
  console.debug('*results*', parsedQuery)

  // Breadcrumbs
  app.hooks.resultsCrumb.find('b').text(parsedQuery.label);
  app.hooks.crumbs.update(app.hooks.resultsCrumb);

  // Search
  app.hooks.searchRight.html(' ');
  app.hooks.searchLeft.removeClass('medium-14').addClass('medium-4').html(' ');
  app.hooks.searchBox.removeClass('medium-10 float-right').addClass('medium-16');

  // Empty both content areas
  app.hooks.content.children().detach();
  app.hooks.belowContent.children().detach();
  app.hooks.aboveContent.children().detach();

  var endpointMap = {
    'address': 'addresses',
    'account': 'account',
    'block': 'block',
    'owner': 'owner',
  };

  var endpoint = endpointMap[parsedQuery.type] + '/',
      isOwnerSearch = false;

  switch (parsedQuery.type) {
    case 'account':
      endpoint += encodeURI(parsedQuery.account);
      break;
    case 'block':
      endpoint += encodeURI(parsedQuery.address);
      break;
    case 'address':
      endpoint += encodeURI(parsedQuery.address);

      if (parsedQuery.unit) {
        endpoint += encodeURI(' UNIT ' + parsedQuery.unit);
      }
      break;
    case 'owner':
      isOwnerSearch = true;
      endpoint += encodeURI(parsedQuery.owner);
      break;
  }

  app.hooks.content.empty(); // Remove loading message
  app.hooks.ownerSearchDisclaimer.addClass('hide');

  var state = history.state;

  if (app.globals.historyState && state && state.ais) {
    didGetAisData(history.state.ais);
  } else {
    app.hooks.content.append(app.hooks.loading);
    getAisData();
  }

  function getAisData () {
    var params = {
      // gatekeeperKey: app.config['gatekeeperKey'],
      include_units: null,
      opa_only: null,
    };

    $.ajax({
      url: 'https://api.phila.gov/ais/v1/' + endpoint,
      data: params,
      dataType: app.config.ajaxType,
      success: didGetAisData,
      fail: function () {
        var error = app.config.defaultError;
        history.replaceState({error: error}, '');
        render();
      },
    });
  }

  function didGetAisData (aisData) {
    var property, accountNumber, href, withUnit;

    if (!app.globals.historyState) history.state = {};

    // For business reasons, owner searches need to always show on the
    // results page for the disclaimer.
    if (!isOwnerSearch && (
      aisData.type == 'Feature' ||
      aisData.features.length === 1)) {
        // If only one property go straight to property view
        feature = (aisData.features ? aisData.features[0] : aisData);
        attrs = feature.properties;
        accountNumber = attrs.opa_account_num;
        href = '?' + $.param({p: accountNumber});
        withUnit = feature.properties.street_address;

        history.replaceState({
          ais: feature,
          address: withUnit,
        }, withUnit, href);

        app.views.property(accountNumber);
      } else {
        // Fetch market_value, sale data from OPA dataset
        var opaUrl = constructOpaUrl(aisData.features);
        $.ajax(opaUrl, {dataType: app.config.ajaxType})
        .done(function (opaData) {
          var keyedOpaData = keyBy(opaData, 'parcel_number')
          $.each(aisData.features, function (index, feature) {
            $.extend(feature.properties, keyedOpaData[feature.properties.opa_account_num] || {})
          })

          var newState = $.extend({}, history.state);
          // Used for rendering a special owner search disclaimer
          if (isOwnerSearch) {
            aisData = $.extend({isOwnerSearch: true}, aisData);
          }
          newState = aisData

          if (!app.globals.historyState) {
            history.state = newState;
          } else {
            history.replaceState(newState, ''); // Second param not optional in IE10
          }
          render();
        });
      }
  }

  function constructOpaUrl (features) {
    var accountNumbers = $.map(features, function (feature) {
      return feature.properties.opa_account_num
    })

    var params = {
      $select: [
        'parcel_number',
        'market_value',
        'sale_date',
        'sale_price'
      ].join(','),
      $where: 'parcel_number in ("' + accountNumbers.join('","') + '")'
    };
    return '//data.phila.gov/resource/w7rb-qrn8.json?' + $.param(params)
  }

  function keyBy (items, key) {
    var hash = {}
    for (var i = 0; i < items.length; i++) {
      hash[items[i][key]] = items[i]
    }
    return hash
  }

  function render () {
    console.debug('results render')

    var state = history.state;
    if (state.error) return app.hooks.content.text(state.error);
    var features = state.features;
    var total_size = state.total_size;
    var isOwnerSearch = state.isOwnerSearch;

    app.hooks.content.empty(); // Remove loading message

    if (total_size === 0) {
      return app.hooks.content.append(app.hooks.noResults);
    }

    if (isOwnerSearch) {
      renderOwnerSearchDisclaimer();
    }

    // TODO find a place for count
    //app.hooks.count.find('#total').text(state.total);
    //app.hooks.content.append(app.hooks.count);
    app.hooks.resultRows.empty(); // TODO reuse existing result nodes
    if (features) {
      $.each(features, addRow);

      if (total_size > features.length) {
        var seeMoreA = app.hooks.seeMore.find('a');
        seeMoreA.off('click'); // Drop previously created click events
        seeMoreA.on('click', function (e) {

          var params = {
            // gatekeeperKey: app.config['gatekeeperKey'],
            include_units: null,
            opa_only: null,
            page: state.page + 1
          };

          $.ajax('https://api.phila.gov/ais/v1/' + endpoint,
                 {data: params, dataType: app.config.ajaxType})
            .done(function (aisData) {
              // Fetch market_value, sale data from OPA dataset
              var opaUrl = constructOpaUrl(aisData.features);
              $.ajax(opaUrl, {dataType: app.config.ajaxType})
              .done(function (opaData) {
                var keyedOpaData = keyBy(opaData, 'parcel_number')
                $.each(aisData.features, function (index, feature) {
                  $.extend(feature.properties, keyedOpaData[feature.properties.opa_account_num] || {})
                })

                state.features = state.features.concat(aisData.features);
                state.page = aisData.page;
                history.replaceState(state, ''); // Second param not optional in IE10

                $.each(aisData.features, addRow);
                if (state.total_size === state.features.length) app.hooks.seeMore.hide();

                // Remove old Tablesaw data and refresh
                $('[data-hook="results-table"]').table().data("table").destroy();
                $('[data-hook="results-table"]').table().data("table").refresh();
              });
            });
        });
        app.hooks.seeMore.show();
      } else {
        app.hooks.seeMore.hide();
      }
      app.hooks.content.append(app.hooks.results);

      // Update the Tablesaw responsive tables
      $(document).trigger('enhance.tablesaw');
    }
  }

  function renderOwnerSearchDisclaimer() {
    var now = new Date(),
        mins = now.getMinutes(),
        prettyMins = mins < 10 ? ('0' + mins) : mins,
        prettyNow = (now.getMonth() + 1) + '/' + now.getDate() + '/' +  now.getFullYear() +
                    ' ' + now.getHours() + ':' + prettyMins;

    app.hooks.ownerSearchDisclaimer.removeClass('hide');
    app.hooks.ownerSearchDisclaimerDatetime.text(prettyNow);
    app.hooks.ownerSearchDisclaimerQuery.text(parsedQuery.label);

    // Fetch IP & show it
    $.getJSON('https://api.ipify.org?format=json')
      .done(function(response) {
        if(response.ip) {
          app.hooks.ownerSearchDisclaimerIp.text(response.ip);
        }
      })
      .fail(function() {
        app.hooks.ownerSearchDisclaimerIp.text(app.hooks.ownerSearchDisclaimerIp.attr('data-default'));
      });
  }

  function addRow (index, property) {
    var row = app.hooks.resultRow.clone(),
        attrs = property.properties,
        accountNumber = attrs.opa_account_num,
        withUnit = attrs.street_address,
        href = '?' + $.param({p: accountNumber});

    row.append($('').append($('').text(withUnit)));
    row.append($('').text(accounting.formatMoney(attrs.market_value)));
    row.append($('').text(moment(attrs.sale_date).format('M/D/YYYY') + ', ' + accounting.formatMoney(attrs.sale_price)));
    row.append($('').text(attrs.opa_owners && attrs.opa_owners.join(', ')));
    row.append($('').html(''));

    row.on('click', function (e) {
        if (e.ctrlKey || e.altKey || e.shiftKey) return;
        e.preventDefault();
        history.pushState({ais: property, address: withUnit}, withUnit, href);
        window.scroll(0, 0);
        app.views.property(accountNumber);
      });
    app.hooks.resultRows.append(row);
  }
};
<|endoftext|>


<commit_before>function worldMap() {
  let map;

  let width = 0;
  let height = 0;
  let shift = 0;

  let tooltipLeft = 0;
  let tooltipTop = 0;
  let tooltip;

  let scale = 1;
  let initX = 0;
  let mouseClicked = false;
  let selectedCountry = '';
  let zoomExtent = [1, 10];

  let onSelectLocation;

  let projection, path, zoom, g;

  function onStartPanning() {
    d3.event.preventDefault();
    initX = d3.mouse(this)[0];
    mouseClicked = true;
  };

  function onEndPanning() {
    shift = shift + ((d3.mouse(this)[0] - initX) * 360 / (scale * width));
    mouseClicked = false;
  };

  const svg = d3.select('#map svg')
    .on('mousedown', onStartPanning)
    .on('mouseup', onEndPanning);

  function onShiftMap(endX) {
    projection.rotate([shift + (endX - initX) * 360 / (scale * width), 0, 0])
    g.selectAll('path')
      .attr('d', path);
  }

  function showTooltip(name) {
    const mouse = d3.mouse(svg.node())
      .map(function(v) { return parseInt(v); } );
    tooltip.classed('__hidden', false)
      .attr('style', 'left:' + (mouse[0] + tooltipLeft) + 'px;top:' + (mouse[1] + tooltipTop) + 'px')
      .html(name);
  }

  function onClick(id, name) {
    if (selectedCountry !== id) {
      selectedCountry = id;
      d3.select('.__selected').classed('__selected', false);
      d3.select('#' + id).classed('__selected', true);
      onSelectLocation(id, name);
    }
  }

  function onZoomMap() {
    const t = d3.event.translate;
    const h = 0;
    scale = d3.event.scale;

    t[0] = Math.min(
      (width/height) * (scale - 1),
      Math.max(width * (1 - scale), t[0])
    );

    t[1] = Math.min(
      h * (scale - 1) + h * scale,
      Math.max(height * (1 - scale) - h * scale, t[1])
    );

    zoom.translate(t);
    if (mouseClicked) {
      onShiftMap(d3.mouse(this)[0]);
      return;
    }

    g.attr('transform', 'translate(' + t + ')scale(' + scale + ')');
    d3.selectAll('.vis-world')
      .style('stroke-width', 0.5 / scale);

    // TODO: call for cities
  }

  function resizeMap() {
    scale = 1;
    width = map.clientWidth;
    height = map.clientHeight;

    tooltipLeft = map.offsetLeft + 10;
    tooltipTop = map.offsetTop + 10;

    projection = d3.geo.mercator()
      .scale(Math.min(width, height) * 0.2)
      .translate([width / 2, height / 1.5])
      .rotate([shift, 0, 0]);

    zoom.on('zoom', onZoomMap);

    path = d3.geo
      .path()
      .projection(projection);

    svg
      .attr('width', width)
      .attr('height', height)
      .call(zoom);

    onShiftMap(initX);
  }

  function initWorldMap(onSelect, avaliableCountries) {
    map = document.getElementById('map');
    tooltip = d3.select('#map .vis-tooltip');
    g = svg.append('g');
    onSelectLocation = onSelect;
    zoom = d3.behavior.zoom().scaleExtent(zoomExtent)

    resizeMap();

    d3.json('data/topology.json', function(error, world) {
      if(error) return console.error(error);

      g.append('g')
        .attr('class', 'vis-world')
        .selectAll('vis-world')
        .data(topojson.feature(world, world.countries).features)
        .enter().append('path')
        .attr('class', 'vis-country')
        .attr('id', function(d) { return d.id; })
        .attr('d', path)
        .each(function(d) {
          const item = d3.select(this);
          const filter = avaliableCountries.filter((country) => country.code === d.id);
          if (filter.length > 0) {
            item
              .on('click', function(d) { onClick(d.id, filter[0].name); })
              .on('mousemove', function(d) { showTooltip(filter[0].name); })
              .on('mouseout',  function(d, i) { tooltip.classed('__hidden', true); });
          } else {
            item
              .attr('class', 'vis-country __disabled');
          }
        });
    });
  }

  function showCities(avaliableCities) {
    // TODO
  }

  return {
    initWorldMap,
    resizeMap
  };
};
<commit_msg>Change event from click to mousedown<commit_after>function worldMap() {
  let map;

  let width = 0;
  let height = 0;
  let shift = 0;

  let tooltipLeft = 0;
  let tooltipTop = 0;
  let tooltip;

  let scale = 1;
  let initX = 0;
  let mouseClicked = false;
  let selectedCountry = '';
  let zoomExtent = [1, 10];

  let onSelectLocation;

  let projection, path, zoom, g;

  function onStartPanning() {
    d3.event.preventDefault();
    initX = d3.mouse(this)[0];
    mouseClicked = true;
  };

  function onEndPanning() {
    shift = shift + ((d3.mouse(this)[0] - initX) * 360 / (scale * width));
    mouseClicked = false;
  };

  const svg = d3.select('#map svg')
    .on('mousedown', onStartPanning)
    .on('mouseup', onEndPanning);

  function onShiftMap(endX) {
    projection.rotate([shift + (endX - initX) * 360 / (scale * width), 0, 0])
    g.selectAll('path')
      .attr('d', path);
  }

  function showTooltip(name) {
    const mouse = d3.mouse(svg.node())
      .map(function(v) { return parseInt(v); } );
    tooltip.classed('__hidden', false)
      .attr('style', 'left:' + (mouse[0] + tooltipLeft) + 'px;top:' + (mouse[1] + tooltipTop) + 'px')
      .html(name);
  }

  function onClick(id, name) {
    if (selectedCountry !== id) {
      selectedCountry = id;
      d3.select('.__selected').classed('__selected', false);
      d3.select('#' + id).classed('__selected', true);
      onSelectLocation(id, name);
    }
  }

  function onZoomMap() {
    const t = d3.event.translate;
    const h = 0;
    scale = d3.event.scale;

    t[0] = Math.min(
      (width/height) * (scale - 1),
      Math.max(width * (1 - scale), t[0])
    );

    t[1] = Math.min(
      h * (scale - 1) + h * scale,
      Math.max(height * (1 - scale) - h * scale, t[1])
    );

    zoom.translate(t);
    if (mouseClicked) {
      onShiftMap(d3.mouse(this)[0]);
      return;
    }

    g.attr('transform', 'translate(' + t + ')scale(' + scale + ')');
    d3.selectAll('.vis-world')
      .style('stroke-width', 0.5 / scale);

    // TODO: call for cities
  }

  function resizeMap() {
    scale = 1;
    width = map.clientWidth;
    height = map.clientHeight;

    tooltipLeft = map.offsetLeft + 10;
    tooltipTop = map.offsetTop + 10;

    projection = d3.geo.mercator()
      .scale(Math.min(width, height) * 0.2)
      .translate([width / 2, height / 1.5])
      .rotate([shift, 0, 0]);

    zoom.on('zoom', onZoomMap);

    path = d3.geo
      .path()
      .projection(projection);

    svg
      .attr('width', width)
      .attr('height', height)
      .call(zoom);

    onShiftMap(initX);
  }

  function initWorldMap(onSelect, avaliableCountries) {
    map = document.getElementById('map');
    tooltip = d3.select('#map .vis-tooltip');
    g = svg.append('g');
    onSelectLocation = onSelect;
    zoom = d3.behavior.zoom().scaleExtent(zoomExtent)

    resizeMap();

    d3.json('data/topology.json', function(error, world) {
      if(error) return console.error(error);

      g.append('g')
        .attr('class', 'vis-world')
        .selectAll('vis-world')
        .data(topojson.feature(world, world.countries).features)
        .enter().append('path')
        .attr('class', 'vis-country')
        .attr('id', function(d) { return d.id; })
        .attr('d', path)
        .each(function(d) {
          const item = d3.select(this);
          const filter = avaliableCountries.filter((country) => country.code === d.id);
          if (filter.length > 0) {
            item
              .on('mousedown', function(d) { onClick(d.id, filter[0].name); })
              .on('mousemove', function(d) { showTooltip(filter[0].name); })
              .on('mouseout',  function(d, i) { tooltip.classed('__hidden', true); });
          } else {
            item
              .attr('class', 'vis-country __disabled');
          }
        });
    });
  }

  function showCities(avaliableCities) {
    // TODO
  }

  return {
    initWorldMap,
    resizeMap
  };
};
<|endoftext|>


<commit_before>var width = $('#content').parent().width(),
    height = 700,
    centered;

var svg, projection, path, g;
var school_scale, school_data, activeId, choropleth_data;
var all_data = {}, activeData = 'population_total';
var min_population = 100;
var defaultColor = "#aaa",
    fiveColors = ["#e5ffc7", "#d9fcb9", "#bbef8e", "#9ad363", "#6eb43f"],
    fourColors = ["#e5ffc7", "#d9fcb9", "#bbef8e", "#6eb43f"],
    threeColors = ["#e5ffc7", "#bbef8e", "#6eb43f"];

$(document).ready(function() {
  init();
}); // end document ready function

function init(){
  drawChoropleth();
  drawSchools();

  // slide out menu
  $('.menu-toggle').on('click', toggleMenu);

  // event listeners for changing d3
  // choropleth color change
  $('.neighborhood-menu > li').on('click', 'a', function(e){
    e.preventDefault();
    changeNeighborhoodData($(this).attr('id'));
    $(this).parent().addClass('selected').siblings().removeClass('selected');
  });
  // circle changes
  $('.school-menu > li').on('click', 'a', function(e){
    e.preventDefault();
    var value = $(this).attr('id') === 'no_school_data' ? 4 : $(this).attr('id');
    changeSchoolData(value);
    $(this).parent().addClass('selected').siblings().removeClass('selected');
  });
}

function drawChoropleth(){
  projection = d3.geo.mercator()
    .center([-77.01551, 38.90755]) // Dunbar High School
    .scale(150000)
    .translate([width/2, height/2]);

  path = d3.geo.path().projection(projection);

  svg = d3.select("#content")
    .append("svg:svg")
    .attr("width", width)
    .attr("height", height);

  svg.append("rect")
      .attr("class", "background")
      .attr("width", width)
      .attr("height", height)
      .on("click", clicked);

  g = svg.append("g");
  g.append("g").attr("id", "neighborhoods");
  g.append("g").attr("id", "schools");
  g.append("g").attr("id", "legend");

  queue()
    // .defer(d3.json, "data/neighborhood_boundaries.json")
    // .defer(d3.csv, "data/neighborhoods.csv")
    .defer(d3.json, "data/neighborhoods44.json")
    .defer(d3.csv, "data/neighborhoods44.csv")
    .await(setUpChoropleth);

  function setUpChoropleth(error, dc, choropleth) {
    //clean choropleth data for display.
    choropleth_data = choropleth;
    choropleth_data.forEach(function(d) {
      all_data[d.gis_id] = d;
      choropleth_data[d.gis_id] = +d.population_total;
    });

    g.select("#neighborhoods")
      .selectAll("path")
        .data(dc.features)
      .enter().append("path")
        .attr("d", path)
        .attr('class', 'nbhd')
        .on("click", clicked)
        .on("mouseover", hoverNeighborhood)
        .style("fill", defaultColor);

  } // setUpChoropleth function

}

function changeNeighborhoodData(new_data_column) {
  var data_values = _.compact(_.map(choropleth_data, function(d){ return parseFloat(d[new_data_column]); }));
  var jenks = _.unique(ss.jenks(data_values, 3));
  var color_palette = ["#e5ffc7", "#d9fcb9", "#bbef8e", "#9ad363", "#6eb43f"];
  activeData = new_data_column;

  var choro_color = d3.scale.threshold()
    .domain(jenks)
    .range(color_palette);

  choropleth_data.forEach(function(d) {
    choropleth_data[d.gis_id] = +d[new_data_column];
  });

  g.select("#neighborhoods").selectAll("path")
    .transition().duration(600)
    .style("fill", function(d) {
      var totalPop = all_data[d.properties.gis_id].population_total;
      return totalPop > min_population ? choro_color(all_data[d.properties.gis_id][new_data_column]) : defaultColor;
    });

  if(activeId && new_data_column !== 'no_neighborhood_data') {
    setVisMetric(new_data_column, all_data[activeId][new_data_column]);
  } else {
    setVisMetric(null, null, true);
  }

  var legendText = function(d, jenks){
    if(_.max(jenks) < 1){
      return parseInt(d * 100, 10) + "%";
    } else {
      var number_formatter = d3.format(",");
      return number_formatter(parseInt(d, 10));
    }
  };

  var updatedLegend = g.select("#legend").selectAll(".legend")
      .data(jenks);

  updatedLegend.select("text")
    .text(function(d){ return legendText(d, jenks);});

  enterLegend = updatedLegend.enter().append("g")
    .attr("transform", function(d, i){ return "translate(0," + (350 + i * 35) + ")"; })
    .attr("class", "legend");

  enterLegend.append("rect")
    .attr("width", 70)
    .attr("height", 30)
    .style("fill", function(d){ return choro_color(d);});

  enterLegend.append("text")
    .style("fill", "black")
    .attr("dy",20)
    .attr("dx", 5)
    .text(function(d){ return legendText(d, jenks); });

  updatedLegend.exit().remove();

}

function drawSchools(){
  var packer = sm.packer();

  d3.csv('data/schools.csv', function(data){
    school_data = data;
    school_scale = d3.scale.sqrt().range([1,10]);
    g.select("#schools").selectAll("circle")
      .data(data).enter().append("circle")
        .attr('class', 'school')
        .attr("r", 4)
        .attr("transform", function(d) {
          return "translate(" +
            projection([d.long, d.lat]) +
            ")";})
        .on("click", displaySchoolData)
        .append("title").text(function(d){return d.name;});
    packMetros();

    function displaySchoolData(school) {
      $("#details").prepend("

"+school.name+"

enrollment: " +
          getDisplayValue(school.enrollment, 'enrollment') + "

allocation: " +
          getDisplayValue(school.alloc, 'alloc') + "

");
    }
  });
  function packMetros() {
    var elements = d3.selectAll('#content circle')[0];
    packer.elements(elements).start();
  }
}

function changeSchoolData(new_data_column) {
  if (typeof new_data_column === 'string'){
    matchScaleToData(school_scale, function(d){return +d[new_data_column];})
  }
  g.select("#schools").selectAll("circle")
    .transition().duration(600)
    .attr("r", function(d) {
      return typeof new_data_column !== 'string' ? 4 : school_scale(d[new_data_column]);
    });
}

function matchScaleToData(scale, fieldFunction) {
  var minimum = d3.min(school_data, fieldFunction),
      maximum = d3.max(school_data, fieldFunction);
  scale.domain([minimum, maximum]);
}

function toggleMenu() {
  var $this = $('.menu-toggle');
  if ($this.parent().hasClass('toggled')){
    $this.parent().animate({ 'left' : 0 }, 350, function(){ $('#main-container').removeClass('toggled') });
  } else {
    $this.parent().animate({ 'left' : $('#nav-panel').width() }, 350, function(){ $('#main-container').addClass('toggled') });
  }
}

function displayPopBox(d) {
  //clear the menu if it's exposed.
  if($('#main-container')[0].classList.contains('toggled')) {
    toggleMenu();
  }

  var $popbox = $('#pop-info'),
      highlighted = all_data[d.properties.gis_id];

  $popbox.siblings('.panel-heading').find('.panel-title').html(highlighted.NBH_NAMES);

  var val, key;
  $.each($popbox.find('tr'), function(k, row){
    key = $(row).attr('data-type');
    val = highlighted[key];
    $(row).find('.count').html(getDisplayValue(val, key));
  });
}

function clicked(d) {
  var x, y, k;

  if (d && centered !== d) {
    var centroid = path.centroid(d);
    x = centroid[0];
    y = centroid[1];
    k = 4;
    centered = d;
  } else {
    x = width / 2;
    y = height / 2;
    k = 1;
    centered = null;
  }

  g.selectAll("path")
      .classed("active", centered && function(d) { return d === centered; });

  g.transition()
      .duration(750)
      .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
      .style("stroke-width", 1.5 / k + "px");

  // if d is a neighborhood boundary and clicked
  if (d && all_data[d.properties.gis_id]){
    displayPopBox(d);
    //last neighborhood to display in popBox.
    activeId = d.properties.gis_id;
    setVisMetric(activeData, all_data[activeId][activeData]);
  }
}

function hoverNeighborhood(d) {

  //bring hovered neighborhood path to front.
  var neighborhood = d3.select(d3.event.target);
  neighborhood.each(function () {
    this.parentNode.appendChild(this);
  });

  //but also keep centered neighborhood path up front
  if (centered) {
    var activeNeighborhood = d3.select(".active");
    activeNeighborhood.each(function () {
      this.parentNode.appendChild(this);
    });
    return;
  }

  if (d && all_data[d.properties.gis_id]){
    displayPopBox(d);
    //last neighborhood to display in popBox.
    activeId = d.properties.gis_id;
    setVisMetric(activeData, all_data[activeId][activeData]);
  }
}

function cleanData(rawData) {
  var d, cleanedData = [];

  for (var i = 0, len = rawData.length; i < len; i++) {
    d = rawData[i];

    for(var key in d){
      if(d.hasOwnProperty(key) && !isNaN(parseInt(d[key], 10))) {
        d[key] = cleanNumber(d[key], key);
      }
    }

    cleanedData.push(d);
  }

  return cleanedData;

  function cleanNumber(strNum, name) {
    var num = parseFloat(strNum);

    if (name.indexOf('_perc') !== -1) { //percentage
      num = num * 100;
      num = Math.round(num);
      return num + '%';
    } else if (name.indexOf('alloc') !== -1) {  //some kind of allocation amount
      return '$' + num.addCommas();
    }

    num = Math.round(num);
    return num.addCommas(0);
  }
}

function getDisplayValue(strNum, name) {
  var num = parseFloat(strNum);

  if (isNaN(num)) { return strNum; }

  name = name.toLowerCase();

  if (name.indexOf('perc') !== -1) { //percentage
    num = num * 100;
    num = Math.round(num);
    return num + '%';
  } else if ((name.indexOf('alloc') !== -1) || (name.indexOf('amount') !== -1)) {  //some kind of allocation or amount
    return '$' + num.addCommas();
  } else if (name.indexOf('ratio') !== -1) { //a ratio
    return num.toPrecision(2);
  }

  num = Math.round(num);
  return num.addCommas(0);
}

function setVisMetric(metric, val, clear) {
  var $metric = $('#visualized-metric');
  var $metricDesc = $('#visualized-description');

  if (clear) {
    $metric.text('');
    $metricDesc.text('');
    return;
  }

  var metricText = $('a#' + metric).text();
  $metric.text(metricText);
  $metricDesc.text(getDisplayValue(val, metricText));
};

Number.prototype.addCommas = function(decimalPlaces) {
  var n = this,
      c = isNaN(decimalPlaces) ? 2 : Math.abs(decimalPlaces),
      d = '.',
      t = ',',
      sign = (n < 0) ? '-' : '',
      i = parseInt(n = Math.abs(n).toFixed(c), 10) + '',
      initialDigits = ((initialDigits = i.length) > 3) ? initialDigits % 3 : 0;

  return sign + (initialDigits? i.substr(0, initialDigits) + t : '')
      + i.substr(initialDigits).replace(/(\d{3})(?=\d)/g, '$1' + t)
      + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : '');
};
<commit_msg>add another jenks breakpoint<commit_after>var width = $('#content').parent().width(),
    height = 700,
    centered;

var svg, projection, path, g;
var school_scale, school_data, activeId, choropleth_data;
var all_data = {}, activeData = 'population_total';
var min_population = 100;
var defaultColor = "#aaa",
    fiveColors = ["#e5ffc7", "#d9fcb9", "#bbef8e", "#9ad363", "#6eb43f"],
    fourColors = ["#e5ffc7", "#d9fcb9", "#bbef8e", "#6eb43f"],
    threeColors = ["#e5ffc7", "#bbef8e", "#6eb43f"];

$(document).ready(function() {
  init();
}); // end document ready function

function init(){
  drawChoropleth();
  drawSchools();

  // slide out menu
  $('.menu-toggle').on('click', toggleMenu);

  // event listeners for changing d3
  // choropleth color change
  $('.neighborhood-menu > li').on('click', 'a', function(e){
    e.preventDefault();
    changeNeighborhoodData($(this).attr('id'));
    $(this).parent().addClass('selected').siblings().removeClass('selected');
  });
  // circle changes
  $('.school-menu > li').on('click', 'a', function(e){
    e.preventDefault();
    var value = $(this).attr('id') === 'no_school_data' ? 4 : $(this).attr('id');
    changeSchoolData(value);
    $(this).parent().addClass('selected').siblings().removeClass('selected');
  });
}

function drawChoropleth(){
  projection = d3.geo.mercator()
    .center([-77.01551, 38.90755]) // Dunbar High School
    .scale(150000)
    .translate([width/2, height/2]);

  path = d3.geo.path().projection(projection);

  svg = d3.select("#content")
    .append("svg:svg")
    .attr("width", width)
    .attr("height", height);

  svg.append("rect")
      .attr("class", "background")
      .attr("width", width)
      .attr("height", height)
      .on("click", clicked);

  g = svg.append("g");
  g.append("g").attr("id", "neighborhoods");
  g.append("g").attr("id", "schools");
  g.append("g").attr("id", "legend");

  queue()
    // .defer(d3.json, "data/neighborhood_boundaries.json")
    // .defer(d3.csv, "data/neighborhoods.csv")
    .defer(d3.json, "data/neighborhoods44.json")
    .defer(d3.csv, "data/neighborhoods44.csv")
    .await(setUpChoropleth);

  function setUpChoropleth(error, dc, choropleth) {
    //clean choropleth data for display.
    choropleth_data = choropleth;
    choropleth_data.forEach(function(d) {
      all_data[d.gis_id] = d;
      choropleth_data[d.gis_id] = +d.population_total;
    });

    g.select("#neighborhoods")
      .selectAll("path")
        .data(dc.features)
      .enter().append("path")
        .attr("d", path)
        .attr('class', 'nbhd')
        .on("click", clicked)
        .on("mouseover", hoverNeighborhood)
        .style("fill", defaultColor);

  } // setUpChoropleth function

}

function changeNeighborhoodData(new_data_column) {
  var data_values = _.compact(_.map(choropleth_data, function(d){ return parseFloat(d[new_data_column]); }));
  var jenks = _.unique(_.compact(ss.jenks(data_values, 4)));
  var color_palette = [ "#e5ffc7", "#d9fcb9", "#bbef8e", "#9ad363", "#6eb43f", "#6eb43f"];
  activeData = new_data_column;

  var choro_color = d3.scale.threshold()
    .domain(jenks)
    .range(color_palette);

  choropleth_data.forEach(function(d) {
    choropleth_data[d.gis_id] = +d[new_data_column];
  });

  g.select("#neighborhoods").selectAll("path")
    .transition().duration(600)
    .style("fill", function(d) {
      var totalPop = all_data[d.properties.gis_id].population_total;
      return totalPop > min_population ? choro_color(all_data[d.properties.gis_id][new_data_column]) : defaultColor;
    });

  if(activeId && new_data_column !== 'no_neighborhood_data') {
    setVisMetric(new_data_column, all_data[activeId][new_data_column]);
  } else {
    setVisMetric(null, null, true);
  }

  var legendText = function(d, jenks){
    if(_.max(jenks) < 1){
      return parseInt(d * 100, 10) + "%";
    } else {
      var number_formatter = d3.format(",");
      return number_formatter(parseInt(d, 10));
    }
  };

  var updatedLegend = g.select("#legend").selectAll(".legend")
      .data(jenks);

  updatedLegend.select("text")
    .text(function(d){ return legendText(d, jenks);});

  enterLegend = updatedLegend.enter().append("g")
    .attr("transform", function(d, i){ return "translate(0," + (350 + i * 35) + ")"; })
    .attr("class", "legend");

  enterLegend.append("rect")
    .attr("width", 70)
    .attr("height", 30)
    .style("fill", function(d){ return choro_color(d);});

  enterLegend.append("text")
    .style("fill", "black")
    .attr("dy",20)
    .attr("dx", 5)
    .text(function(d){ return legendText(d, jenks); });

  updatedLegend.exit().remove();

}

function drawSchools(){
  var packer = sm.packer();

  d3.csv('data/schools.csv', function(data){
    school_data = data;
    school_scale = d3.scale.sqrt().range([1,10]);
    g.select("#schools").selectAll("circle")
      .data(data).enter().append("circle")
        .attr('class', 'school')
        .attr("r", 4)
        .attr("transform", function(d) {
          return "translate(" +
            projection([d.long, d.lat]) +
            ")";})
        .on("click", displaySchoolData)
        .append("title").text(function(d){return d.name;});
    packMetros();

    function displaySchoolData(school) {
      $("#details").prepend("

"+school.name+"

enrollment: " +
          getDisplayValue(school.enrollment, 'enrollment') + "

allocation: " +
          getDisplayValue(school.alloc, 'alloc') + "

");
    }
  });
  function packMetros() {
    var elements = d3.selectAll('#content circle')[0];
    packer.elements(elements).start();
  }
}

function changeSchoolData(new_data_column) {
  if (typeof new_data_column === 'string'){
    matchScaleToData(school_scale, function(d){return +d[new_data_column];})
  }
  g.select("#schools").selectAll("circle")
    .transition().duration(600)
    .attr("r", function(d) {
      return typeof new_data_column !== 'string' ? 4 : school_scale(d[new_data_column]);
    });
}

function matchScaleToData(scale, fieldFunction) {
  var minimum = d3.min(school_data, fieldFunction),
      maximum = d3.max(school_data, fieldFunction);
  scale.domain([minimum, maximum]);
}

function toggleMenu() {
  var $this = $('.menu-toggle');
  if ($this.parent().hasClass('toggled')){
    $this.parent().animate({ 'left' : 0 }, 350, function(){ $('#main-container').removeClass('toggled') });
  } else {
    $this.parent().animate({ 'left' : $('#nav-panel').width() }, 350, function(){ $('#main-container').addClass('toggled') });
  }
}

function displayPopBox(d) {
  //clear the menu if it's exposed.
  if($('#main-container')[0].classList.contains('toggled')) {
    toggleMenu();
  }

  var $popbox = $('#pop-info'),
      highlighted = all_data[d.properties.gis_id];

  $popbox.siblings('.panel-heading').find('.panel-title').html(highlighted.NBH_NAMES);

  var val, key;
  $.each($popbox.find('tr'), function(k, row){
    key = $(row).attr('data-type');
    val = highlighted[key];
    $(row).find('.count').html(getDisplayValue(val, key));
  });
}

function clicked(d) {
  var x, y, k;

  if (d && centered !== d) {
    var centroid = path.centroid(d);
    x = centroid[0];
    y = centroid[1];
    k = 4;
    centered = d;
  } else {
    x = width / 2;
    y = height / 2;
    k = 1;
    centered = null;
  }

  g.selectAll("path")
      .classed("active", centered && function(d) { return d === centered; });

  g.transition()
      .duration(750)
      .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
      .style("stroke-width", 1.5 / k + "px");

  // if d is a neighborhood boundary and clicked
  if (d && all_data[d.properties.gis_id]){
    displayPopBox(d);
    //last neighborhood to display in popBox.
    activeId = d.properties.gis_id;
    setVisMetric(activeData, all_data[activeId][activeData]);
  }
}

function hoverNeighborhood(d) {

  //bring hovered neighborhood path to front.
  var neighborhood = d3.select(d3.event.target);
  neighborhood.each(function () {
    this.parentNode.appendChild(this);
  });

  //but also keep centered neighborhood path up front
  if (centered) {
    var activeNeighborhood = d3.select(".active");
    activeNeighborhood.each(function () {
      this.parentNode.appendChild(this);
    });
    return;
  }

  if (d && all_data[d.properties.gis_id]){
    displayPopBox(d);
    //last neighborhood to display in popBox.
    activeId = d.properties.gis_id;
    setVisMetric(activeData, all_data[activeId][activeData]);
  }
}

function cleanData(rawData) {
  var d, cleanedData = [];

  for (var i = 0, len = rawData.length; i < len; i++) {
    d = rawData[i];

    for(var key in d){
      if(d.hasOwnProperty(key) && !isNaN(parseInt(d[key], 10))) {
        d[key] = cleanNumber(d[key], key);
      }
    }

    cleanedData.push(d);
  }

  return cleanedData;

  function cleanNumber(strNum, name) {
    var num = parseFloat(strNum);

    if (name.indexOf('_perc') !== -1) { //percentage
      num = num * 100;
      num = Math.round(num);
      return num + '%';
    } else if (name.indexOf('alloc') !== -1) {  //some kind of allocation amount
      return '$' + num.addCommas();
    }

    num = Math.round(num);
    return num.addCommas(0);
  }
}

function getDisplayValue(strNum, name) {
  var num = parseFloat(strNum);

  if (isNaN(num)) { return strNum; }

  name = name.toLowerCase();

  if (name.indexOf('perc') !== -1) { //percentage
    num = num * 100;
    num = Math.round(num);
    return num + '%';
  } else if ((name.indexOf('alloc') !== -1) || (name.indexOf('amount') !== -1)) {  //some kind of allocation or amount
    return '$' + num.addCommas();
  } else if (name.indexOf('ratio') !== -1) { //a ratio
    return num.toPrecision(2);
  }

  num = Math.round(num);
  return num.addCommas(0);
}

function setVisMetric(metric, val, clear) {
  var $metric = $('#visualized-metric');
  var $metricDesc = $('#visualized-description');

  if (clear) {
    $metric.text('');
    $metricDesc.text('');
    return;
  }

  var metricText = $('a#' + metric).text();
  $metric.text(metricText);
  $metricDesc.text(getDisplayValue(val, metricText));
};

Number.prototype.addCommas = function(decimalPlaces) {
  var n = this,
      c = isNaN(decimalPlaces) ? 2 : Math.abs(decimalPlaces),
      d = '.',
      t = ',',
      sign = (n < 0) ? '-' : '',
      i = parseInt(n = Math.abs(n).toFixed(c), 10) + '',
      initialDigits = ((initialDigits = i.length) > 3) ? initialDigits % 3 : 0;

  return sign + (initialDigits? i.substr(0, initialDigits) + t : '')
      + i.substr(initialDigits).replace(/(\d{3})(?=\d)/g, '$1' + t)
      + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : '');
};
<|endoftext|>