<commit_before>var Git = require('nodegit');
var { EOL } = require('os');
var fs = require('fs');
var path = require('path');
//provides some nicer logs function, such as only showing on the --verbose flag
var logger = require('captains-log');
var gitRoot = '';

function getNewRevisionLines(hunkHeader) {
    var start = hunkHeader.indexOf('+') + 1;
    hunkHeader = hunkHeader.slice(start, hunkHeader.length-1);
    var end = hunkHeader.indexOf('@') - 1;
    var content = hunkHeader.slice(0, end);
    var contentSplit = content.split(',');
    return {
        start : parseInt(contentSplit[0]),
        length : parseInt(contentSplit[1])
    };
}

function getOldRevisionLines(hunkHeader) {
    var start = hunkHeader.indexOf('-') + 1;
    var end = hunkHeader.indexOf('+') - 1;
    var content = hunkHeader.slice(start, end);
    var contentSplit = content.split(',');
    return {
        start : parseInt(contentSplit[0]),
        length : parseInt(contentSplit[1]),
    };
}

// Extract info from the hunk header into an object
function interpretHunkHeader(hunkHeader) {
    var oldLines = getOldRevisionLines(hunkHeader);
    var newLines = getNewRevisionLines(hunkHeader);
    return {
        oldRevision: oldLines,
        newRevision: newLines
    };
}

// Dealing with the condition where lines
function removedLineBlame(runningBlame, newHunk, commitHash) {
    var hunkHeader = interpretHunkHeader(newHunk.header());
    var newIndex = hunkHeader.newRevision.start - 1;
    var counter = 0;
    for (var line of newHunk.lines()) {
        var origin = String.fromCharCode(line.origin());
        if (origin == '-') {
            // Remove the elemnt at index
            runningBlame.splice(newIndex + counter, 1)
        } else if (origin == '+') {
            var indexBlame = runningBlame[newIndex + counter];
            indexBlame.commit.push(commitHash);
            var content = getLineContentFromLine(line);
            var lineBlame = {
                commit: indexBlame.commit,
                line: content
            };
            runningBlame.splice(newIndex + counter, 0, lineBlame);
            counter++;
        } else {
            counter++;
        }
    }
    return runningBlame;
}

function applyRules(runningBlame, newHunk, commitHash) {
    // delegate the rules according to the hunk header
    var hunkHeader = interpretHunkHeader(newHunk.header());
    // this would reveal whether it's purely add/remove or a mixture of the two.
    if (hunkHeader.oldRevision.length == hunkHeader.newRevision.length) {
        return changedLineBlame(runningBlame, newHunk, commitHash);
    } else if (hunkHeader.oldRevision.length < hunkHeader.newRevision.length) {
        return addedLineBlame(runningBlame, newHunk, commitHash);
    } else {
        return removedLineBlame(runningBlame, newHunk, commitHash);
    }
}

function getLineContentFromLine(line) {
    var content = line.content();
    return content.slice(0, content.indexOf(EOL));
}

function changedLineBlame(runningBlame, newHunk, commitHash) {
    var hunkHeader = interpretHunkHeader(newHunk.header());
    var newIndex = hunkHeader.newRevision.start - 1;
    var counter = 0; // deals with the offset
    for (var line of newHunk.lines()) {
        var origin = String.fromCharCode(line.origin());
        if (origin == '-') {
            // The line being replaced, not sure if we need to do anything
            // No increment
        } else if (origin == '+') {
            // Line needs to be changed
            var indexBlame = runningBlame[newIndex + counter];
            indexBlame.commit.push(commitHash);
            var content = getLineContentFromLine(line);
            var lineBlame = {
                commit: indexBlame.commit,
                line: content
            };
            runningBlame[newIndex + counter] = lineBlame;
            counter++;
        } else {
            counter++;
        }
    }
    return runningBlame;
}

function addedLineBlame(runningBlame, newHunk, commitHash) {
    if (runningBlame.length == 0) {
        // If previous blame is empty, i.e. dealing with initial commit
        var newBlame = [];
        for (var line of newHunk.lines()) {
            var content = line.content();
            content = content.slice(0, content.indexOf(EOL));
            var lineBlame = {
                commit: [commitHash],
                line: content
            };
            newBlame.push(lineBlame);
        }
        return newBlame;
    } else {
        // Previous blame is not empty, will need to insert new line to
        // position provided by the hunk header
        var header = interpretHunkHeader(newHunk.header());
        var newIndex = header.newRevision.start - 1;
        var counter = 0;
        var deleteCounter = 0;
        for (var line of newHunk.lines()) {
            var origin = String.fromCharCode(line.origin());
            if (origin == '+') {
                var content = getLineContentFromLine(line);
                if (deleteCounter == 0) {
                    var lineBlame = {
                        commit: [commitHash],
                        line: content
                    }
                    runningBlame.splice(newIndex + counter, 0, lineBlame);
                } else {
                    var indexBlame = runningBlame[newIndex + counter];
                    indexBlame.commit.push(commitHash);
                    var lineBlame = {
                        commit: indexBlame.commit,
                        line: content
                    };
                    runningBlame[newIndex + counter] = lineBlame;
                    deleteCounter--;
                }
                counter++;
            } else if (origin == '-') {
                deleteCounter++;
            } else {
                counter++;
            }
        }
        return runningBlame;
    }
}

//if you pass 'file' it looks for the flag '--file' or '-f'
function parseArg(type) {
  var argVal = null;
  if (type === 'file' && process.argv.length > 2) {
    //if there is no -- at all in the string or at a point later, within the filename
    if (process.argv[2].indexOf('--') === -1 || process.argv[2].indexOf('--') > 0) {
      return process.argv[2]
    }
  }
  for (var i = 0; i < process.argv.length - 1; i++) {
    if (process.argv[i] === `--${type}` || process.argv[i] === `-${type.slice(0, 1)}`) {
      argVal = process.argv[i + 1];
      break;
    }
  }
  return argVal;
}

//Note: Whenever you use an 'await' in a function, it needs to be async.
//And you always need to put the await keyword before an async function call
//so var [firstMasterCommit, gitRoot] = await getFirstMasterCommit();
async function getFirstMasterCommit(atPath) {
  atPath = atPath || './';
  try {
    return Git.Repository.openExt(path.dirname(atPath), 0, '').then(function (repository) {
      gitRoot = path.dirname(repository.path());
      return repository.getMasterCommit();
    }).catch(function (err) {
      throw err;
    });
  } catch (err) {
    throw new Error(err);
  }
}

module.exports = async function (cmdArgs) {
  var fileName = parseArg('file') || cmdArgs[0] || null;
  var atPath = parseArg('file') || './';
  //making it able for tests to set the level of logging at runtime
  var log = logger(cmdArgs[1] || {});
  var runningBlame = [];

  if (typeof fileName === 'undefined' || fileName === null || fileName.length === 0) {
    var err = new Error('Please provide a valid filename');
    err.name = 'NotValidFile';
    throw err;
  }

  // Don't think this check is necessary since you could provide a filename from
  // a previous revision
  // if (!fs.statSync(fileName)) {
  //   var err = new Error('Please provide a file that currently exists in your file sysyem.');
  //   err.type = 'FileNotFound';
  //   throw err;
  // }

  var firstMasterCommit = await getFirstMasterCommit(atPath);
  fileName = path.relative(gitRoot, fileName).trim();
  console.log(fileName.trim());
  var history = firstMasterCommit.history(Git.Revwalk.SORT.REVERSE);
  //var commits = [];
  //
  function printBlame(blame) {
    //save the print in a string so we can return it.
    var printedBlame = '';
    for (var lineBlame of blame) {
      printedBlame += lineBlame.commit.join() + ': ' + lineBlame.line + EOL
    }
    //like console.log but without a trailing newline
    process.stdout.write(printedBlame);
    return printedBlame;
  }

  history.on('commit', async function (commit) {
    //Generate an array of diff trees showing changes between this commit and its parent(s).
    //This is essentially the same as 'git diff <parentCommitId> <childCommitId>'
    var diffList = await commit.getDiff();
    var filePath = '';

    for (var diff of diffList) {
      //Retrieve patches (ConvenientPatches in nodegit) in this difflist
      for (var patch of diff.patches()) {
        //not sure why to check oldFile and newFile path, but library example did so.
        var oldFilePath = patch.oldFile().path();
        var newFilePath = patch.newFile().path();
        if (newFilePath.includes(fileName)) {
          filePath = newFilePath;
        } else if (oldFilePath.includes(fileName)) {
          filePath = oldFilePath;
        }

        //found the file we are looking for
        if (filePath.length > 0) {
          log.verbose();
          log.verbose();
          log.verbose(`Found file in commit ${commit.sha()}`);
          log.verbose("Author:", commit.author().name() +
         " <" + commit.author().email() + ">");
          log.verbose("Date:", commit.date());
          log.verbose("\n    " + commit.message());
          log.verbose(`Showing hunk/diff for file ${filePath}`);

          //getting the hunks (ConvenientHunk) in this patch
          //that is the diff of this file
          for (var hunk of patch.hunks()) {
            log.verbose('displayed hunk/diff size:', hunk.size());
            log.verbose('header', hunk.header().trim());
            runningBlame = applyRules(runningBlame, hunk, commit.sha());
            var count = 1;
            //getting the diff content line-by-line
            for (var line of hunk.lines()) {
              var content = line.content();
              log.verbose(count, String.fromCharCode(line.origin()) + ' ' + content.slice(0, content.indexOf(EOL)));
              count++;
            }
          }
          //found file so break out of for loop
          break;
        }
      }
      //found file in that commit, so break
      if (filePath.length > 0) {
        break;
      }
    }
  });

  //Promises are needed to return something from within a callback function.
  var promise = new Promise(function (resolve, reject){
    history.on('end', function () {
      var printedBlame = printBlame(runningBlame);
      resolve(printedBlame);
    });

    history.on('error', function(error) {
      reject(error);
    });

    // Don't forget to call `start()`!
    history.start();
  });

  return promise;
};
<commit_msg>[Hash] shortened hash to 7 characters<commit_after>var Git = require('nodegit');
var { EOL } = require('os');
var fs = require('fs');
var path = require('path');
//provides some nicer logs function, such as only showing on the --verbose flag
var logger = require('captains-log');
var gitRoot = '';

function getNewRevisionLines(hunkHeader) {
    var start = hunkHeader.indexOf('+') + 1;
    hunkHeader = hunkHeader.slice(start, hunkHeader.length-1);
    var end = hunkHeader.indexOf('@') - 1;
    var content = hunkHeader.slice(0, end);
    var contentSplit = content.split(',');
    return {
        start : parseInt(contentSplit[0]),
        length : parseInt(contentSplit[1])
    };
}

function getOldRevisionLines(hunkHeader) {
    var start = hunkHeader.indexOf('-') + 1;
    var end = hunkHeader.indexOf('+') - 1;
    var content = hunkHeader.slice(start, end);
    var contentSplit = content.split(',');
    return {
        start : parseInt(contentSplit[0]),
        length : parseInt(contentSplit[1]),
    };
}

// Extract info from the hunk header into an object
function interpretHunkHeader(hunkHeader) {
    var oldLines = getOldRevisionLines(hunkHeader);
    var newLines = getNewRevisionLines(hunkHeader);
    return {
        oldRevision: oldLines,
        newRevision: newLines
    };
}

// Dealing with the condition where lines
function removedLineBlame(runningBlame, newHunk, commitHash) {
    var hunkHeader = interpretHunkHeader(newHunk.header());
    var newIndex = hunkHeader.newRevision.start - 1;
    var counter = 0;
    for (var line of newHunk.lines()) {
        var origin = String.fromCharCode(line.origin());
        if (origin == '-') {
            // Remove the elemnt at index
            runningBlame.splice(newIndex + counter, 1)
        } else if (origin == '+') {
            var indexBlame = runningBlame[newIndex + counter];
            indexBlame.commit.push(commitHash);
            var content = getLineContentFromLine(line);
            var lineBlame = {
                commit: indexBlame.commit,
                line: content
            };
            runningBlame.splice(newIndex + counter, 0, lineBlame);
            counter++;
        } else {
            counter++;
        }
    }
    return runningBlame;
}

function applyRules(runningBlame, newHunk, commitHash) {
    // delegate the rules according to the hunk header
    var hunkHeader = interpretHunkHeader(newHunk.header());
    commitHash = commitHash.slice(0,7);
    // this would reveal whether it's purely add/remove or a mixture of the two.
    if (hunkHeader.oldRevision.length == hunkHeader.newRevision.length) {
        return changedLineBlame(runningBlame, newHunk, commitHash);
    } else if (hunkHeader.oldRevision.length < hunkHeader.newRevision.length) {
        return addedLineBlame(runningBlame, newHunk, commitHash);
    } else {
        return removedLineBlame(runningBlame, newHunk, commitHash);
    }
}

function getLineContentFromLine(line) {
    var content = line.content();
    return content.slice(0, content.indexOf(EOL));
}

function changedLineBlame(runningBlame, newHunk, commitHash) {
    var hunkHeader = interpretHunkHeader(newHunk.header());
    var newIndex = hunkHeader.newRevision.start - 1;
    var counter = 0; // deals with the offset
    for (var line of newHunk.lines()) {
        var origin = String.fromCharCode(line.origin());
        if (origin == '-') {
            // The line being replaced, not sure if we need to do anything
            // No increment
        } else if (origin == '+') {
            // Line needs to be changed
            var indexBlame = runningBlame[newIndex + counter];
            indexBlame.commit.push(commitHash);
            var content = getLineContentFromLine(line);
            var lineBlame = {
                commit: indexBlame.commit,
                line: content
            };
            runningBlame[newIndex + counter] = lineBlame;
            counter++;
        } else {
            counter++;
        }
    }
    return runningBlame;
}

function addedLineBlame(runningBlame, newHunk, commitHash) {
    if (runningBlame.length == 0) {
        // If previous blame is empty, i.e. dealing with initial commit
        var newBlame = [];
        for (var line of newHunk.lines()) {
            var content = line.content();
            content = content.slice(0, content.indexOf(EOL));
            var lineBlame = {
                commit: [commitHash],
                line: content
            };
            newBlame.push(lineBlame);
        }
        return newBlame;
    } else {
        // Previous blame is not empty, will need to insert new line to
        // position provided by the hunk header
        var header = interpretHunkHeader(newHunk.header());
        var newIndex = header.newRevision.start - 1;
        var counter = 0;
        var deleteCounter = 0;
        for (var line of newHunk.lines()) {
            var origin = String.fromCharCode(line.origin());
            if (origin == '+') {
                var content = getLineContentFromLine(line);
                if (deleteCounter == 0) {
                    var lineBlame = {
                        commit: [commitHash],
                        line: content
                    }
                    runningBlame.splice(newIndex + counter, 0, lineBlame);
                } else {
                    var indexBlame = runningBlame[newIndex + counter];
                    indexBlame.commit.push(commitHash);
                    var lineBlame = {
                        commit: indexBlame.commit,
                        line: content
                    };
                    runningBlame[newIndex + counter] = lineBlame;
                    deleteCounter--;
                }
                counter++;
            } else if (origin == '-') {
                deleteCounter++;
            } else {
                counter++;
            }
        }
        return runningBlame;
    }
}

//if you pass 'file' it looks for the flag '--file' or '-f'
function parseArg(type) {
  var argVal = null;
  if (type === 'file' && process.argv.length > 2) {
    //if there is no -- at all in the string or at a point later, within the filename
    if (process.argv[2].indexOf('--') === -1 || process.argv[2].indexOf('--') > 0) {
      return process.argv[2]
    }
  }
  for (var i = 0; i < process.argv.length - 1; i++) {
    if (process.argv[i] === `--${type}` || process.argv[i] === `-${type.slice(0, 1)}`) {
      argVal = process.argv[i + 1];
      break;
    }
  }
  return argVal;
}

//Note: Whenever you use an 'await' in a function, it needs to be async.
//And you always need to put the await keyword before an async function call
//so var [firstMasterCommit, gitRoot] = await getFirstMasterCommit();
async function getFirstMasterCommit(atPath) {
  atPath = atPath || './';
  try {
    return Git.Repository.openExt(path.dirname(atPath), 0, '').then(function (repository) {
      gitRoot = path.dirname(repository.path());
      return repository.getMasterCommit();
    }).catch(function (err) {
      throw err;
    });
  } catch (err) {
    throw new Error(err);
  }
}

module.exports = async function (cmdArgs) {
  var fileName = parseArg('file') || cmdArgs[0] || null;
  var atPath = parseArg('file') || './';
  //making it able for tests to set the level of logging at runtime
  var log = logger(cmdArgs[1] || {});
  var runningBlame = [];

  if (typeof fileName === 'undefined' || fileName === null || fileName.length === 0) {
    var err = new Error('Please provide a valid filename');
    err.name = 'NotValidFile';
    throw err;
  }

  // Don't think this check is necessary since you could provide a filename from
  // a previous revision
  // if (!fs.statSync(fileName)) {
  //   var err = new Error('Please provide a file that currently exists in your file sysyem.');
  //   err.type = 'FileNotFound';
  //   throw err;
  // }

  var firstMasterCommit = await getFirstMasterCommit(atPath);
  fileName = path.relative(gitRoot, fileName).trim();
  console.log(fileName.trim());
  var history = firstMasterCommit.history(Git.Revwalk.SORT.REVERSE);
  //var commits = [];
  //
  function printBlame(blame) {
    //save the print in a string so we can return it.
    var printedBlame = '';
    for (var lineBlame of blame) {
      printedBlame += lineBlame.commit.join() + ': ' + lineBlame.line + EOL
    }
    //like console.log but without a trailing newline
    process.stdout.write(printedBlame);
    return printedBlame;
  }

  history.on('commit', async function (commit) {
    //Generate an array of diff trees showing changes between this commit and its parent(s).
    //This is essentially the same as 'git diff <parentCommitId> <childCommitId>'
    var diffList = await commit.getDiff();
    var filePath = '';

    for (var diff of diffList) {
      //Retrieve patches (ConvenientPatches in nodegit) in this difflist
      for (var patch of diff.patches()) {
        //not sure why to check oldFile and newFile path, but library example did so.
        var oldFilePath = patch.oldFile().path();
        var newFilePath = patch.newFile().path();
        if (newFilePath.includes(fileName)) {
          filePath = newFilePath;
        } else if (oldFilePath.includes(fileName)) {
          filePath = oldFilePath;
        }

        //found the file we are looking for
        if (filePath.length > 0) {
          log.verbose();
          log.verbose();
          log.verbose(`Found file in commit ${commit.sha()}`);
          log.verbose("Author:", commit.author().name() +
         " <" + commit.author().email() + ">");
          log.verbose("Date:", commit.date());
          log.verbose("\n    " + commit.message());
          log.verbose(`Showing hunk/diff for file ${filePath}`);

          //getting the hunks (ConvenientHunk) in this patch
          //that is the diff of this file
          for (var hunk of patch.hunks()) {
            log.verbose('displayed hunk/diff size:', hunk.size());
            log.verbose('header', hunk.header().trim());
            runningBlame = applyRules(runningBlame, hunk, commit.sha());
            var count = 1;
            //getting the diff content line-by-line
            for (var line of hunk.lines()) {
              var content = line.content();
              log.verbose(count, String.fromCharCode(line.origin()) + ' ' + content.slice(0, content.indexOf(EOL)));
              count++;
            }
          }
          //found file so break out of for loop
          break;
        }
      }
      //found file in that commit, so break
      if (filePath.length > 0) {
        break;
      }
    }
  });

  //Promises are needed to return something from within a callback function.
  var promise = new Promise(function (resolve, reject){
    history.on('end', function () {
      var printedBlame = printBlame(runningBlame);
      resolve(printedBlame);
    });

    history.on('error', function(error) {
      reject(error);
    });

    // Don't forget to call `start()`!
    history.start();
  });

  return promise;
};
<|endoftext|>


<commit_before>/*********************************************************
Choropleth of Labour Force Projection Rates in Ireland 2011

Based in part on tutorial found here:-
http://www.cartographicperspectives.org/index.php/journal/article/view/1278/1359
**********************************************************/

//global variables
var keyArray = ["varA_F","varB_M"];
//variable names for display
var keyLongArray = [
	"Female",
	"Male"
	];
var expressed = keyArray[0];

//map colour palette
var colorPalette = [
			"#db9151",
			"#e8a363",
			"#f3b67a",
			"#facb96",
			"#fde1b8",
			"#f9f9df",
			"#e4d8e3",
			"#cdbae0",
			"#b7a1d8",
			"#a08bcb",
			"#8b79b9"
		]

//begin script when window loads
window.onload = initialize();

//the first function called once the html is loaded
function initialize(){
	setMap();
};
//set choropleth map parameters
function setMap(){
	//map frame dimensions
	var width = 400;
	var height = 530;

/*	//title for page
	var title = d3.select("body")
		.append("h1")
		.text("2011 Rates for Labour Force Participation");
*/
	//create a new SVG element with the above dimensions
	var map = d3.select("body")
		.append("svg")
		.attr("width", width)
		.attr("height", height)
		.attr("class", "map");

	//draw the colour palette legend
	var colorPanelWidth = 20 // the width of each block of colour
	var xPosIni = 90 // leftmost edge of legend

	map.selectAll("rect")
		.data(colorPalette).enter()
    .append("rect")
    .attr("fill", function (color){ return color; })
    .attr("x", function (color, index){
    	return xPosIni + (index * colorPanelWidth) + "px";
    })
    .attr("y", height-35)
    .attr("width", colorPanelWidth + "px")
    .attr("height", 10 + "px");

	map.selectAll("text")
		.data(["below average", "above average"]).enter()
		.append("text")
		.text(function (label){ return label})
		.attr("x", function (label, index){
			return xPosIni + (index * 147) + "px" //adjust rightmost label location
		})
		.attr("y", height-10)
		.attr("font-family", "sans-serif")
		.attr("font-size", "11px")
		.attr("fill", "grey");

	//Define map projection
	var projection = d3.geo.mercator()
		.scale([25000])
	  .center([-8.2, 53.25])
	  .translate([width / 2, height / 2]); //NEW

	//create svg path generator using the projection
	var path = d3.geo.path()
		.projection(projection);

	//use queue.js to parallelize asynchronous data loading
	queue()
		.defer(d3.csv, "assets/LFPR2011_Data.csv") //load csv data
		.defer(d3.json, "assets/ireland_mapping.topojson") //load geometry
		.await(callback); //trigger callback function once data loaded

	//retrieve and process data
	function callback(error, csvData, irelandData){
		var recolorMap = colorScale(csvData);

		//variables for csv to json data transfer
		//keyArray set as global variables
		var jsonRegions = irelandData.objects.ie_counties_redAtt.geometries;

		//loop through csv to assign each csv value to json region
		for (var i=0; i<csvData.length; i++) {
			var csvRegion = csvData[i]; //the current region
			var csvAdm1 = csvRegion.adm1_code; //adm1 code

			//loop through json regions to find right region
			for (var a=0; a<jsonRegions.length; a++){
				//where adm1 codes match, attach csv to json object
				if (jsonRegions[a].properties.GEOGID	== csvAdm1){

					// assign all five key/value pairs
					for (var key in keyArray){
						var attr = keyArray[key];
						var val = parseFloat(csvRegion[attr]);
						jsonRegions[a].properties[attr] = val;
					};

					jsonRegions[a].properties.name = csvRegion.name; //set prop
					break; //stop looking through the json regions
				};
			};
		};

		//add regions to map as enumeration units colored by data
		var regions = map.selectAll(".regions")
			.data(topojson.feature(irelandData,
				irelandData.objects.ie_counties_redAtt).features)
			.enter() //create elements
			.append("g") //give each province its own g element
			.attr("class", "regions") //assign class for additional styling
			.append("path")
			.attr("class", function(d) { return d.properties.GEOGID })
			.attr("d", path) //project data as geometry in svg
			.style("fill", function(d) { //color enumeration units
				return choropleth(d, recolorMap);
			})
			.on("mouseover", highlight)
			.on("mouseout", dehighlight)
			.on("mousemove", moveLabel)
			.append("desc") //append the current color
				.text(function(d) {
					return choropleth(d, recolorMap)
				});

		//create dropdown menu
		createDropdown(csvData);
	};
};

// from SO; ref. 3751520
function makeArray(count, content) {
   var result = [];
   if(typeof(content) == "function") {
      for(var i=0; i<count; i++) {
         result.push(content(i));
      }
   } else {
      for(var i=0; i<count; i++) {
         result.push(content);
      }
   }
   return result;
};

function createDropdown(csvData){
	//add a select element for the dropdown menu
	var dropdown = d3.select("body")
		.append("div")
		.attr("class","dropdown") //for positioning menu with css
		.html("
Gender:
")
		.append("select")
		.on("change", function(){
			changeAttribute(this.value, csvData);
		});

	//create each option element within the dropdown
	var keyNum = makeArray(keyArray.length, function(i) {
		return i++; });
	dropdown.selectAll("options")
		.data(keyNum)
		.enter()
		.append("option")
		.attr("value", function(d){ return keyArray[d] })
		.text(function(d) {
			d = keyLongArray[d];
			return d
		});
};

function colorScale(csvData){
	//create quantile classes with color scale
	var color = d3.scale.quantile() //designate quantile scale generator
		.range(colorPalette);

	//set min and max data values as domain
	color.domain([
		d3.min(csvData, function(d) { return Number(d[expressed]); }),
		d3.max(csvData, function(d) { return Number(d[expressed]); })
	]);

	return color;	 //return the color scale generator
};

function choropleth(d, recolorMap){
	//get data value
	var value = d.properties[expressed];
	//if value exists, assign it a color; otherwise assign gray
	if (value) {
		return recolorMap(value);
	} else {
		return "#ccc"; //default grey if no match
	};
};

function changeAttribute(attribute, csvData){
	//change the expressed attribute
	expressed = attribute;

	//recolor the map
	d3.selectAll(".regions") //select every region
		.select("path")
		.transition().duration(500).ease("linear")
		.style("fill", function(d) { //color enumeration units
			return choropleth(d, colorScale(csvData)); //->
		})
		.select("desc") //replace the color text in each desc element
			.text(function(d) {
				return choropleth(d, colorScale(csvData)); //->
			});
};

function highlight(data){

	var props = data.properties; //json properties

	d3.select("."+props.GEOGID) //select the current region in the DOM
		.transition().duration(150).ease("linear")
		.style("fill", "#000"); //set the enumeration unit fill to black

	var labelName = props.name; //html string for name (county)

	var labelAttribute = "
"+
		parseFloat(props[expressed]).toFixed(1)+ //display to 1 decimal
		"%" + "

"+
		labelName+""; //label content; value & variable

	//create info label div
	var infolabel = d3.select("body")
		.append("div")
		.attr("class", "infolabel") //for styling label
		.attr("id", props.GEOGID+"label") //for label div
		.html(labelAttribute); //add text
};

function dehighlight(data){

	//json or csv properties
	var props = data.properties; //json properties
	var region = d3.select("."+props.GEOGID); //select the current region
	var fillcolor = region.select("desc").text(); //access original color from desc
	region
	.transition().duration(300).ease("linear")
	.style("fill", fillcolor); //reset enumeration unit to orginal color

	d3.select("#"+props.GEOGID+"label").remove(); //remove info label
};

function moveLabel() {

	var x = d3.event.clientX+10; //horizontal label coordinate based mouse position stored in d3.event
	var y = d3.event.clientY-150; //vertical label coordinate
	d3.select(".infolabel") //select the label div for moving
		.style("margin-left", x+"px") //reposition label horizontal
		.style("margin-top", y+"px"); //reposition label vertical
};<commit_msg>more indents sorted<commit_after>/*********************************************************
Choropleth of Labour Force Projection Rates in Ireland 2011

Based in part on tutorial found here:-
http://www.cartographicperspectives.org/index.php/journal/article/view/1278/1359
**********************************************************/

//global variables
var keyArray = ["varA_F","varB_M"];
//variable names for display
var keyLongArray = [
	"Female",
	"Male"
	];
var expressed = keyArray[0];

//map colour palette
var colorPalette = [
			"#db9151",
			"#e8a363",
			"#f3b67a",
			"#facb96",
			"#fde1b8",
			"#f9f9df",
			"#e4d8e3",
			"#cdbae0",
			"#b7a1d8",
			"#a08bcb",
			"#8b79b9"
		]

//begin script when window loads
window.onload = initialize();

//the first function called once the html is loaded
function initialize(){
	setMap();
};
//set choropleth map parameters
function setMap(){
	//map frame dimensions
	var width = 400;
	var height = 530;

/*	//title for page
	var title = d3.select("body")
		.append("h1")
		.text("2011 Rates for Labour Force Participation");
*/
	//create a new SVG element with the above dimensions
	var map = d3.select("body")
		.append("svg")
		.attr("width", width)
		.attr("height", height)
		.attr("class", "map");

	//draw the colour palette legend
	var colorPanelWidth = 20 // the width of each block of colour
	var xPosIni = 90 // leftmost edge of legend

	map.selectAll("rect")
		.data(colorPalette).enter()
		.append("rect")
		.attr("fill", function (color){ return color; })
		.attr("x", function (color, index){
			return xPosIni + (index * colorPanelWidth) + "px";
		})
		.attr("y", height-35)
		.attr("width", colorPanelWidth + "px")
		.attr("height", 10 + "px");

	map.selectAll("text")
		.data(["below average", "above average"]).enter()
		.append("text")
		.text(function (label){ return label})
		.attr("x", function (label, index){
			return xPosIni + (index * 147) + "px" //adjust rightmost label location
		})
		.attr("y", height-10)
		.attr("font-family", "sans-serif")
		.attr("font-size", "11px")
		.attr("fill", "grey");

	//Define map projection
	var projection = d3.geo.mercator()
		.scale([25000])
	  .center([-8.2, 53.25])
	  .translate([width / 2, height / 2]); //NEW

	//create svg path generator using the projection
	var path = d3.geo.path()
		.projection(projection);

	//use queue.js to parallelize asynchronous data loading
	queue()
		.defer(d3.csv, "assets/LFPR2011_Data.csv") //load csv data
		.defer(d3.json, "assets/ireland_mapping.topojson") //load geometry
		.await(callback); //trigger callback function once data loaded

	//retrieve and process data
	function callback(error, csvData, irelandData){
		var recolorMap = colorScale(csvData);

		//variables for csv to json data transfer
		//keyArray set as global variables
		var jsonRegions = irelandData.objects.ie_counties_redAtt.geometries;

		//loop through csv to assign each csv value to json region
		for (var i=0; i<csvData.length; i++) {
			var csvRegion = csvData[i]; //the current region
			var csvAdm1 = csvRegion.adm1_code; //adm1 code

			//loop through json regions to find right region
			for (var a=0; a<jsonRegions.length; a++){
				//where adm1 codes match, attach csv to json object
				if (jsonRegions[a].properties.GEOGID	== csvAdm1){

					// assign all five key/value pairs
					for (var key in keyArray){
						var attr = keyArray[key];
						var val = parseFloat(csvRegion[attr]);
						jsonRegions[a].properties[attr] = val;
					};

					jsonRegions[a].properties.name = csvRegion.name; //set prop
					break; //stop looking through the json regions
				};
			};
		};

		//add regions to map as enumeration units colored by data
		var regions = map.selectAll(".regions")
			.data(topojson.feature(irelandData,
				irelandData.objects.ie_counties_redAtt).features)
			.enter() //create elements
			.append("g") //give each province its own g element
			.attr("class", "regions") //assign class for additional styling
			.append("path")
			.attr("class", function(d) { return d.properties.GEOGID })
			.attr("d", path) //project data as geometry in svg
			.style("fill", function(d) { //color enumeration units
				return choropleth(d, recolorMap);
			})
			.on("mouseover", highlight)
			.on("mouseout", dehighlight)
			.on("mousemove", moveLabel)
			.append("desc") //append the current color
				.text(function(d) {
					return choropleth(d, recolorMap)
				});

		//create dropdown menu
		createDropdown(csvData);
	};
};

// from SO; ref. 3751520
function makeArray(count, content) {
   var result = [];
   if(typeof(content) == "function") {
      for(var i=0; i<count; i++) {
         result.push(content(i));
      }
   } else {
      for(var i=0; i<count; i++) {
         result.push(content);
      }
   }
   return result;
};

function createDropdown(csvData){
	//add a select element for the dropdown menu
	var dropdown = d3.select("body")
		.append("div")
		.attr("class","dropdown") //for positioning menu with css
		.html("
Gender:
")
		.append("select")
		.on("change", function(){
			changeAttribute(this.value, csvData);
		});

	//create each option element within the dropdown
	var keyNum = makeArray(keyArray.length, function(i) {
		return i++; });
	dropdown.selectAll("options")
		.data(keyNum)
		.enter()
		.append("option")
		.attr("value", function(d){ return keyArray[d] })
		.text(function(d) {
			d = keyLongArray[d];
			return d
		});
};

function colorScale(csvData){
	//create quantile classes with color scale
	var color = d3.scale.quantile() //designate quantile scale generator
		.range(colorPalette);

	//set min and max data values as domain
	color.domain([
		d3.min(csvData, function(d) { return Number(d[expressed]); }),
		d3.max(csvData, function(d) { return Number(d[expressed]); })
	]);

	return color;	 //return the color scale generator
};

function choropleth(d, recolorMap){
	//get data value
	var value = d.properties[expressed];
	//if value exists, assign it a color; otherwise assign gray
	if (value) {
		return recolorMap(value);
	} else {
		return "#ccc"; //default grey if no match
	};
};

function changeAttribute(attribute, csvData){
	//change the expressed attribute
	expressed = attribute;

	//recolor the map
	d3.selectAll(".regions") //select every region
		.select("path")
		.transition().duration(500).ease("linear")
		.style("fill", function(d) { //color enumeration units
			return choropleth(d, colorScale(csvData)); //->
		})
		.select("desc") //replace the color text in each desc element
			.text(function(d) {
				return choropleth(d, colorScale(csvData)); //->
			});
};

function highlight(data){

	var props = data.properties; //json properties

	d3.select("."+props.GEOGID) //select the current region in the DOM
		.transition().duration(150).ease("linear")
		.style("fill", "#000"); //set the enumeration unit fill to black

	var labelName = props.name; //html string for name (county)

	var labelAttribute = "
"+
		parseFloat(props[expressed]).toFixed(1)+ //display to 1 decimal
		"%" + "

"+
		labelName+""; //label content; value & variable

	//create info label div
	var infolabel = d3.select("body")
		.append("div")
		.attr("class", "infolabel") //for styling label
		.attr("id", props.GEOGID+"label") //for label div
		.html(labelAttribute); //add text
};

function dehighlight(data){

	//json or csv properties
	var props = data.properties; //json properties
	var region = d3.select("."+props.GEOGID); //select the current region
	var fillcolor = region.select("desc").text(); //access original color from desc
	region
	.transition().duration(300).ease("linear")
	.style("fill", fillcolor); //reset enumeration unit to orginal color

	d3.select("#"+props.GEOGID+"label").remove(); //remove info label
};

function moveLabel() {

	var x = d3.event.clientX+10; //horizontal label coordinate based mouse position stored in d3.event
	var y = d3.event.clientY-150; //vertical label coordinate
	d3.select(".infolabel") //select the label div for moving
		.style("margin-left", x+"px") //reposition label horizontal
		.style("margin-top", y+"px"); //reposition label vertical
};<|endoftext|>


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

/**
 * The 'PhET' menu, which appears in the bottom-right of the home screen and the navigation bar, with options like
 * "PhET Website", "Settings", etc.
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  // modules
  var platform = require( 'PHET_CORE/platform' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Shape = require( 'KITE/Shape' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Text = require( 'SCENERY/nodes/Text' );
  var inherit = require( 'PHET_CORE/inherit' );
  var AboutDialog = require( 'JOIST/AboutDialog' );
  var OptionsDialog = require( 'JOIST/OptionsDialog' );
  var SettingsDialog = require( 'JOIST/SettingsDialog' );
  var UpdateDialog = require( 'JOIST/UpdateDialog' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var ButtonListener = require( 'SCENERY/input/ButtonListener' );
  var Plane = require( 'SCENERY/nodes/Plane' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var FullScreen = require( 'JOIST/FullScreen' );
  var Brand = require( 'BRAND/Brand' );
  var FontAwesomeNode = require( 'SUN/FontAwesomeNode' );
  var ScreenshotGenerator = require( 'JOIST/ScreenshotGenerator' );
  var UpdateCheck = require( 'JOIST/UpdateCheck' );

  // strings
  var optionsString = require( 'string!JOIST/menuItem.options' );
  var aboutString = require( 'string!JOIST/menuItem.about' );
  var mailInputEventsLogString = require( 'string!JOIST/menuItem.mailInputEventsLog' );
  var outputInputEventsLogString = require( 'string!JOIST/menuItem.outputInputEventsLog' );
  var phetWebsiteString = require( 'string!JOIST/menuItem.phetWebsite' );
  var reportAProblemString = require( 'string!JOIST/menuItem.reportAProblem' );
  var screenshotString = require( 'string!JOIST/menuItem.screenshot' );
  var fullScreenString = require( 'string!JOIST/menuItem.fullscreen' );
  var settingsString = require( 'string!JOIST/menuItem.settings' );
  var getUpdateString = require( 'string!JOIST/menuItem.getUpdate' );
  var submitInputEventsLogString = require( 'string!JOIST/menuItem.submitInputEventsLog' );

  // constants
  var FONT_SIZE = 18;
  var HIGHLIGHT_COLOR = '#a6d2f4';

  // the checkmark used for toggle-able menu items
  var checkNode = new FontAwesomeNode( 'check_without_box', {
    fill: 'rgba(0,0,0,0.7)',
    scale: 0.4
  } );

  // Creates a menu item that highlights and fires.
  var createMenuItem = function( text, width, height, separatorBefore, closeCallback, callback, checkedProperty, options ) {
    options = _.extend( {
      tandem: null,
      color: '#000'
    }, options );
    // padding between the check and text
    var CHECK_PADDING = 2;
    // offset that includes the checkmark's width and its padding
    var CHECK_OFFSET = checkNode.width + CHECK_PADDING;

    var LEFT_X_MARGIN = 2;
    var RIGHT_X_MARGIN = 5;

    var Y_MARGIN = 3;
    var CORNER_RADIUS = 5;

    var textNode = new Text( text, { font: new PhetFont( FONT_SIZE ), fill: options.color } );
    var highlight = new Rectangle( 0, 0, width + LEFT_X_MARGIN + RIGHT_X_MARGIN + CHECK_OFFSET, height + Y_MARGIN + Y_MARGIN, CORNER_RADIUS, CORNER_RADIUS );

    var menuItem = new Node( {
      cursor: 'pointer',
      focusable: true,
      textDescription: text + ' Button'
    } );
    menuItem.addChild( highlight );
    menuItem.addChild( textNode );

    textNode.left = highlight.left + LEFT_X_MARGIN + CHECK_OFFSET; // text is left aligned
    textNode.centerY = highlight.centerY;

    menuItem.addInputListener( {
      enter: function() { highlight.fill = HIGHLIGHT_COLOR; },
      exit: function() { highlight.fill = null; }
    } );
    var fire = function( event ) {
      menuItem.trigger0( 'startedCallbacksForFired' );
      closeCallback( event );
      callback( event );
      menuItem.trigger0( 'endedCallbacksForFired' );
    };
    menuItem.addInputListener( new ButtonListener( {
      fire: fire
    } ) );

    menuItem.separatorBefore = separatorBefore;

    // if there is a check-mark property, add the check mark and hook up visibility changes
    var checkListener;
    if ( checkedProperty ) {
      var checkNodeHolder = new Node( {
        children: [ checkNode ],
        right: textNode.left - CHECK_PADDING,
        centerY: textNode.centerY
      } );
      checkListener = function( isChecked ) {
        checkNodeHolder.visible = isChecked;
      };
      checkedProperty.link( checkListener );
      menuItem.addChild( checkNodeHolder );
    }

    menuItem.dispose = function() {
      if ( checkedProperty ) {
        checkedProperty.unlink( checkListener );
      }
      options.tandem && options.tandem.removeInstance( menuItem );
    };

    options.tandem && options.tandem.addInstance( menuItem );

    return menuItem;
  };

  // Creates a comic-book style bubble.
  var createBubble = function( width, height ) {

    var rectangle = new Rectangle( 0, 0, width, height, 8, 8, { fill: 'white', lineWidth: 1, stroke: 'black' } );

    var tail = new Shape();
    tail.moveTo( width - 20, height - 2 );
    tail.lineToRelative( 0, 20 );
    tail.lineToRelative( -20, -20 );
    tail.close();

    var tailOutline = new Shape();
    tailOutline.moveTo( width - 20, height );
    tailOutline.lineToRelative( 0, 20 - 2 );
    tailOutline.lineToRelative( -18, -18 );

    var bubble = new Node();
    bubble.addChild( rectangle );
    bubble.addChild( new Path( tail, { fill: 'white' } ) );
    bubble.addChild( new Path( tailOutline, { stroke: 'black', lineWidth: 1 } ) );

    return bubble;
  };

  function PhetMenu( sim, options ) {

    //Only show certain features for PhET Sims, such as links to our website
    var isPhETBrand = Brand.id === 'phet';

    options = _.extend( {

      //For sims that have save/load enabled, show menu items for those.
      showSaveAndLoad: false,

      tandem: null

    }, options );

    var thisMenu = this;
    Node.call( thisMenu );

    /*
     * Description of the items in the menu. Each descriptor has these properties:
     * {string} text - the item's text
     * {boolean} present - whether the item should be added to the menu
     * {function} callback - called when the item fires
     */
    var itemDescriptors = [
      {
        text: optionsString,
        present: !!sim.options.optionsNode,
        callback: function() {
          new OptionsDialog( sim.options.optionsNode ).show();
        }
      },
      {
        text: phetWebsiteString,
        tandem: options.tandem && options.tandem.createTandem( 'phetWebsiteButton' ),
        present: isPhETBrand,
        callback: function() {
          var phetWindow = window.open( 'http://phet.colorado.edu', '_blank' );
          phetWindow.focus();
        }
      },
      {
        text: outputInputEventsLogString,
        present: !!sim.options.recordInputEventLog,
        callback: function() {
          // prints the recorded input event log to the console
          console.log( sim.getRecordedInputEventLogString() );
        }
      },
      {
        text: submitInputEventsLogString,
        present: !!sim.options.recordInputEventLog,
        callback: function() {
          // submits a recorded event log to the same-origin server (run scenery/tests/event-logs/server/server.js with Node, from the same directory)
          sim.submitEventLog();
        }
      },
      {
        text: mailInputEventsLogString,
        present: !!sim.options.recordInputEventLog,
        callback: function() {
          // mailto: link including the body to email
          sim.mailEventLog();
        }
      },
      {
        text: settingsString,
        present: false,
        callback: function() {
          var settingsDialog = new SettingsDialog( sim.showPointersProperty );

          // TODO: This is broken.  Should probably use new Dialog infrastruture
          var plane = new Plane( { fill: 'black', opacity: 0.3 } );
          sim.addChild( plane );
          sim.addChild( settingsDialog );
          settingsDialog.addDoneListener( function() {
            plane.detach();
            settingsDialog.detach();
          } );
        }
      },
      {
        text: reportAProblemString,
        present: isPhETBrand,
        callback: function() {
          // Create a smaller version of our dependencies to send, due to the URL length issues.
          // See https://github.com/phetsims/joist/issues/249.
          var dependenciesCopy = phet.chipper.dependencies ? JSON.parse( JSON.stringify( phet.chipper.dependencies ) ) : {};
          delete dependenciesCopy.comment;
          for ( var key in dependenciesCopy ) {
            if ( dependenciesCopy[ key ].sha ) {
              dependenciesCopy[ key ].sha = dependenciesCopy[ key ].sha.substring( 0, 8 );
            }
          }

          var url = 'http://phet.colorado.edu/files/troubleshooting/' +
                    '?sim=' + encodeURIComponent( sim.name ) +
                    '&version=' + encodeURIComponent( sim.version + ' ' +
                    ( phet.chipper.buildTimestamp ? phet.chipper.buildTimestamp : '(require.js)' ) ) +
                    '&url=' + encodeURIComponent( window.location.href ) +
                    '&dependencies=' + encodeURIComponent( JSON.stringify( dependenciesCopy ) );
          var reportWindow = window.open( url, '_blank' );
          reportWindow.focus();
        },
        tandem: options.tandem && options.tandem.createTandem( 'reportAProblemButton' )
      },
      {
        text: 'QR code',
        present: phet.chipper.getQueryParameter( 'qrCode' ),
        callback: function() {
          var win = window.open( 'http://api.qrserver.com/v1/create-qr-code/?data=' + encodeURIComponent( window.location.href ) + '&size=220x220&margin=0', '_blank' );
          win.focus();
        },
        tandem: options.tandem && options.tandem.createTandem( 'qrCode' )
      },
      {
        text: getUpdateString,
        present: UpdateCheck.areUpdatesChecked,
        color: UpdateCheck.state === 'out-of-date' ? '#0a0' : '#000',
        callback: function() {
          new UpdateDialog().show();
        },
        tandem: options.tandem && options.tandem.createTandem( 'getUpdate' )
      },

      //Feasibility test for capturing screen shots as images
      {
        text: screenshotString,
        present: phet.chipper.getQueryParameter( 'screenshot' ) && !platform.ie9, // TODO is this going to be implemented for IE9? see issue #212
        callback: function() {
          var dataURL = ScreenshotGenerator.generateScreenshot( sim );

          // if we have FileSaver support
          if ( window.Blob && !!new window.Blob() ) {
            // construct a blob out of it
            var requiredPrefix = 'data:image/png;base64,';
            assert && assert( dataURL.slice( 0, requiredPrefix.length ) === requiredPrefix );
            var dataBase64 = dataURL.slice( requiredPrefix.length );
            var byteChars = window.atob( dataBase64 );
            var byteArray = new window.Uint8Array( byteChars.length );
            for ( var i = 0; i < byteArray.length; i++ ) {
              byteArray[ i ] = byteChars.charCodeAt( i ); // need check to make sure this cast doesn't give problems?
            }

            var blob = new window.Blob( [ byteArray ], { type: 'image/png' } );

            // our preferred filename
            var filename = sim.name + ' screenshot.png';

            window.saveAs( blob, filename );
          }
          else {
            window.open( dataURL, '_blank', '' );
          }
        }
      },
      {
        text: fullScreenString,
        present: FullScreen.isFullScreenEnabled(),
        checkedProperty: FullScreen.isFullScreenProperty,
        callback: function() {
          FullScreen.toggleFullScreen( sim );
        },
        tandem: options.tandem && options.tandem.createTandem( 'fullScreenButton' )
      },

      //About dialog button
      {
        text: aboutString,
        present: true,
        separatorBefore: isPhETBrand,
        callback: function() {
          new AboutDialog( sim.name, sim.version, sim.credits, Brand, sim.locale ).show();
        },
        tandem: options.tandem && options.tandem.createTandem( 'aboutButton' )
      }
    ];

    // Menu items have uniform size, so compute the max text dimensions.
    var keepItemDescriptors = _.filter( itemDescriptors, function( itemDescriptor ) {return itemDescriptor.present;} );
    var textNodes = _.map( keepItemDescriptors, function( item ) {return new Text( item.text, { font: new PhetFont( FONT_SIZE ) } );} );
    var maxTextWidth = _.max( textNodes, function( node ) {return node.width;} ).width;
    var maxTextHeight = _.max( textNodes, function( node ) {return node.height;} ).height;

    // Create the menu items.
    var items = this.items = _.map( keepItemDescriptors, function( itemDescriptor ) {
      return createMenuItem(
        itemDescriptor.text,
        maxTextWidth,
        maxTextHeight,
        itemDescriptor.separatorBefore,
        options.closeCallback,
        itemDescriptor.callback,
        itemDescriptor.checkedProperty, {
          tandem: itemDescriptor.tandem,
          color: itemDescriptor.color
        } );
    } );
    var separatorWidth = _.max( items, function( item ) {return item.width;} ).width;
    var itemHeight = _.max( items, function( item ) {return item.height;} ).height;
    var content = new Node();
    var y = 0;
    var ySpacing = 2;
    var separator;
    _.each( items, function( item ) {
      if ( item.separatorBefore ) {
        y += ySpacing;
        separator = new Path( Shape.lineSegment( 0, y, separatorWidth, y ), { stroke: 'gray', lineWidth: 1 } );
        content.addChild( separator );
        y = y + separator.height + ySpacing;
      }
      item.top = y;
      content.addChild( item );
      y += itemHeight;
    } );

    // Create a comic-book-style bubble.
    var X_MARGIN = 5;
    var Y_MARGIN = 5;
    var bubble = createBubble( content.width + X_MARGIN + X_MARGIN, content.height + Y_MARGIN + Y_MARGIN );

    thisMenu.addChild( bubble );
    thisMenu.addChild( content );
    content.left = X_MARGIN;
    content.top = Y_MARGIN;

    thisMenu.mutate( _.omit( options, 'tandem' ) );
  }

  inherit( Node, PhetMenu, {
    show: function() {
      if ( !window.phet.joist.sim.isPoppedUp( this ) ) {
        window.phet.joist.sim.showPopup( this, true );
      }
    },

    hide: function() {
      if ( window.phet.joist.sim.isPoppedUp( this ) ) {
        this.dispose();
        window.phet.joist.sim.hidePopup( this, true );
      }
    },

    dispose: function() {
      _.each( this.items, function( item ) {
        item.dispose();
      } );
    }
  } );

  return PhetMenu;
} );
<commit_msg>#97 open PhET website to same locale as sim<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * The 'PhET' menu, which appears in the bottom-right of the home screen and the navigation bar, with options like
 * "PhET Website", "Settings", etc.
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  // modules
  var platform = require( 'PHET_CORE/platform' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Shape = require( 'KITE/Shape' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Text = require( 'SCENERY/nodes/Text' );
  var inherit = require( 'PHET_CORE/inherit' );
  var AboutDialog = require( 'JOIST/AboutDialog' );
  var OptionsDialog = require( 'JOIST/OptionsDialog' );
  var SettingsDialog = require( 'JOIST/SettingsDialog' );
  var UpdateDialog = require( 'JOIST/UpdateDialog' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var ButtonListener = require( 'SCENERY/input/ButtonListener' );
  var Plane = require( 'SCENERY/nodes/Plane' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var FullScreen = require( 'JOIST/FullScreen' );
  var Brand = require( 'BRAND/Brand' );
  var FontAwesomeNode = require( 'SUN/FontAwesomeNode' );
  var ScreenshotGenerator = require( 'JOIST/ScreenshotGenerator' );
  var UpdateCheck = require( 'JOIST/UpdateCheck' );

  // strings
  var optionsString = require( 'string!JOIST/menuItem.options' );
  var aboutString = require( 'string!JOIST/menuItem.about' );
  var mailInputEventsLogString = require( 'string!JOIST/menuItem.mailInputEventsLog' );
  var outputInputEventsLogString = require( 'string!JOIST/menuItem.outputInputEventsLog' );
  var phetWebsiteString = require( 'string!JOIST/menuItem.phetWebsite' );
  var reportAProblemString = require( 'string!JOIST/menuItem.reportAProblem' );
  var screenshotString = require( 'string!JOIST/menuItem.screenshot' );
  var fullScreenString = require( 'string!JOIST/menuItem.fullscreen' );
  var settingsString = require( 'string!JOIST/menuItem.settings' );
  var getUpdateString = require( 'string!JOIST/menuItem.getUpdate' );
  var submitInputEventsLogString = require( 'string!JOIST/menuItem.submitInputEventsLog' );

  // constants
  var FONT_SIZE = 18;
  var HIGHLIGHT_COLOR = '#a6d2f4';

  // the checkmark used for toggle-able menu items
  var checkNode = new FontAwesomeNode( 'check_without_box', {
    fill: 'rgba(0,0,0,0.7)',
    scale: 0.4
  } );

  // Creates a menu item that highlights and fires.
  var createMenuItem = function( text, width, height, separatorBefore, closeCallback, callback, checkedProperty, options ) {
    options = _.extend( {
      tandem: null,
      color: '#000'
    }, options );
    // padding between the check and text
    var CHECK_PADDING = 2;
    // offset that includes the checkmark's width and its padding
    var CHECK_OFFSET = checkNode.width + CHECK_PADDING;

    var LEFT_X_MARGIN = 2;
    var RIGHT_X_MARGIN = 5;

    var Y_MARGIN = 3;
    var CORNER_RADIUS = 5;

    var textNode = new Text( text, { font: new PhetFont( FONT_SIZE ), fill: options.color } );
    var highlight = new Rectangle( 0, 0, width + LEFT_X_MARGIN + RIGHT_X_MARGIN + CHECK_OFFSET, height + Y_MARGIN + Y_MARGIN, CORNER_RADIUS, CORNER_RADIUS );

    var menuItem = new Node( {
      cursor: 'pointer',
      focusable: true,
      textDescription: text + ' Button'
    } );
    menuItem.addChild( highlight );
    menuItem.addChild( textNode );

    textNode.left = highlight.left + LEFT_X_MARGIN + CHECK_OFFSET; // text is left aligned
    textNode.centerY = highlight.centerY;

    menuItem.addInputListener( {
      enter: function() { highlight.fill = HIGHLIGHT_COLOR; },
      exit: function() { highlight.fill = null; }
    } );
    var fire = function( event ) {
      menuItem.trigger0( 'startedCallbacksForFired' );
      closeCallback( event );
      callback( event );
      menuItem.trigger0( 'endedCallbacksForFired' );
    };
    menuItem.addInputListener( new ButtonListener( {
      fire: fire
    } ) );

    menuItem.separatorBefore = separatorBefore;

    // if there is a check-mark property, add the check mark and hook up visibility changes
    var checkListener;
    if ( checkedProperty ) {
      var checkNodeHolder = new Node( {
        children: [ checkNode ],
        right: textNode.left - CHECK_PADDING,
        centerY: textNode.centerY
      } );
      checkListener = function( isChecked ) {
        checkNodeHolder.visible = isChecked;
      };
      checkedProperty.link( checkListener );
      menuItem.addChild( checkNodeHolder );
    }

    menuItem.dispose = function() {
      if ( checkedProperty ) {
        checkedProperty.unlink( checkListener );
      }
      options.tandem && options.tandem.removeInstance( menuItem );
    };

    options.tandem && options.tandem.addInstance( menuItem );

    return menuItem;
  };

  // Creates a comic-book style bubble.
  var createBubble = function( width, height ) {

    var rectangle = new Rectangle( 0, 0, width, height, 8, 8, { fill: 'white', lineWidth: 1, stroke: 'black' } );

    var tail = new Shape();
    tail.moveTo( width - 20, height - 2 );
    tail.lineToRelative( 0, 20 );
    tail.lineToRelative( -20, -20 );
    tail.close();

    var tailOutline = new Shape();
    tailOutline.moveTo( width - 20, height );
    tailOutline.lineToRelative( 0, 20 - 2 );
    tailOutline.lineToRelative( -18, -18 );

    var bubble = new Node();
    bubble.addChild( rectangle );
    bubble.addChild( new Path( tail, { fill: 'white' } ) );
    bubble.addChild( new Path( tailOutline, { stroke: 'black', lineWidth: 1 } ) );

    return bubble;
  };

  function PhetMenu( sim, options ) {

    //Only show certain features for PhET Sims, such as links to our website
    var isPhETBrand = Brand.id === 'phet';

    options = _.extend( {

      //For sims that have save/load enabled, show menu items for those.
      showSaveAndLoad: false,

      tandem: null

    }, options );

    var thisMenu = this;
    Node.call( thisMenu );

    /*
     * Description of the items in the menu. Each descriptor has these properties:
     * {string} text - the item's text
     * {boolean} present - whether the item should be added to the menu
     * {function} callback - called when the item fires
     */
    var itemDescriptors = [
      {
        text: optionsString,
        present: !!sim.options.optionsNode,
        callback: function() {
          new OptionsDialog( sim.options.optionsNode ).show();
        }
      },
      {
        text: phetWebsiteString,
        tandem: options.tandem && options.tandem.createTandem( 'phetWebsiteButton' ),
        present: isPhETBrand,
        callback: function() {
          var phetWindow = window.open( 'http://phet.colorado.edu/' + sim.locale, '_blank' );
          phetWindow.focus();
        }
      },
      {
        text: outputInputEventsLogString,
        present: !!sim.options.recordInputEventLog,
        callback: function() {
          // prints the recorded input event log to the console
          console.log( sim.getRecordedInputEventLogString() );
        }
      },
      {
        text: submitInputEventsLogString,
        present: !!sim.options.recordInputEventLog,
        callback: function() {
          // submits a recorded event log to the same-origin server (run scenery/tests/event-logs/server/server.js with Node, from the same directory)
          sim.submitEventLog();
        }
      },
      {
        text: mailInputEventsLogString,
        present: !!sim.options.recordInputEventLog,
        callback: function() {
          // mailto: link including the body to email
          sim.mailEventLog();
        }
      },
      {
        text: settingsString,
        present: false,
        callback: function() {
          var settingsDialog = new SettingsDialog( sim.showPointersProperty );

          // TODO: This is broken.  Should probably use new Dialog infrastruture
          var plane = new Plane( { fill: 'black', opacity: 0.3 } );
          sim.addChild( plane );
          sim.addChild( settingsDialog );
          settingsDialog.addDoneListener( function() {
            plane.detach();
            settingsDialog.detach();
          } );
        }
      },
      {
        text: reportAProblemString,
        present: isPhETBrand,
        callback: function() {
          // Create a smaller version of our dependencies to send, due to the URL length issues.
          // See https://github.com/phetsims/joist/issues/249.
          var dependenciesCopy = phet.chipper.dependencies ? JSON.parse( JSON.stringify( phet.chipper.dependencies ) ) : {};
          delete dependenciesCopy.comment;
          for ( var key in dependenciesCopy ) {
            if ( dependenciesCopy[ key ].sha ) {
              dependenciesCopy[ key ].sha = dependenciesCopy[ key ].sha.substring( 0, 8 );
            }
          }

          var url = 'http://phet.colorado.edu/files/troubleshooting/' +
                    '?sim=' + encodeURIComponent( sim.name ) +
                    '&version=' + encodeURIComponent( sim.version + ' ' +
                    ( phet.chipper.buildTimestamp ? phet.chipper.buildTimestamp : '(require.js)' ) ) +
                    '&url=' + encodeURIComponent( window.location.href ) +
                    '&dependencies=' + encodeURIComponent( JSON.stringify( dependenciesCopy ) );
          var reportWindow = window.open( url, '_blank' );
          reportWindow.focus();
        },
        tandem: options.tandem && options.tandem.createTandem( 'reportAProblemButton' )
      },
      {
        text: 'QR code',
        present: phet.chipper.getQueryParameter( 'qrCode' ),
        callback: function() {
          var win = window.open( 'http://api.qrserver.com/v1/create-qr-code/?data=' + encodeURIComponent( window.location.href ) + '&size=220x220&margin=0', '_blank' );
          win.focus();
        },
        tandem: options.tandem && options.tandem.createTandem( 'qrCode' )
      },
      {
        text: getUpdateString,
        present: UpdateCheck.areUpdatesChecked,
        color: UpdateCheck.state === 'out-of-date' ? '#0a0' : '#000',
        callback: function() {
          new UpdateDialog().show();
        },
        tandem: options.tandem && options.tandem.createTandem( 'getUpdate' )
      },

      //Feasibility test for capturing screen shots as images
      {
        text: screenshotString,
        present: phet.chipper.getQueryParameter( 'screenshot' ) && !platform.ie9, // TODO is this going to be implemented for IE9? see issue #212
        callback: function() {
          var dataURL = ScreenshotGenerator.generateScreenshot( sim );

          // if we have FileSaver support
          if ( window.Blob && !!new window.Blob() ) {
            // construct a blob out of it
            var requiredPrefix = 'data:image/png;base64,';
            assert && assert( dataURL.slice( 0, requiredPrefix.length ) === requiredPrefix );
            var dataBase64 = dataURL.slice( requiredPrefix.length );
            var byteChars = window.atob( dataBase64 );
            var byteArray = new window.Uint8Array( byteChars.length );
            for ( var i = 0; i < byteArray.length; i++ ) {
              byteArray[ i ] = byteChars.charCodeAt( i ); // need check to make sure this cast doesn't give problems?
            }

            var blob = new window.Blob( [ byteArray ], { type: 'image/png' } );

            // our preferred filename
            var filename = sim.name + ' screenshot.png';

            window.saveAs( blob, filename );
          }
          else {
            window.open( dataURL, '_blank', '' );
          }
        }
      },
      {
        text: fullScreenString,
        present: FullScreen.isFullScreenEnabled(),
        checkedProperty: FullScreen.isFullScreenProperty,
        callback: function() {
          FullScreen.toggleFullScreen( sim );
        },
        tandem: options.tandem && options.tandem.createTandem( 'fullScreenButton' )
      },

      //About dialog button
      {
        text: aboutString,
        present: true,
        separatorBefore: isPhETBrand,
        callback: function() {
          new AboutDialog( sim.name, sim.version, sim.credits, Brand, sim.locale ).show();
        },
        tandem: options.tandem && options.tandem.createTandem( 'aboutButton' )
      }
    ];

    // Menu items have uniform size, so compute the max text dimensions.
    var keepItemDescriptors = _.filter( itemDescriptors, function( itemDescriptor ) {return itemDescriptor.present;} );
    var textNodes = _.map( keepItemDescriptors, function( item ) {return new Text( item.text, { font: new PhetFont( FONT_SIZE ) } );} );
    var maxTextWidth = _.max( textNodes, function( node ) {return node.width;} ).width;
    var maxTextHeight = _.max( textNodes, function( node ) {return node.height;} ).height;

    // Create the menu items.
    var items = this.items = _.map( keepItemDescriptors, function( itemDescriptor ) {
      return createMenuItem(
        itemDescriptor.text,
        maxTextWidth,
        maxTextHeight,
        itemDescriptor.separatorBefore,
        options.closeCallback,
        itemDescriptor.callback,
        itemDescriptor.checkedProperty, {
          tandem: itemDescriptor.tandem,
          color: itemDescriptor.color
        } );
    } );
    var separatorWidth = _.max( items, function( item ) {return item.width;} ).width;
    var itemHeight = _.max( items, function( item ) {return item.height;} ).height;
    var content = new Node();
    var y = 0;
    var ySpacing = 2;
    var separator;
    _.each( items, function( item ) {
      if ( item.separatorBefore ) {
        y += ySpacing;
        separator = new Path( Shape.lineSegment( 0, y, separatorWidth, y ), { stroke: 'gray', lineWidth: 1 } );
        content.addChild( separator );
        y = y + separator.height + ySpacing;
      }
      item.top = y;
      content.addChild( item );
      y += itemHeight;
    } );

    // Create a comic-book-style bubble.
    var X_MARGIN = 5;
    var Y_MARGIN = 5;
    var bubble = createBubble( content.width + X_MARGIN + X_MARGIN, content.height + Y_MARGIN + Y_MARGIN );

    thisMenu.addChild( bubble );
    thisMenu.addChild( content );
    content.left = X_MARGIN;
    content.top = Y_MARGIN;

    thisMenu.mutate( _.omit( options, 'tandem' ) );
  }

  inherit( Node, PhetMenu, {
    show: function() {
      if ( !window.phet.joist.sim.isPoppedUp( this ) ) {
        window.phet.joist.sim.showPopup( this, true );
      }
    },

    hide: function() {
      if ( window.phet.joist.sim.isPoppedUp( this ) ) {
        this.dispose();
        window.phet.joist.sim.hidePopup( this, true );
      }
    },

    dispose: function() {
      _.each( this.items, function( item ) {
        item.dispose();
      } );
    }
  } );

  return PhetMenu;
} );
<|endoftext|>


<commit_before>/*
Copyright 2013 Drifty Co.
http://drifty.com/

Ionic - an amazing HTML5 mobile app framework.
http://ionicframework.com/

By @maxlynch, @helloimben, @adamdbradley <3

Licensed under the MIT license. Please see LICENSE for more information.
*/
<commit_msg>Make awesome shit<commit_after>/*
Copyright 2013 Drifty Co.
http://drifty.com/

Ionic - an amazing HTML5 mobile app framework.
http://ionicframework.com/

By @maxlynch, @helloimben, @adamdbradley <3

Licensed under the MIT license. Please see LICENSE for more information.

Make awesome shit.
*/
<|endoftext|>


<commit_before>define(['angular', 'ngRoute'], function(angular) {
    var app = angular.module('app', ['ngRoute']);

    var amdRoute = function(config) {
            var controllerUrl = config.controllerUrl,
                controller = config.controller,
                resolve = config.resolve || {};

            if (controller === void 0) {
                controller = ['$scope', '$injector', 'amdController', function($scope, $injector, amdController) {
                    if (typeof amdController !== 'undefined') {
                        $injector.invoke(amdController, this, {'$scope' : $scope});
                    }
                }];

                config.controller = controller;
            }

            if (controllerUrl) {
                delete config.controllerUrl;

                resolve['amdController'] = ['$q', '$rootScope', function($q, $rootScope) {
                    var defer = $q.defer();

                    require([controllerUrl], function (amdController) {
                        defer.resolve(amdController);
                        $rootScope.$apply();
                    });

                    return defer.promise;
                }];

                config.resolve = resolve;
            }

            return config;
        },

        init = function() {
            angular.resumeBootstrap(['app']);
        };

    app.config([
        '$routeProvider',
        '$controllerProvider',
        '$compileProvider',
        '$filterProvider',
        '$provide',
        '$injector',
    function($routeProvider, $controllerProvider, $compileProvider, $filterProvider, $provide, $injector) {
        angular.extend(app, {
            when : function(path, config) {
                $routeProvider.when(path, amdRoute(config));
                return this;
            },

            otherwise : function(config) {
                $routeProvider.otherwise(amdRoute(config));
                return this;
            },

            provider : function(name, constructor) {
                $provide.provider(name, constructor);
                return this;
            },

            controller : function(name, constructor) {
                $controllerProvider.register(name, constructor);
                return this;
            },

            directive : function(name, constructor) {
                $compileProvider.directive(name, constructor);
                return this;
            },

            filter : function(name, constructor) {
                $filterProvider.register(name, constructor);
                return this;
            },

            factory : function(name, constructor) {
                $provide.factory(name, constructor);
                return this;
            },

            service : function(name, constructor) {
                $provide.service(name, constructor);
                return this;
            },

            constant : function(name, constructor) {
                $provide.constant(name, constructor);
                return this;
            },

            value : function(name, constructor) {
                $provide.value(name, constructor);
                return this;
            }
        });
    }]);

    app.config(function() {
        app.when('/', {
            templateUrl   : './tmpl/index.html',
            controllerUrl : 'controller/mainController'
        })
        .when('/list', {
            templateUrl   : './tmpl/list.html',
            controllerUrl : 'controller/listController'
        })
        .otherwise({
            redirectTo : '/'
        });
    });

    init(/* app bootstarp */);

    return app;
});<commit_msg>去掉typeof undefined<commit_after>define(['angular', 'ngRoute'], function(angular) {
    var app = angular.module('app', ['ngRoute']);

    var amdRoute = function(config) {
            var controllerUrl = config.controllerUrl,
                controller = config.controller,
                resolve = config.resolve || {};

            if (controller === void 0) {
                controller = ['$scope', '$injector', 'amdController', function($scope, $injector, amdController) {
                    if (amdController !== void 0) {
                        $injector.invoke(amdController, this, {'$scope' : $scope});
                    }
                }];

                config.controller = controller;
            }

            if (controllerUrl) {
                delete config.controllerUrl;

                resolve['amdController'] = ['$q', '$rootScope', function($q, $rootScope) {
                    var defer = $q.defer();

                    require([controllerUrl], function (amdController) {
                        defer.resolve(amdController);
                        $rootScope.$apply();
                    });

                    return defer.promise;
                }];

                config.resolve = resolve;
            }

            return config;
        },

        init = function() {
            angular.resumeBootstrap(['app']);
        };

    app.config([
        '$routeProvider',
        '$controllerProvider',
        '$compileProvider',
        '$filterProvider',
        '$provide',
        '$injector',
    function($routeProvider, $controllerProvider, $compileProvider, $filterProvider, $provide, $injector) {
        angular.extend(app, {
            when : function(path, config) {
                $routeProvider.when(path, amdRoute(config));
                return this;
            },

            otherwise : function(config) {
                $routeProvider.otherwise(amdRoute(config));
                return this;
            },

            provider : function(name, constructor) {
                $provide.provider(name, constructor);
                return this;
            },

            controller : function(name, constructor) {
                $controllerProvider.register(name, constructor);
                return this;
            },

            directive : function(name, constructor) {
                $compileProvider.directive(name, constructor);
                return this;
            },

            filter : function(name, constructor) {
                $filterProvider.register(name, constructor);
                return this;
            },

            factory : function(name, constructor) {
                $provide.factory(name, constructor);
                return this;
            },

            service : function(name, constructor) {
                $provide.service(name, constructor);
                return this;
            },

            constant : function(name, constructor) {
                $provide.constant(name, constructor);
                return this;
            },

            value : function(name, constructor) {
                $provide.value(name, constructor);
                return this;
            }
        });
    }]);

    app.config(function() {
        app.when('/', {
            templateUrl   : './tmpl/index.html',
            controllerUrl : 'controller/mainController'
        })
        .when('/list', {
            templateUrl   : './tmpl/list.html',
            controllerUrl : 'controller/listController'
        })
        .otherwise({
            redirectTo : '/'
        });
    });

    init(/* app bootstarp */);

    return app;
});<|endoftext|>