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

/**
 * Creates locale-specific HTML files by populating a template.
 *
 * This is one step in the 'after-requirejs-build' task.
 * See afterRequirejsBuild.js for documentation on how this step fits into that asynchronous task.
 */
/* eslint-env node */
'use strict';

// built-in node APIs
var assert = require( 'assert' );

// third-party node APIs
var nodeHTMLEncoder = require( 'node-html-encoder' ); // eslint-disable-line require-statement-match

// constants
var Encoder = nodeHTMLEncoder.Encoder;

// modules
var loadFileAsDataURI = require( '../../../chipper/js/common/loadFileAsDataURI' );
var getStringMap = require( '../../../chipper/js/grunt/getStringMap' );
var getThirdPartyLibEntries = require( '../../../chipper/js/grunt/getThirdPartyLibEntries' );
var ChipperConstants = require( '../../../chipper/js/common/ChipperConstants' );
var ChipperStringUtils = require( '../../../chipper/js/common/ChipperStringUtils' );

/**
 * @param grunt - the grunt instance
 * @param buildConfig - see getBuildConfig.js
 * @param {string} dependencies - dependencies information, including shas and branches for repositories
 * @param mipmapsJavaScript - script for mipmaps
 * @param {function} callback - called when this step is completed
 */
module.exports = function( grunt, buildConfig, dependencies, mipmapsJavaScript, callback ) {

  // TODO: chipper#101 eek, this is scary! we are importing from the node_modules dir. ideally we should just have uglify-js installed once in sherpa?
  var uglify = require( '../../../chipper/node_modules/uglify-js' );// eslint-disable-line require-statement-match

  // stringMap[locale][stringKey] give us a string
  var stringMap = getStringMap( grunt, buildConfig );

  // HTML encoder
  var encoder = new Encoder( 'entity' );

  // Get the title and version to display in the HTML header.
  // The HTML header is not internationalized, so order can just be hard coded here, see #156
  var simTitle = stringMap[ ChipperConstants.FALLBACK_LOCALE ][ buildConfig.simTitleStringKey ];
  assert( simTitle, 'missing entry for sim title, key = ' + buildConfig.simTitleStringKey );

  // Select the HTML comment header based on the brand, see https://github.com/phetsims/chipper/issues/156
  var htmlHeader = null;
  if ( buildConfig.brand === 'phet-io' ) {

    // License text provided by @kathy-phet in https://github.com/phetsims/chipper/issues/148#issuecomment-112584773
    htmlHeader = simTitle + ' ' + buildConfig.version + '\n' +
                 'Copyright 2002-' + grunt.template.today( 'yyyy' ) + ', Regents of the University of Colorado\n' +
                 'PhET Interactive Simulations, University of Colorado Boulder\n' +
                 '\n' +
                 'This Interoperable PhET Simulation file requires a license.\n' +
                 'USE WITHOUT A LICENSE AGREEMENT IS STRICTLY PROHIBITED.\n' +
                 'Contact phethelp@colorado.edu regarding licensing.\n' +
                 'https://phet.colorado.edu/en/licensing';
  }
  else {
    htmlHeader = simTitle + ' ' + buildConfig.version + '\n' +
                 'Copyright 2002-' + grunt.template.today( 'yyyy' ) + ', Regents of the University of Colorado\n' +
                 'PhET Interactive Simulations, University of Colorado Boulder\n' +
                 '\n' +
                 'This file is licensed under Creative Commons Attribution 4.0\n' +
                 'For alternate source code licensing, see https://github.com/phetsims\n' +
                 'For licenses for third-party software used by this simulation, see below\n' +
                 'For more information, see https://phet.colorado.edu/en/licensing/html\n' +
                 '\n' +
                 'The PhET name and PhET logo are registered trademarks of The Regents of the\n' +
                 'University of Colorado. Permission is granted to use the PhET name and PhET logo\n' +
                 'only for attribution purposes. Use of the PhET name and/or PhET logo for promotional,\n' +
                 'marketing, or advertising purposes requires a separate license agreement from the\n' +
                 'University of Colorado. Contact phethelp@colorado.edu regarding licensing.';
  }

  // All html files share the same build timestamp
  var timestamp = new Date().toISOString().split( 'T' ).join( ' ' );
  timestamp = timestamp.substring( 0, timestamp.indexOf( '.' ) ) + ' UTC';

  // Directory on the PhET website where the latest version of the sim lives
  var latestDir = 'https://phet.colorado.edu/sims/html/' + buildConfig.name + '/latest/';

  // Load the splash SVG from the appropriate brand.
  var splashDataURI = loadFileAsDataURI( '../brand/' + buildConfig.brand + '/images/splash.svg' );

  // Minify scripts that will be preloaded in the HMTL file.
  grunt.log.debug( 'Minifying preload scripts' );
  var preloadScripts = [];
  for ( var libIdx = 0; libIdx < buildConfig.preload.length; libIdx++ ) {
    var lib = buildConfig.preload[ libIdx ];

    var processedCode;
    if ( grunt.option( 'uglify' ) === false ) {
      processedCode = global.phet.chipper.fs.readFileSync( lib, 'utf8' );
    }
    else {
      processedCode = uglify.minify( [ lib ], {
        mangle: grunt.option( 'mangle' ) !== false,
        output: {
          inline_script: true, // escape </script
          beautify: grunt.option( 'mangle' ) === false
        },
        compress: {
          global_defs: {}
        }
      } ).code;
    }

    // workaround for Uglify2's Unicode unescaping. see https://github.com/phetsims/chipper/issues/70
    preloadScripts.push( processedCode.replace( '\x0B', '\\x0B' ) );
  }

  // Load the optimized code for the sim.
  var mainInlineJavascript = grunt.file.read( 'build/' + buildConfig.name + '.min.js' );

  // workaround for Uglify2's Unicode unescaping. see https://github.com/phetsims/chipper/issues/70
  mainInlineJavascript = mainInlineJavascript.replace( '\x0B', '\\x0B' );

  // License entries for third-party media files that were loaded by media plugins.
  // The media plugins populate global.phet.chipper.licenseEntries.
  var thirdPartyEntries = {
    lib: getThirdPartyLibEntries( grunt, buildConfig )
  };
  if ( global.phet.chipper.licenseEntries ) {
    for ( var mediaType in global.phet.chipper.licenseEntries ) {
      if ( global.phet.chipper.licenseEntries.hasOwnProperty( mediaType ) ) {

        var mediaEntry = global.phet.chipper.licenseEntries[ mediaType ];

        // For each resource of that type
        for ( var resourceName in mediaEntry ) {
          if ( mediaEntry.hasOwnProperty( resourceName ) ) {

            var licenseEntry = mediaEntry[ resourceName ];

            // If it is not from PhET, it is from a 3rd party and we must include it in the report
            // But lift this restriction when building a non-phet brand
            if ( !licenseEntry ) {

              // Fail if there is no license entry.  Though this error should have been caught
              if ( buildConfig.brand === 'phet' || buildConfig.brand === 'phet-io' ) {
                // during plugin loading, so this is a "double check"
                grunt.log.error( 'No license.json entry for ' + resourceName );
              }
            }
            else if ( licenseEntry.projectURL !== 'https://phet.colorado.edu' ) {
              thirdPartyEntries[ mediaType ] = thirdPartyEntries[ mediaType ] || {};
              thirdPartyEntries[ mediaType ][ resourceName ] = licenseEntry;
            }
          }
        }
      }
    }
  }

  grunt.log.debug( 'Constructing HTML from template' );
  var html = grunt.file.read( '../chipper/templates/sim.html' );

  function replaceConstants( string, insertScripts ) {
    // Strip out carriage returns (if building on Windows), then add in our own after the MOTW (Mark Of The Web).
    // See https://github.com/phetsims/joist/issues/164 and
    // https://msdn.microsoft.com/en-us/library/ms537628%28v=vs.85%29.aspx
    string = string.replace( /\r/g, '' );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_CARRIAGE_RETURN}}', '\r' );

    // Replace tokens in the template that are independent of locale
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_HTML_HEADER}}', htmlHeader );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_MIPMAPS_JAVASCRIPT}}', mipmapsJavaScript );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_SPLASH_DATA_URI}}', splashDataURI );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_PRELOAD_JAVASCRIPT}}', preloadScripts.map( function( script ) {
      if ( insertScripts ) {
        return '<script type="text/javascript">\n' + script + '\n</script>\n';
      }
      else {
        return script + '\n';
      }
    } ).join( '\n' ) );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_MAIN_JAVASCRIPT}}', mainInlineJavascript );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_START_THIRD_PARTY_LICENSE_ENTRIES}}', ChipperConstants.START_THIRD_PARTY_LICENSE_ENTRIES );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_END_THIRD_PARTY_LICENSE_ENTRIES}}', ChipperConstants.END_THIRD_PARTY_LICENSE_ENTRIES );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_DEPENDENCIES}}', dependencies );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_PROJECT}}', buildConfig.name );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_VERSION}}', buildConfig.version );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_BUILD_TIMESTAMP}}', timestamp );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_BRAND}}', buildConfig.brand );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_THIRD_PARTY_LICENSE_ENTRIES}}', JSON.stringify( thirdPartyEntries, null, 2 ) );

    return string;
  }

  html = replaceConstants( html, true );

  function replaceLocaleConstants( string, locale, includeAllLocales ) {

    var localizedTitle = stringMap[ locale ][ buildConfig.simTitleStringKey ];

    var stringObject = stringMap;
    if ( !includeAllLocales ) {
      stringObject = {};
      stringObject[ locale ] = stringMap[ locale ];
    }

    string = ChipperStringUtils.replaceFirst( string, '{{PHET_STRINGS}}', JSON.stringify( stringObject, null, '' ) );

    //TODO: if locale is being made available for changing layout, we'll need it in requirejs mode
    // Make the locale accessible at runtime (e.g., for changing layout based on RTL languages), see #40
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_LOCALE}}', locale );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_SIM_TITLE}}', encoder.htmlEncode( localizedTitle ) );

    // metadata for Open Graph protocol, see phet-edmodo#2
    string = ChipperStringUtils.replaceFirst( string, '{{OG_TITLE}}', encoder.htmlEncode( localizedTitle ) );
    string = ChipperStringUtils.replaceFirst( string, '{{OG_URL}}', latestDir + buildConfig.name + '_' + locale + '.html' );
    string = ChipperStringUtils.replaceFirst( string, '{{OG_IMAGE}}', latestDir + buildConfig.name + '-600.png' );

    return string;
  }

  /**
   * This function makes sure that the built file looks as expected. It checks that there aren't any template strings
   * still in the build file, and that the first copyright is the correct year.
   * @param {String} html
   */
  function sanityCheckBuiltFile( html ) {

    var copyrightIndex = html.indexOf( 'Copyright 2002-20' );
    var endYear = html.substring( copyrightIndex + 15, copyrightIndex + 19 );

    // Make sure that the current year matches the ending
    if ( new Date().getYear() + 1900 !== parseInt( endYear, 10 ) ) {
      grunt.fail.warn( 'Copyright statement must be current through this year' );
    }

    // Match template strings that look like "{{I_AM_A_TEMPLATE}}". Hyphens and periods also allowed.
    var templateMatches = html.match( /{{[A-z\-._ ]{0,100}}}/g );
    if ( templateMatches.length >= 0 ) {

      if ( templateMatches.length === 1 && templateMatches[ 0 ] === '{{year}}' ) {

        // This is an edge case for the year template string that is in brand and packaged with the built sim.
      }
      else {
        grunt.fail.warn( 'Template strings detected in built file:  ' + templateMatches );
      }
    }
    grunt.file.write( 'build/testHTML.html', html );
  }

  // Create locale-specific HTML files
  for ( var i = 0; i < buildConfig.locales.length; i++ ) {
    var locale = buildConfig.locales[ i ];

    var localeHTML = replaceLocaleConstants( html, locale, false );

    sanityCheckBuiltFile( localeHTML );

    // phet-io simulations end in "phetio", see https://github.com/phetsims/phet-io/issues/288
    var filename = 'build/' + buildConfig.name + '_' + locale;
    if ( buildConfig.brand === 'phet-io' ) {
      filename = filename + '-phetio';
    }
    filename = filename + '.html';

    // Write the single-file built simulation file
    grunt.file.write( filename, localeHTML );
  }

  // Create an _all.html file
  if ( grunt.option( 'allHTML' ) ) {
    assert( buildConfig.brand === 'phet', 'allHTML option only supported with the \'phet\' brand' );
    grunt.file.write( 'build/' + buildConfig.name + '_all.html', replaceLocaleConstants( html, ChipperConstants.FALLBACK_LOCALE, true ) );
  }

  // Create a file for testing iframe embedding.  English (en) is assumed as the locale.
  // phet-io sims should be tested in more powerful wrappers instead of the iframe test.
  if ( buildConfig.locales.indexOf( ChipperConstants.FALLBACK_LOCALE ) !== -1 && buildConfig.brand !== 'phet-io' ) {
    grunt.log.debug( 'Constructing HTML for iframe testing from template' );
    var iframeTestHtml = grunt.file.read( '../chipper/templates/sim-iframe.html' );
    iframeTestHtml = ChipperStringUtils.replaceFirst( iframeTestHtml, '{{PHET_SIM_TITLE}}', encoder.htmlEncode( simTitle + ' iframe test' ) );
    iframeTestHtml = ChipperStringUtils.replaceFirst( iframeTestHtml, '{{PHET_SIM_URL}}', buildConfig.name + '_' + ChipperConstants.FALLBACK_LOCALE + '.html' );
    grunt.file.write( 'build/' + buildConfig.name + '_' + ChipperConstants.FALLBACK_LOCALE + '-iframe' + '.html', iframeTestHtml );
  }

  grunt.log.debug( 'Cleaning temporary files' );
  grunt.file.delete( 'build/' + buildConfig.name + '.min.js' );

  callback();
};
<commit_msg>sanity check built file, https://github.com/phetsims/chipper/issues/575<commit_after>// Copyright 2015, University of Colorado Boulder

/**
 * Creates locale-specific HTML files by populating a template.
 *
 * This is one step in the 'after-requirejs-build' task.
 * See afterRequirejsBuild.js for documentation on how this step fits into that asynchronous task.
 */
/* eslint-env node */
'use strict';

// built-in node APIs
var assert = require( 'assert' );

// third-party node APIs
var nodeHTMLEncoder = require( 'node-html-encoder' ); // eslint-disable-line require-statement-match

// constants
var Encoder = nodeHTMLEncoder.Encoder;

// modules
var loadFileAsDataURI = require( '../../../chipper/js/common/loadFileAsDataURI' );
var getStringMap = require( '../../../chipper/js/grunt/getStringMap' );
var getThirdPartyLibEntries = require( '../../../chipper/js/grunt/getThirdPartyLibEntries' );
var ChipperConstants = require( '../../../chipper/js/common/ChipperConstants' );
var ChipperStringUtils = require( '../../../chipper/js/common/ChipperStringUtils' );

/**
 * @param grunt - the grunt instance
 * @param buildConfig - see getBuildConfig.js
 * @param {string} dependencies - dependencies information, including shas and branches for repositories
 * @param mipmapsJavaScript - script for mipmaps
 * @param {function} callback - called when this step is completed
 */
module.exports = function( grunt, buildConfig, dependencies, mipmapsJavaScript, callback ) {

  // TODO: chipper#101 eek, this is scary! we are importing from the node_modules dir. ideally we should just have uglify-js installed once in sherpa?
  var uglify = require( '../../../chipper/node_modules/uglify-js' );// eslint-disable-line require-statement-match

  // stringMap[locale][stringKey] give us a string
  var stringMap = getStringMap( grunt, buildConfig );

  // HTML encoder
  var encoder = new Encoder( 'entity' );

  // Get the title and version to display in the HTML header.
  // The HTML header is not internationalized, so order can just be hard coded here, see #156
  var simTitle = stringMap[ ChipperConstants.FALLBACK_LOCALE ][ buildConfig.simTitleStringKey ];
  assert( simTitle, 'missing entry for sim title, key = ' + buildConfig.simTitleStringKey );

  // Select the HTML comment header based on the brand, see https://github.com/phetsims/chipper/issues/156
  var htmlHeader = null;
  if ( buildConfig.brand === 'phet-io' ) {

    // License text provided by @kathy-phet in https://github.com/phetsims/chipper/issues/148#issuecomment-112584773
    htmlHeader = simTitle + ' ' + buildConfig.version + '\n' +
                 'Copyright 2002-' + grunt.template.today( 'yyyy' ) + ', Regents of the University of Colorado\n' +
                 'PhET Interactive Simulations, University of Colorado Boulder\n' +
                 '\n' +
                 'This Interoperable PhET Simulation file requires a license.\n' +
                 'USE WITHOUT A LICENSE AGREEMENT IS STRICTLY PROHIBITED.\n' +
                 'Contact phethelp@colorado.edu regarding licensing.\n' +
                 'https://phet.colorado.edu/en/licensing';
  }
  else {
    htmlHeader = simTitle + ' ' + buildConfig.version + '\n' +
                 'Copyright 2002-' + grunt.template.today( 'yyyy' ) + ', Regents of the University of Colorado\n' +
                 'PhET Interactive Simulations, University of Colorado Boulder\n' +
                 '\n' +
                 'This file is licensed under Creative Commons Attribution 4.0\n' +
                 'For alternate source code licensing, see https://github.com/phetsims\n' +
                 'For licenses for third-party software used by this simulation, see below\n' +
                 'For more information, see https://phet.colorado.edu/en/licensing/html\n' +
                 '\n' +
                 'The PhET name and PhET logo are registered trademarks of The Regents of the\n' +
                 'University of Colorado. Permission is granted to use the PhET name and PhET logo\n' +
                 'only for attribution purposes. Use of the PhET name and/or PhET logo for promotional,\n' +
                 'marketing, or advertising purposes requires a separate license agreement from the\n' +
                 'University of Colorado. Contact phethelp@colorado.edu regarding licensing.';
  }

  // All html files share the same build timestamp
  var timestamp = new Date().toISOString().split( 'T' ).join( ' ' );
  timestamp = timestamp.substring( 0, timestamp.indexOf( '.' ) ) + ' UTC';

  // Directory on the PhET website where the latest version of the sim lives
  var latestDir = 'https://phet.colorado.edu/sims/html/' + buildConfig.name + '/latest/';

  // Load the splash SVG from the appropriate brand.
  var splashDataURI = loadFileAsDataURI( '../brand/' + buildConfig.brand + '/images/splash.svg' );

  // Minify scripts that will be preloaded in the HMTL file.
  grunt.log.debug( 'Minifying preload scripts' );
  var preloadScripts = [];
  for ( var libIdx = 0; libIdx < buildConfig.preload.length; libIdx++ ) {
    var lib = buildConfig.preload[ libIdx ];

    var processedCode;
    if ( grunt.option( 'uglify' ) === false ) {
      processedCode = global.phet.chipper.fs.readFileSync( lib, 'utf8' );
    }
    else {
      processedCode = uglify.minify( [ lib ], {
        mangle: grunt.option( 'mangle' ) !== false,
        output: {
          inline_script: true, // escape </script
          beautify: grunt.option( 'mangle' ) === false
        },
        compress: {
          global_defs: {}
        }
      } ).code;
    }

    // workaround for Uglify2's Unicode unescaping. see https://github.com/phetsims/chipper/issues/70
    preloadScripts.push( processedCode.replace( '\x0B', '\\x0B' ) );
  }

  // Load the optimized code for the sim.
  var mainInlineJavascript = grunt.file.read( 'build/' + buildConfig.name + '.min.js' );

  // workaround for Uglify2's Unicode unescaping. see https://github.com/phetsims/chipper/issues/70
  mainInlineJavascript = mainInlineJavascript.replace( '\x0B', '\\x0B' );

  // License entries for third-party media files that were loaded by media plugins.
  // The media plugins populate global.phet.chipper.licenseEntries.
  var thirdPartyEntries = {
    lib: getThirdPartyLibEntries( grunt, buildConfig )
  };
  if ( global.phet.chipper.licenseEntries ) {
    for ( var mediaType in global.phet.chipper.licenseEntries ) {
      if ( global.phet.chipper.licenseEntries.hasOwnProperty( mediaType ) ) {

        var mediaEntry = global.phet.chipper.licenseEntries[ mediaType ];

        // For each resource of that type
        for ( var resourceName in mediaEntry ) {
          if ( mediaEntry.hasOwnProperty( resourceName ) ) {

            var licenseEntry = mediaEntry[ resourceName ];

            // If it is not from PhET, it is from a 3rd party and we must include it in the report
            // But lift this restriction when building a non-phet brand
            if ( !licenseEntry ) {

              // Fail if there is no license entry.  Though this error should have been caught
              if ( buildConfig.brand === 'phet' || buildConfig.brand === 'phet-io' ) {
                // during plugin loading, so this is a "double check"
                grunt.log.error( 'No license.json entry for ' + resourceName );
              }
            }
            else if ( licenseEntry.projectURL !== 'https://phet.colorado.edu' ) {
              thirdPartyEntries[ mediaType ] = thirdPartyEntries[ mediaType ] || {};
              thirdPartyEntries[ mediaType ][ resourceName ] = licenseEntry;
            }
          }
        }
      }
    }
  }

  grunt.log.debug( 'Constructing HTML from template' );
  var htmlTemplate = grunt.file.read( '../chipper/templates/sim.html' );

  function replaceConstants( string, insertScripts ) {
    // Strip out carriage returns (if building on Windows), then add in our own after the MOTW (Mark Of The Web).
    // See https://github.com/phetsims/joist/issues/164 and
    // https://msdn.microsoft.com/en-us/library/ms537628%28v=vs.85%29.aspx
    string = string.replace( /\r/g, '' );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_CARRIAGE_RETURN}}', '\r' );

    // Replace tokens in the template that are independent of locale
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_HTML_HEADER}}', htmlHeader );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_MIPMAPS_JAVASCRIPT}}', mipmapsJavaScript );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_SPLASH_DATA_URI}}', splashDataURI );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_PRELOAD_JAVASCRIPT}}', preloadScripts.map( function( script ) {
      if ( insertScripts ) {
        return '<script type="text/javascript">\n' + script + '\n</script>\n';
      }
      else {
        return script + '\n';
      }
    } ).join( '\n' ) );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_MAIN_JAVASCRIPT}}', mainInlineJavascript );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_START_THIRD_PARTY_LICENSE_ENTRIES}}', ChipperConstants.START_THIRD_PARTY_LICENSE_ENTRIES );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_END_THIRD_PARTY_LICENSE_ENTRIES}}', ChipperConstants.END_THIRD_PARTY_LICENSE_ENTRIES );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_DEPENDENCIES}}', dependencies );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_PROJECT}}', buildConfig.name );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_VERSION}}', buildConfig.version );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_BUILD_TIMESTAMP}}', timestamp );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_BRAND}}', buildConfig.brand );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_THIRD_PARTY_LICENSE_ENTRIES}}', JSON.stringify( thirdPartyEntries, null, 2 ) );

    return string;
  }

  var html = replaceConstants( htmlTemplate, true );

  function replaceLocaleConstants( string, locale, includeAllLocales ) {

    var localizedTitle = stringMap[ locale ][ buildConfig.simTitleStringKey ];

    var stringObject = stringMap;
    if ( !includeAllLocales ) {
      stringObject = {};
      stringObject[ locale ] = stringMap[ locale ];
    }

    string = ChipperStringUtils.replaceFirst( string, '{{PHET_STRINGS}}', JSON.stringify( stringObject, null, '' ) );

    //TODO: if locale is being made available for changing layout, we'll need it in requirejs mode
    // Make the locale accessible at runtime (e.g., for changing layout based on RTL languages), see #40
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_LOCALE}}', locale );
    string = ChipperStringUtils.replaceFirst( string, '{{PHET_SIM_TITLE}}', encoder.htmlEncode( localizedTitle ) );

    // metadata for Open Graph protocol, see phet-edmodo#2
    string = ChipperStringUtils.replaceFirst( string, '{{OG_TITLE}}', encoder.htmlEncode( localizedTitle ) );
    string = ChipperStringUtils.replaceFirst( string, '{{OG_URL}}', latestDir + buildConfig.name + '_' + locale + '.html' );
    string = ChipperStringUtils.replaceFirst( string, '{{OG_IMAGE}}', latestDir + buildConfig.name + '-600.png' );

    return string;
  }

  /**
   * This function makes sure that the built file looks as expected. It checks that there aren't any template strings
   * still in the build file.
   * @param {String} html
   */
  function sanityCheckBuiltFile( html ) {

    // Match template strings that look like "{{I_AM-A.TEMPLATE}}".
    var templateHTMLTemplateStrings = htmlTemplate.match( /{{[A-z\-._ ]{1,100}}}/g );

    if ( templateHTMLTemplateStrings ) {
      templateHTMLTemplateStrings.forEach( function( templateString ) {
        if ( html.indexOf( templateString ) >= 0 ) {
          grunt.fail.warn( 'Template string detected in built file:  ' + templateString );
        }
      } );
    }
  }

  // Create locale-specific HTML files
  for ( var i = 0; i < buildConfig.locales.length; i++ ) {
    var locale = buildConfig.locales[ i ];

    var localeHTML = replaceLocaleConstants( html, locale, false );

    sanityCheckBuiltFile( localeHTML );

    // phet-io simulations end in "phetio", see https://github.com/phetsims/phet-io/issues/288
    var filename = 'build/' + buildConfig.name + '_' + locale;
    if ( buildConfig.brand === 'phet-io' ) {
      filename = filename + '-phetio';
    }
    filename = filename + '.html';

    // Write the single-file built simulation file
    grunt.file.write( filename, localeHTML );
  }

  // Create an _all.html file
  if ( grunt.option( 'allHTML' ) ) {
    assert( buildConfig.brand === 'phet', 'allHTML option only supported with the \'phet\' brand' );
    grunt.file.write( 'build/' + buildConfig.name + '_all.html', replaceLocaleConstants( html, ChipperConstants.FALLBACK_LOCALE, true ) );
  }

  // Create a file for testing iframe embedding.  English (en) is assumed as the locale.
  // phet-io sims should be tested in more powerful wrappers instead of the iframe test.
  if ( buildConfig.locales.indexOf( ChipperConstants.FALLBACK_LOCALE ) !== -1 && buildConfig.brand !== 'phet-io' ) {
    grunt.log.debug( 'Constructing HTML for iframe testing from template' );
    var iframeTestHtml = grunt.file.read( '../chipper/templates/sim-iframe.html' );
    iframeTestHtml = ChipperStringUtils.replaceFirst( iframeTestHtml, '{{PHET_SIM_TITLE}}', encoder.htmlEncode( simTitle + ' iframe test' ) );
    iframeTestHtml = ChipperStringUtils.replaceFirst( iframeTestHtml, '{{PHET_SIM_URL}}', buildConfig.name + '_' + ChipperConstants.FALLBACK_LOCALE + '.html' );
    grunt.file.write( 'build/' + buildConfig.name + '_' + ChipperConstants.FALLBACK_LOCALE + '-iframe' + '.html', iframeTestHtml );
  }

  grunt.log.debug( 'Cleaning temporary files' );
  grunt.file.delete( 'build/' + buildConfig.name + '.min.js' );

  callback();
};
<|endoftext|>


<commit_before>describe('computedStyle', function () {
  // Localize head, body, and expect
  var head = document.getElementsByTagName('head')[0],
      body = document.body,
      expect = chai.expect;

  describe('querying an inline styled DOM element', function () {
    before(function () {
      // Create and style an element
      var el = document.createElement('div');
      el.style.cssText = 'color: #FF0000;';

      // Save the element for later
      this.el = el;

      // Append it to the DOM
      body.appendChild(el);

      // Query the element for its styles
      var color = computedStyle(el, 'color');
      this.color = color;
    });

    after(function () {
      // Clean up the element
      body.removeChild(this.el);
    });

    it('can find the styles', function () {
      // Color varies from browser to browser. jQuery doesn't tweak it and if we are keeping this single purpose, neithe will I.
      var color = this.color;
      expect(color).to.match(/^#FF0000|rgb\(255, 0, 0\)$/i);
    });
  });

  describe('querying an stylesheet styled DOM element', function () {
    before(function () {
      // Create an element
      var el = document.createElement('div');
      el.setAttribute('id', 'test-el');

      // Save the element for later
      this.el = el;

      // Append it to the DOM
      body.appendChild(el);

      try {
        // Create a stylesheet and append it to the DOM
        var stylesheet = document.createElement('style');
        stylesheet.innerHTML = '#test-el { color: #00FF00; }';

        // Save it for later
        this.stylesheet = stylesheet;

        // Append the stylesheet to the DOM
        head.appendChild(stylesheet);
      } catch (e) {
        // If the previous attempt failed, we are in IE8 or lower
        // Use native IE methods
        // Reference: http://www.quirksmode.org/dom/w3c_css.html
        var stylesheet = document.createStyleSheet();
        stylesheet.addRule('#test-el', 'color: #00FF00;');
      }

      // Query the element for its styles
      var color = computedStyle(el, 'color');
      this.color = color;
    });

    after(function () {
      // Clean up the element and stylesheet
      body.removeChild(this.el);

      var stylesheet = this.stylesheet;
      if (stylesheet) {
        head.removeChild(this.stylesheet);
      }
    });

    it('can find the styles', function () {
      var color = this.color;
      expect(color).to.match(/^#00FF00|rgb\(0, 255, 0\)$/i);
    });
  });

  describe('querying z-index of an element', function () {

  });
});<commit_msg>Receiving 'auto' for phantomjs on z-index test o_O<commit_after>describe('computedStyle', function () {
  // Localize head, body, and expect
  var head = document.getElementsByTagName('head')[0],
      body = document.body,
      expect = chai.expect;

  describe('querying an inline styled DOM element', function () {
    before(function () {
      // Create and style an element
      var el = document.createElement('div');
      el.style.cssText = 'color: #FF0000;';

      // Save the element for later
      this.el = el;

      // Append it to the DOM
      body.appendChild(el);

      // Query the element for its styles
      var color = computedStyle(el, 'color');
      this.color = color;
    });

    after(function () {
      // Clean up the element
      body.removeChild(this.el);
    });

    it('can find the styles', function () {
      // Color varies from browser to browser. jQuery doesn't tweak it and if we are keeping this single purpose, neithe will I.
      var color = this.color;
      expect(color).to.match(/^#FF0000|rgb\(255, 0, 0\)$/i);
    });
  });

  describe('querying an stylesheet styled DOM element', function () {
    before(function () {
      // Create an element
      var el = document.createElement('div');
      el.setAttribute('id', 'test-el');

      // Save the element for later
      this.el = el;

      // Append it to the DOM
      body.appendChild(el);

      try {
        // Create a stylesheet and append it to the DOM
        var stylesheet = document.createElement('style');
        stylesheet.innerHTML = '#test-el { color: #00FF00; }';

        // Save it for later
        this.stylesheet = stylesheet;

        // Append the stylesheet to the DOM
        head.appendChild(stylesheet);
      } catch (e) {
        // If the previous attempt failed, we are in IE8 or lower
        // Use native IE methods
        // Reference: http://www.quirksmode.org/dom/w3c_css.html
        var stylesheet = document.createStyleSheet();
        stylesheet.addRule('#test-el', 'color: #00FF00;');
      }

      // Query the element for its styles
      var color = computedStyle(el, 'color');
      this.color = color;
    });

    after(function () {
      // Clean up the element and stylesheet
      body.removeChild(this.el);

      var stylesheet = this.stylesheet;
      if (stylesheet) {
        head.removeChild(this.stylesheet);
      }
    });

    it('can find the styles', function () {
      var color = this.color;
      expect(color).to.match(/^#00FF00|rgb\(0, 255, 0\)$/i);
    });
  });

  describe('querying z-index of an element', function () {
    before(function () {
      // Create and style an element
      var el = document.createElement('div');
      // It's over 9000
      el.style.cssText = 'z-index: 9001;';

      // Save the element for later
      this.el = el;

      // Append it to the DOM
      body.appendChild(el);

      // Query the element for its styles
      var zIndex = computedStyle(el, 'z-index');
      this.zIndex = zIndex;
    });

    after(function () {
      // Clean up the element
      body.removeChild(this.el);
    });

    it('return the elements z-index', function () {
      var zIndex = this.zIndex;
      expect(zIndex).to.equal('9001');
    });
  });
});<|endoftext|>


<commit_before>function Spiral(graphType) {
  this.graphType = graphType || "points";
}

Spiral.prototype = {
  numberOfPoints: null,
  period: null,
  margin: {
    top: 0,
    right: 0,
    bottom: 0,
    left: 0
  },
  svgHeight: 0,
  svgWidth: 0,
  spacing: 1,
  targetElement: "body",
  width: 730,
  height: 480,
  data: [],
  x: d3.scale.linear().range([0, 730]).domain([-740, 740]),
  y: d3.scale.linear().range([480, 0]).domain([-490, 490]),
  cartesian: function(radius, angle, size, startAngle, endAngle) {
    var size = size || 1;
    var xPos = this.x(radius * Math.cos(angle));
    var yPos = this.y(radius * Math.sin(angle));
    console.log(xPos)
    return [xPos, yPos, size, radius, angle, startAngle, endAngle];
  },
  render: function() {
    // this.y.domain([-this.height-this.margin.bottom, this.height+this.margin.top]);
    // this.x.domain([-this.width-this.margin.left, this.width+this.margin.right]);

    var svg = d3.select(this.targetElement)
      .append("svg")
      .attr("width", this.svgWidth)
      .attr("height", this.svgHeight)

    if (this.graphType === "points") {
      svg.append("g")
        .attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")");

      svg.selectAll("g").selectAll("dot")
        .data(this.data)
          .enter().append("circle")
            .attr("r", function(d) { return d[2]; })
            .attr("cx", function(d) { return d[0]; })
            .attr("cy", function(d) { return d[1]; });
    } else if (this.graphType === "arcs") {
      svg.append("g")
        .attr("transform", "translate(" + (this.width * 0.5 + this.margin.left) + "," + (this.height * 0.5 + this.margin.top) + ")");

      svg.selectAll("g").selectAll("path")
        .data(this.data)
          .enter().append("path")
          .attr("d", d3.svg.arc()
          .outerRadius(function(d){return y(d[3]+10)})
          .innerRadius(function(d){return y(d[3]-10)})
          .startAngle(function(d) { return d[5] > 0 ? d[5] : 0; })
          .endAngle(function(d) { return d[6]; }))
          .attr("opacity", function(d){return d[2]/5})
    } else if (this.graphType === "paths") {

      var line = d3.svg.line()
          .interpolate("basis");

      svg.append("g")
        .attr("transform", "translate(" + (this.margin.left) + "," + (this.margin.top) + ")");

      svg.selectAll("g").selectAll("path")
          .data(quad(sample(line(this.data), this.numberOfPoints, this.period))) // ***** Need to figure out the proper precision to map data crrectly
        .enter().append("path")
          .style("fill", function(d) { return "black"; })
          .style("stroke", function(d) { return "black"; })
          .style("opacity", function(d) {return d.data/6})
          .attr("d", function(d) { return lineJoin(d[0], d[1], d[2], d[3], 10); });

      // Sample the SVG path string "d" uniformly with the specified precision.
      function sample(d, numberDataPoints, periodicity) {
        var path = document.createElementNS(d3.ns.prefix.svg, "path");
        path.setAttribute("d", d);

        var n = path.getTotalLength();
        var t = [0];
        var i = 1;
        var dt = Math.ceil(i/periodicity)*2;
        var lastSample = 0;
        while (lastSample + dt < n) {
          dt = Math.ceil(i/periodicity)*2;
          t.push(lastSample + dt);
          lastSample = lastSample + dt;
          i++;
        }
        t.push(n);

        return t.map(function(t, index) {
          var p = path.getPointAtLength(t);
          var a = data[index] ? [p.x, p.y, data[index][2]] : [p.x, p.y, 0];
          return a;
        });
      }

      // Compute quads of adjacent points [p0, p1, p2, p3].
      function quad(points) {
        return d3.range(points.length - 1).map(function(i) {
          var a = [points[i - 1], points[i], points[i + 1], points[i + 2]];
          a.data = points[i][2];
          return a;
        });
      }

      // Compute stroke outline for segment p12.
      function lineJoin(p0, p1, p2, p3, width) {
        var u12 = perp(p1, p2),
            r = width / 2,
            a = [p1[0] + u12[0] * r, p1[1] + u12[1] * r],
            b = [p2[0] + u12[0] * r, p2[1] + u12[1] * r],
            c = [p2[0] - u12[0] * r, p2[1] - u12[1] * r],
            d = [p1[0] - u12[0] * r, p1[1] - u12[1] * r];

        if (p0) { // clip ad and dc using average of u01 and u12
          var u01 = perp(p0, p1), e = [p1[0] + u01[0] + u12[0], p1[1] + u01[1] + u12[1]];
          a = lineIntersect(p1, e, a, b);
          d = lineIntersect(p1, e, d, c);
        }

        if (p3) { // clip ab and dc using average of u12 and u23
          var u23 = perp(p2, p3), e = [p2[0] + u23[0] + u12[0], p2[1] + u23[1] + u12[1]];
          b = lineIntersect(p2, e, a, b);
          c = lineIntersect(p2, e, d, c);
        }

        return "M" + a + "L" + b + " " + c + " " + d + "Z";
      }

      // Compute intersection of two infinite lines ab and cd.
      function lineIntersect(a, b, c, d) {
        var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3;
        var y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3;
        var ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21);
        return [x1 + ua * x21, y1 + ua * y21];
      }

      // Compute unit vector perpendicular to p01.
      function perp(p0, p1) {
        var u01x = p0[1] - p1[1];
        var u01y = p1[0] - p0[0];
        var u01d = Math.sqrt(u01x * u01x + u01y * u01y);
        return [u01x / u01d, u01y / u01d];
      }
    } else if (this.graphType === "custom-path") {
      var pathWidth = 50;

      customData.forEach(function(datum, t, dataSet){
        var start = startAngle(t, this.period);
        var end = endAngle(t, this.period);

        var startInnerRadius = radius(this.spacing, start) - pathWidth*0.5;
        var startOuterRadius = radius(this.spacing, start) + pathWidth*0.5;
        var endInnerRadius = radius(this.spacing, end) - pathWidth*0.5;
        var endOuterRadius = radius(this.spacing, end) + pathWidth*0.5;

        var ctrlInnerRad = 0.01; // Use to adjust arc inner radius
        var ctrlOuterRad = 0.01; // Use to adjust arc outer radius
        var innerControlPoint = cartesian(radius(this.spacing, theta(t, this.period)) - pathWidth*0.5 + ctrlInnerRad, theta(t, this.period));
        var outerControlPoint = cartesian(radius(this.spacing, theta(t, this.period)) + pathWidth*0.5 + ctrlOuterRad, theta(t, this.period));

        var startPoint = cartesian(startInnerRadius, start); // Bottom right of arc
        var point2 = cartesian(startOuterRadius, start); // Top right of arc
        var point3 = cartesian(endOuterRadius, end); // Top left of arc
        var point4 = cartesian(endInnerRadius, end); // Bottom left of arc
        var arcPath = "M" + startPoint[0] + " " + startPoint[1] + "L" + point2[0] + " " + point2[1];
        arcPath += "Q" + outerControlPoint[0] + " " + outerControlPoint[1] + " " + point3[0] + " " + point3[1];
        arcPath += "L" + point4[0] + " " + point4[1];
        arcPath += "Q" + innerControlPoint[0] + " " + innerControlPoint[1] + " " + startPoint[0] + " " + startPoint[1] + "Z";
        datum[1] = arcPath
      });

      svg.append("g")
        .attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")");
      svg.selectAll("g").selectAll("path")
        .data(customData.slice(100))
        .enter().append("path")
          .style("fill", function(d) { return "black"; })
          .style("opacity", function(d) {return d[0]/9})
          .attr("d", function(d) { return d[1]});
    } else if (this.graphType === "non-spiral") {
      // --------------------vvv Standard Line Graph vvv---------------------------
      var x2 = d3.scale.linear().range([0, width]);
      var y2 = d3.scale.linear().range([height, 0]);
      x2.domain(d3.extent(data2, function(d) { return d[0]; }));
      y2.domain(d3.extent(data2, function(d) { return d[1]; }));

      var xAxis = d3.svg.axis().scale(x2)
        .orient("bottom").ticks(5);

      var yAxis = d3.svg.axis().scale(y2)
        .orient("left").ticks(5);

      var line = d3.svg.line()
        .x(function(d) { return x2(d[0]); })
        .y(function(d) { return y2(d[1]); });

      // Add the X Axis
      svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate("+this.margin.left+"," + this.height + ")")
        .call(xAxis)
        .append("text")
          .attr("x", this.width)
          .attr("y", -3)
          .attr("dy", "-.35em")
          .style("text-anchor", "middle")
          .text("time");

      // Add the Y Axis
      svg.append("g")
        .attr("class", "y axis")
        .attr("transform", "translate("+this.margin.left+",0)")
        .call(yAxis)
        .append("text")
          .attr("transform", "rotate(-90)")
          .attr("y", 6)
          .attr("dy", ".71em")
          .style("text-anchor", "end")
          .text("Signal (a.u.)");

      svg.append("path")
        .datum(data2)
        .attr("class", "line")
        .attr("d", line)
        .attr("fill", "none")
        .attr("stroke-width", "1")
        .attr("stroke", "steelblue")
        .attr("transform", "translate("+this.margin.left+",0)")
    }
  },
  randomData: function() {
    console.log('generating', this.numberOfPoints, 'data')
    for (var i=0; i<this.numberOfPoints; i++){
      var angle = theta(i, this.period);
      var rad = radius(this.spacing, angle);
      var size = 1 + Math.random()*1.5;
      if (i % 10 === 0) {
        size = 5.5 + Math.random()*3;
      }
      this.data.push(this.cartesian(rad, angle, size, startAngle(i, this.period), endAngle(i, this.period)));
    }
  }
}

// For time point #t, calculate the angle to the center of the arc
// period is the number of time points per revolution (2 PI)
function theta(t, period) {
  return 2 * Math.PI / (period) * t;
}

function startAngle(t, period){
  return (theta(t-1, period) + theta(t, period))/2;
}

function endAngle(t, period){
  return (theta(t+1, period) + theta(t, period))/2;
}

function radius(spacing, angle) {
  return spacing * angle;
}

var spiral1 = new Spiral('points');
spiral1.numberOfPoints = 1000;
spiral1.period = 100;
spiral1.margin = {
  top: 20,
  right: 20,
  bottom: 20,
  left: 20
};
spiral1.svgHeight = 500;
spiral1.svgWidth = 750;
spiral1.spacing = 8;
spiral1.randomData();
spiral1.render();

// spiral('points');
// spiral('custom-path');
//spiral('paths');
//spiral('arcs');
// spiral('non-spiral');<commit_msg>Switch away from Spiral.prototype methods to prevent data fallback when instantiating multiple spirals<commit_after>function Spiral(graphType) {
  this.graphType = graphType || "points";
  this.numberOfPoints = null,
  this.period = null,
  this.margin = {
    top: 0,
    right: 0,
    bottom: 0,
    left: 0
  },
  this.svgHeight = 0,
  this.svgWidth = 0,
  this.spacing = 1,
  this.targetElement = "body",
  this.width = 730,
  this.height = 480,
  this.data = [],
  this.x = d3.scale.linear().range([0, 730]).domain([-740, 740]),
  this.y = d3.scale.linear().range([480, 0]).domain([-490, 490]),
  this.cartesian = function(radius, angle, size, startAngle, endAngle) {
    var size = size || 1;
    var xPos = this.x(radius * Math.cos(angle));
    var yPos = this.y(radius * Math.sin(angle));
    return [xPos, yPos, size, radius, angle, startAngle, endAngle];
  },
  this.render = function() {
    // this.y.domain([-this.height-this.margin.bottom, this.height+this.margin.top]);
    // this.x.domain([-this.width-this.margin.left, this.width+this.margin.right]);

    var svg = d3.select(this.targetElement)
      .append("svg")
      .attr("width", this.svgWidth)
      .attr("height", this.svgHeight)

    if (this.graphType === "points") {
      svg.append("g")
        .attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")");

      svg.selectAll("g").selectAll("dot")
        .data(this.data)
          .enter().append("circle")
            .attr("r", function(d) { return d[2]; })
            .attr("cx", function(d) { return d[0]; })
            .attr("cy", function(d) { return d[1]; });
    } else if (this.graphType === "arcs") {
      svg.append("g")
        .attr("transform", "translate(" + (this.width * 0.5 + this.margin.left) + "," + (this.height * 0.5 + this.margin.top) + ")");

      svg.selectAll("g").selectAll("path")
        .data(this.data)
          .enter().append("path")
          .attr("d", d3.svg.arc()
          .outerRadius(function(d){return y(d[3]+10)})
          .innerRadius(function(d){return y(d[3]-10)})
          .startAngle(function(d) { return d[5] > 0 ? d[5] : 0; })
          .endAngle(function(d) { return d[6]; }))
          .attr("opacity", function(d){return d[2]/5})
    } else if (this.graphType === "paths") {

      var line = d3.svg.line()
          .interpolate("basis");

      svg.append("g")
        .attr("transform", "translate(" + (this.margin.left) + "," + (this.margin.top) + ")");

      svg.selectAll("g").selectAll("path")
          .data(quad(sample(line(this.data), this.numberOfPoints, this.period))) // ***** Need to figure out the proper precision to map data crrectly
        .enter().append("path")
          .style("fill", function(d) { return "black"; })
          .style("stroke", function(d) { return "black"; })
          .style("opacity", function(d) {return d.data/6})
          .attr("d", function(d) { return lineJoin(d[0], d[1], d[2], d[3], 10); });

      // Sample the SVG path string "d" uniformly with the specified precision.
      function sample(d, numberDataPoints, periodicity) {
        var path = document.createElementNS(d3.ns.prefix.svg, "path");
        path.setAttribute("d", d);

        var n = path.getTotalLength();
        var t = [0];
        var i = 1;
        var dt = Math.ceil(i/periodicity)*2;
        var lastSample = 0;
        while (lastSample + dt < n) {
          dt = Math.ceil(i/periodicity)*2;
          t.push(lastSample + dt);
          lastSample = lastSample + dt;
          i++;
        }
        t.push(n);

        return t.map(function(t, index) {
          var p = path.getPointAtLength(t);
          var a = data[index] ? [p.x, p.y, data[index][2]] : [p.x, p.y, 0];
          return a;
        });
      }

      // Compute quads of adjacent points [p0, p1, p2, p3].
      function quad(points) {
        return d3.range(points.length - 1).map(function(i) {
          var a = [points[i - 1], points[i], points[i + 1], points[i + 2]];
          a.data = points[i][2];
          return a;
        });
      }

      // Compute stroke outline for segment p12.
      function lineJoin(p0, p1, p2, p3, width) {
        var u12 = perp(p1, p2),
            r = width / 2,
            a = [p1[0] + u12[0] * r, p1[1] + u12[1] * r],
            b = [p2[0] + u12[0] * r, p2[1] + u12[1] * r],
            c = [p2[0] - u12[0] * r, p2[1] - u12[1] * r],
            d = [p1[0] - u12[0] * r, p1[1] - u12[1] * r];

        if (p0) { // clip ad and dc using average of u01 and u12
          var u01 = perp(p0, p1), e = [p1[0] + u01[0] + u12[0], p1[1] + u01[1] + u12[1]];
          a = lineIntersect(p1, e, a, b);
          d = lineIntersect(p1, e, d, c);
        }

        if (p3) { // clip ab and dc using average of u12 and u23
          var u23 = perp(p2, p3), e = [p2[0] + u23[0] + u12[0], p2[1] + u23[1] + u12[1]];
          b = lineIntersect(p2, e, a, b);
          c = lineIntersect(p2, e, d, c);
        }

        return "M" + a + "L" + b + " " + c + " " + d + "Z";
      }

      // Compute intersection of two infinite lines ab and cd.
      function lineIntersect(a, b, c, d) {
        var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3;
        var y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3;
        var ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21);
        return [x1 + ua * x21, y1 + ua * y21];
      }

      // Compute unit vector perpendicular to p01.
      function perp(p0, p1) {
        var u01x = p0[1] - p1[1];
        var u01y = p1[0] - p0[0];
        var u01d = Math.sqrt(u01x * u01x + u01y * u01y);
        return [u01x / u01d, u01y / u01d];
      }
    } else if (this.graphType === "custom-path") {
      var pathWidth = 50;

      customData.forEach(function(datum, t, dataSet){
        var start = startAngle(t, this.period);
        var end = endAngle(t, this.period);

        var startInnerRadius = radius(this.spacing, start) - pathWidth*0.5;
        var startOuterRadius = radius(this.spacing, start) + pathWidth*0.5;
        var endInnerRadius = radius(this.spacing, end) - pathWidth*0.5;
        var endOuterRadius = radius(this.spacing, end) + pathWidth*0.5;

        var ctrlInnerRad = 0.01; // Use to adjust arc inner radius
        var ctrlOuterRad = 0.01; // Use to adjust arc outer radius
        var innerControlPoint = cartesian(radius(this.spacing, theta(t, this.period)) - pathWidth*0.5 + ctrlInnerRad, theta(t, this.period));
        var outerControlPoint = cartesian(radius(this.spacing, theta(t, this.period)) + pathWidth*0.5 + ctrlOuterRad, theta(t, this.period));

        var startPoint = cartesian(startInnerRadius, start); // Bottom right of arc
        var point2 = cartesian(startOuterRadius, start); // Top right of arc
        var point3 = cartesian(endOuterRadius, end); // Top left of arc
        var point4 = cartesian(endInnerRadius, end); // Bottom left of arc
        var arcPath = "M" + startPoint[0] + " " + startPoint[1] + "L" + point2[0] + " " + point2[1];
        arcPath += "Q" + outerControlPoint[0] + " " + outerControlPoint[1] + " " + point3[0] + " " + point3[1];
        arcPath += "L" + point4[0] + " " + point4[1];
        arcPath += "Q" + innerControlPoint[0] + " " + innerControlPoint[1] + " " + startPoint[0] + " " + startPoint[1] + "Z";
        datum[1] = arcPath
      });

      svg.append("g")
        .attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")");
      svg.selectAll("g").selectAll("path")
        .data(customData.slice(100))
        .enter().append("path")
          .style("fill", function(d) { return "black"; })
          .style("opacity", function(d) {return d[0]/9})
          .attr("d", function(d) { return d[1]});
    } else if (this.graphType === "non-spiral") {
      // --------------------vvv Standard Line Graph vvv---------------------------
      var x2 = d3.scale.linear().range([0, width]);
      var y2 = d3.scale.linear().range([height, 0]);
      x2.domain(d3.extent(data2, function(d) { return d[0]; }));
      y2.domain(d3.extent(data2, function(d) { return d[1]; }));

      var xAxis = d3.svg.axis().scale(x2)
        .orient("bottom").ticks(5);

      var yAxis = d3.svg.axis().scale(y2)
        .orient("left").ticks(5);

      var line = d3.svg.line()
        .x(function(d) { return x2(d[0]); })
        .y(function(d) { return y2(d[1]); });

      // Add the X Axis
      svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate("+this.margin.left+"," + this.height + ")")
        .call(xAxis)
        .append("text")
          .attr("x", this.width)
          .attr("y", -3)
          .attr("dy", "-.35em")
          .style("text-anchor", "middle")
          .text("time");

      // Add the Y Axis
      svg.append("g")
        .attr("class", "y axis")
        .attr("transform", "translate("+this.margin.left+",0)")
        .call(yAxis)
        .append("text")
          .attr("transform", "rotate(-90)")
          .attr("y", 6)
          .attr("dy", ".71em")
          .style("text-anchor", "end")
          .text("Signal (a.u.)");

      svg.append("path")
        .datum(data2)
        .attr("class", "line")
        .attr("d", line)
        .attr("fill", "none")
        .attr("stroke-width", "1")
        .attr("stroke", "steelblue")
        .attr("transform", "translate("+this.margin.left+",0)")
    }
  },
  this.randomData = function() {
    for (var i=0; i<this.numberOfPoints; i++){
      var angle = theta(i, this.period);
      var rad = radius(this.spacing, angle);
      var size = 1 + Math.random()*1.5;
      if (i % 10 === 0) {
        size = 5.5 + Math.random()*3;
      }
      this.data.push(this.cartesian(rad, angle, size, startAngle(i, this.period), endAngle(i, this.period)));
    }
  }
}

// For time point #t, calculate the angle to the center of the arc
// period is the number of time points per revolution (2 PI)
function theta(t, period) {
  return 2 * Math.PI / (period) * t;
}

function startAngle(t, period){
  return (theta(t-1, period) + theta(t, period))/2;
}

function endAngle(t, period){
  return (theta(t+1, period) + theta(t, period))/2;
}

function radius(spacing, angle) {
  return spacing * angle;
}

var spiral1 = new Spiral('points');
spiral1.numberOfPoints = 1000;
spiral1.period = 100;
spiral1.margin = {
  top: 20,
  right: 20,
  bottom: 20,
  left: 20
};
spiral1.svgHeight = 500;
spiral1.svgWidth = 750;
spiral1.spacing = 8;
spiral1.randomData();
spiral1.render();

var spiral2 = new Spiral('non-spiral')
console.log('spiral1', spiral1.data)
console.log('spiral2', spiral2.data)
// spiral('points');
// spiral('custom-path');
//spiral('paths');
//spiral('arcs');
// spiral('non-spiral');<|endoftext|>


<commit_before>/**
 * FrameBufferObject manage fbo / rtt
 * @class FrameBufferObject
 */
osg.FrameBufferObject = function () {
    osg.StateAttribute.call(this);
    this.fbo = undefined;
    this.attachments = [];
    this.dirty();
};

/** @lends osg.FrameBufferObject.prototype */
osg.FrameBufferObject.prototype = osg.objectInehrit(osg.StateAttribute.prototype, {
    attributeType: "FrameBufferObject",
    cloneType: function() {return new osg.FrameBufferObject(); },
    getType: function() { return this.attributeType;},
    getTypeMember: function() { return this.attributeType;},
    setAttachment: function(attachment) { this.attachments.push(attachment); },
    apply: function(state) {
        var status;
        if (this.attachments.length > 0) {
            if (this.isDirty()) {

                if (!this.fbo) {
                    this.fbo = gl.createFramebuffer();
                }

                gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo);
                var hasRenderBuffer = false;
                for (var i = 0, l = this.attachments.length; i < l; ++i) {

                    if (this.attachments[i].texture === undefined) { // render buffer
                        var rb = gl.createRenderbuffer();
                        gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
                        gl.renderbufferStorage(gl.RENDERBUFFER, this.attachments[i].format, this.attachments[i].width, this.attachments[i].height);
                        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, this.attachments[i].attachment, gl.RENDERBUFFER, rb);
                        hasRenderBuffer = true;
                    } else {
                        var texture = this.attachments[i].texture;
                        // apply on unit 0 to init it
                        state.applyTextureAttribute(0, texture);

                        gl.framebufferTexture2D(gl.FRAMEBUFFER, this.attachments[i].attachment, gl[texture.target], texture.textureObject, this.attachments[i].level);
                    }
                }
                status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
                if (status !== 0x8CD5) {
                    osg.log("framebuffer error check " + status);
                }

                if (hasRenderBuffer) { // set it to null only if used renderbuffer
                    gl.bindRenderbuffer(null);
                }
                this.setDirty(false);
            } else {
                gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo);
                if (osg.reportErrorGL === true) {
                    status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
                    if (status !== 0x8CD5) {
                        osg.log("framebuffer error check " + status);
                    }
                }
            }
        } else {
            gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        }
    }
});
<commit_msg>fix missing RENDERBUFFER when disabling render buffer<commit_after>/**
 * FrameBufferObject manage fbo / rtt
 * @class FrameBufferObject
 */
osg.FrameBufferObject = function () {
    osg.StateAttribute.call(this);
    this.fbo = undefined;
    this.attachments = [];
    this.dirty();
};

/** @lends osg.FrameBufferObject.prototype */
osg.FrameBufferObject.prototype = osg.objectInehrit(osg.StateAttribute.prototype, {
    attributeType: "FrameBufferObject",
    cloneType: function() {return new osg.FrameBufferObject(); },
    getType: function() { return this.attributeType;},
    getTypeMember: function() { return this.attributeType;},
    setAttachment: function(attachment) { this.attachments.push(attachment); },
    apply: function(state) {
        var status;
        if (this.attachments.length > 0) {
            if (this.isDirty()) {

                if (!this.fbo) {
                    this.fbo = gl.createFramebuffer();
                }

                gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo);
                var hasRenderBuffer = false;
                for (var i = 0, l = this.attachments.length; i < l; ++i) {

                    if (this.attachments[i].texture === undefined) { // render buffer
                        var rb = gl.createRenderbuffer();
                        gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
                        gl.renderbufferStorage(gl.RENDERBUFFER, this.attachments[i].format, this.attachments[i].width, this.attachments[i].height);
                        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, this.attachments[i].attachment, gl.RENDERBUFFER, rb);
                        hasRenderBuffer = true;
                    } else {
                        var texture = this.attachments[i].texture;
                        // apply on unit 0 to init it
                        state.applyTextureAttribute(0, texture);

                        gl.framebufferTexture2D(gl.FRAMEBUFFER, this.attachments[i].attachment, gl[texture.target], texture.textureObject, this.attachments[i].level);
                    }
                }
                status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
                if (status !== 0x8CD5) {
                    osg.log("framebuffer error check " + status);
                }

                if (hasRenderBuffer) { // set it to null only if used renderbuffer
                    gl.bindRenderbuffer(gl.RENDERBUFFER, null);
                }
                this.setDirty(false);
            } else {
                gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo);
                if (osg.reportErrorGL === true) {
                    status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
                    if (status !== 0x8CD5) {
                        osg.log("framebuffer error check " + status);
                    }
                }
            }
        } else {
            gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        }
    }
});
<|endoftext|>


<commit_before>var configHelper = require('../lib/config-helper');
var fs = require('fs');

describe('config-helper', function() {
  describe('onPrepare', function() {
    var ensureOnPrepareMatches = function(filePath, expected) {
      // Read the file
      var templateFile = fs.readFileSync(filePath, 'utf-8'),
          startIndex = templateFile.indexOf('onPrepare: function'),
          endIndex = templateFile.indexOf('}', startIndex),
          onPrepare = templateFile.substring(startIndex, endIndex);

      expect(onPrepare).toContain(expected);
    };

    it('should go to url', function(done) {
      // When you create a configuration with url.
      var promise = configHelper.createProtractorConfig({
        url: 'https://www.google.com'
      });

      // Then ensure onPrepare has a browser.get
      promise.then(function(path) {
        ensureOnPrepareMatches(path, "browser.get('https://www.google.com');");
        done();
      })
    });

    it('should ignore synchronization', function(done) {
      // When you create a configuration that ignores synchronization.
      var promise = configHelper.createProtractorConfig({
        ignoreSynchronization: true
      });

      // Then ensure onPrepare ignores synchronization.
      promise.then(function(path) {
        ensureOnPrepareMatches(path, "browser.ignoreSynchronization = true;");
        done();
      });
    });
  });
});
<commit_msg>Add test for load extension<commit_after>var configHelper = require('../lib/config-helper');
var fs = require('fs');
var path = require('path');

describe('config-helper', function() {

  describe('onPrepare', function() {

    var ensureOnPrepareMatches = function(filePath, expected) {
      // Read the file.
      var templateFile = fs.readFileSync(filePath, 'utf-8'),
          startIndex = templateFile.indexOf('onPrepare: function'),
          endIndex = templateFile.indexOf('}', startIndex),
          onPrepare = templateFile.substring(startIndex, endIndex);

      expect(onPrepare).toContain(expected);
    };

    it('should go to url', function(done) {
      // When you create a configuration with url.
      var promise = configHelper.createProtractorConfig({
        url: 'https://www.google.com'
      });

      // Then ensure onPrepare has a browser.get
      promise.then(function(path) {
        ensureOnPrepareMatches(path, "browser.get('https://www.google.com');");
        done();
      })
    });

    it('should ignore synchronization', function(done) {
      // When you create a configuration that ignores synchronization.
      var promise = configHelper.createProtractorConfig({
        ignoreSynchronization: true
      });

      // Then ensure onPrepare ignores synchronization.
      promise.then(function(path) {
        ensureOnPrepareMatches(path, 'browser.ignoreSynchronization = true;');
        done();
      });
    });
  });

  describe('chromeOptions', function() {

    var ensureChromeOptionsContains = function(filePath, expected) {
      // Read the file.
      var templateFile = fs.readFileSync(filePath, 'utf-8'),
          startIndex = templateFile.indexOf('chromeOptions'),
          endIndex = templateFile.indexOf('}', startIndex),
          onPrepare = templateFile.substring(startIndex, endIndex);

      expect(onPrepare).toContain(expected);
    };

    it('should resolve extension path', function(done) {
      // When you create a default config file.
      var promise = configHelper.createProtractorConfig({});

      // Then ensure the extension path was resolved.
      promise.then(function(file) {
        var extensionDir = path.resolve(__dirname, '../extension');
        ensureChromeOptionsContains(file, "'--load-extension=" + extensionDir + "'");
        done();
      });
    });
  });
});
<|endoftext|>


<commit_before>"use strict";

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

const foxbit = require ('./foxbit.js');

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

module.exports = class surbitcoin extends foxbit {

    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'surbitcoin',
            'name': 'SurBitcoin',
            'countries': 'VE',
            'has': {
                'CORS': false,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27991511-f0a50194-6481-11e7-99b5-8f02932424cc.jpg',
                'api': {
                    'public': 'https://api.blinktrade.com/api',
                    'private': 'https://api.blinktrade.com/tapi',
                },
                'www': 'https://surbitcoin.com',
                'doc': 'https://blinktrade.com/docs',
            },
        });
    }
}
<commit_msg>surbitcoin: linting<commit_after>'use strict';

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

const foxbit = require ('./foxbit.js');

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

module.exports = class surbitcoin extends foxbit {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'surbitcoin',
            'name': 'SurBitcoin',
            'countries': 'VE',
            'has': {
                'CORS': false,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27991511-f0a50194-6481-11e7-99b5-8f02932424cc.jpg',
                'api': {
                    'public': 'https://api.blinktrade.com/api',
                    'private': 'https://api.blinktrade.com/tapi',
                },
                'www': 'https://surbitcoin.com',
                'doc': 'https://blinktrade.com/docs',
            },
        });
    }
};
<|endoftext|>


<commit_before>$(document).ready(function() {
  $( "#donation_trigger" ).click();

  function moonTicker() {
    var timeStamp = ((Date.now() / 1000) | 0);
    $.ajax({
      dataType: "json",
      url: "https://cors-anywhere.herokuapp.com/https://api.cryptowat.ch/markets/bitstamp/btcusd/ohlc?periods=3600&after="+timeStamp,
      success: mooningFunction
    });
  }

  function mooningFunction(data) {
  	var tickerDataArray = data['result']['3600'];
  	var lastTickerData = tickerDataArray[tickerDataArray.length-1];
  	var oldEarth = lastTickerData[1];
  	var currentMoon = lastTickerData[4];

  	var hodlerStatus = currentMoon>oldEarth;

    var rollerCoasterStatus = hodlerStatus ? "To the mooooon " : "PANIC SELLING!!! ";
    $('#current-moon').html('$'+currentMoon+" USD");
    document.title = '('+Number(currentMoon).toFixed(1)+')' + " Bitcoin Roller Coaster Guy";

    rBitcoin_or_rBtc(hodlerStatus);

    $('#roller-coaster-status').html(rollerCoasterStatus);
  }

  function rBitcoin_or_rBtc(hodlerStatus){
    if(hodlerStatus){
      $('#roller-coaster-guy').addClass("here-we-go");
      $('.panel').removeClass("panel-danger").addClass("panel-success");
      $('.label').removeClass("label-danger").addClass("label-success");
    }else {
      $('#roller-coaster-guy').removeClass("here-we-go");
      $('.panel').removeClass("panel-success").addClass("panel-danger");
      $('.label').removeClass("label-success").addClass("label-danger");
    }
    return;
  }

  moonTicker();
  setInterval(moonTicker, 4 * 1000);
});<commit_msg>change to bitstamp api<commit_after>$(document).ready(function() {
  $( "#donation_trigger" ).click();

  function moonTicker() {
    var timeStamp = ((Date.now() / 1000) | 0);
    $.ajax({
      dataType: "json",
      url: "https://www.bitstamp.net/api/v2/ticker_hour/btcusd/",
      success: mooningFunction
    });
  }

  function mooningFunction(data) {
  	//var tickerDataArray = data['result']['3600'];
  	//var lastTickerData = tickerDataArray[tickerDataArray.length-1];

  	var oldEarth = data.open;
  	var currentMoon = data.last;

  	var hodlerStatus = currentMoon>oldEarth;

    var rollerCoasterStatus = hodlerStatus ? "To the mooooon " : "PANIC SELLING!!! ";
    $('#current-moon').html('$'+currentMoon+" USD");
    document.title = '('+Number(currentMoon).toFixed(1)+')' + " Bitcoin Roller Coaster Guy";

    rBitcoin_or_rBtc(hodlerStatus);

    $('#roller-coaster-status').html(rollerCoasterStatus);
  }

  function rBitcoin_or_rBtc(hodlerStatus){
    if(hodlerStatus){
      $('#roller-coaster-guy').addClass("here-we-go");
      $('.panel').removeClass("panel-danger").addClass("panel-success");
      $('.label').removeClass("label-danger").addClass("label-success");
    }else {
      $('#roller-coaster-guy').removeClass("here-we-go");
      $('.panel').removeClass("panel-success").addClass("panel-danger");
      $('.label').removeClass("label-success").addClass("label-danger");
    }
    return;
  }

  moonTicker();
  setInterval(moonTicker, 8 * 1000);
});
<|endoftext|>


<commit_before>'use strict'

const dd=(a,b)=>`\\frac{\\mathrm{d}${a}}{\\mathrm{d}${b}}`
const dydt=dd('y','t')
const int=(fx,x)=>`\\int\\!${fx}\\,\\mathrm{d}${x}`
const eqsol=(name)=>`<a href='https://en.wikipedia.org/wiki/Equilibrium_point'>${name}</a>`

module.exports={
	// TODO require all functions to be continuous on the domain?
	root: {
		parents: {},
		name: "first-order",
		importance: 0,
		equation: `${dydt} = f(t,y)`,
		properties: [
			[
				['main',[
					"Can test if \\(y_p(t)\\) is a solution by substituting \\(y = y_p\\) into the equation.",
				]],
			],
		]
	},
	separable: {
		parents: {
			root: true,
		},
		name: "first-order separable",
		htmlName: "first-order separable",
		importance: 1,
		equation: `${dydt} = f_1(t) \\cdot f_2(y)`,
		solutions: [
			[
				['title',[
					"Solutions",
				]],
				['detail',[
					`\\[ ${dydt} = f_1(t) \\cdot f_2(y) \\]`,
					`\\[ \\frac{1}{f_2(y)} \\cdot ${dydt} = f_1(t) \\]`,
					`\\[ ${int(`\\frac{1}{f_2(y)} \\cdot ${dydt}`,'t')} = ${int('f_1(t)','t')} \\]`,
				]],
				['main',[
					`\\[ ${int(`\\frac{1}{f_2(y)}`,'y')} = ${int('f_1(t)','t')} \\]`,
				]],
				['note',[
					`this may or may not include ${eqsol("equilibrium solutions")}`,
				]],
			],
			[
				['title',[
					eqsol("Equilibrium solutions"),
				]],
				['main',[
					`Solve \\( f_2(y) = 0 \\) for constant \\( y \\).`,
				]],
				//`domain is not necessary \\( -\\infty < t < +\\infty \\)`
			],
		],
	},
	autonomous: {
		parents: {
			separable: true,
		},
		name: "first-order autonomous",
		htmlName: "first-order autonomous",
		importance: 1,
		equation: `${dydt} = f(y)`,
		properties: [
			[
				['main',[
					"horizontal isoclines",
				]],
			],
			[
				['main',[
					"\\( y_p(t )\\) is a solution \\( \\Rightarrow \\) \\( y_p(t+C) \\) is a solution",
				]],
			],
		],
		solutions: [
			[
				['title',[
					"Solutions",
				]],
				['detail',[
					`\\[ ${dydt} = f(y) \\]`,
					`\\[ \\frac{1}{f(y)} \\cdot ${dydt} = 1 \\]`,
					`\\[ ${int(`\\frac{1}{f(y)} \\cdot ${dydt}`,'t')} = ${int('1','t')} \\]`,
				]],
				['main',[
					`\\[ ${int(`\\frac{1}{f(y)}`,'y')} = t \\]`,
				]],
				['note',[
					`this may or may not include ${eqsol("equilibrium solutions")}`,
				]],
			],
			[
				['title',[
					eqsol("Equilibrium solutions"),
				]],
				['main',[
					`Solve \\( f(y) = 0 \\) for constant \\( y \\)`,
				]],
				//`domain is \\( -\\infty < t < +\\infty \\)` // only if f(y) is continuous
			],
		],
	},
	bernoulli: {
		parents: {
			root: true,
		},
		name: "Bernoulli",
		htmlName: "Bernoulli",
		importance: 2,
		equation: `${dydt} + p(t) \\cdot y = q(t) \\cdot y^n`, // TODO we include n=0 and 1 here, others may exclude
	},
	linear: {
		parents: {
			bernoulli: true,
		},
		name: "first-order linear",
		htmlName: "first-order linear",
		importance: 1,
		equation: `${dydt} = a(t) \\cdot y + b(t)`,
		properties: [
			[
				['title',[
					"Extended linearity",
				]],
				['main',[
					`If \\( y_p(t) \\) is a solution of the original equation \\( ${dydt} = a(t) \\cdot y + b(t) \\)`,
					`and \\( y_h(t) \\) is a solution of its associated homogeneous equation \\( ${dydt} = a(t) \\cdot y \\),`,
					`then \\( k \\cdot y_h(t) + y_p(t) \\) is a solution of the original equation.`,
					`If additionally \\( y_h(t) \\neq 0 \\), then \\( k \\cdot y_h(t) + y_p(t) \\) is a general solution.`,
				]],
			],
			[
				['main',[
					`If \\( y_p(t) \\) and \\( y_q(t) \\) are solutions,`,
					`then \\( y_p(t) - y_q(t) \\) is a solution of the associated homogeneous equation \\( ${dydt} = a(t) \\cdot y \\).`,
				]],
			],
		],
	},
	linearHomogeneous: {
		parents: {
			separable: true,
			linear: true,
		},
		name: "first-order linear homogeneous",
		htmlName: "first-order linear homogeneous",
		importance: 1,
		equation: `${dydt} = a(t) \\cdot y`,
		properties: [
			[
				['title',[
					"Linearity",
				]],
				['main',[
					`If \\( y_h(t) \\) is a solution, then \\( k \\cdot y_h(t) \\) is a solution.`,
					`If additionally \\( y_h(t) \\neq 0 \\), then \\( k \\cdot y_h(t) \\) is a general solution.`,
				]],
			],
		],
		solutions: [
			[
				['title',[
					eqsol("Equilibrium solution"),
				]],
				['main',[
					`\\[ y(t) = 0 \\]`
				]],
			],
		],
	},
	separableInT: {
		parents: {
			separable: true,
			linear: true,
		},
		name: "first-order separable in independent variable",
		htmlName: "first-order separable in \\(t\\)",
		importance: 2,
		equation: `${dydt} = f(t)`,
		properties: [
			[
				['main',[
					"vertical isoclines",
				]],
			],
			[
				['main',[
					"\\( y_p(t) \\) is a solution \\( \\Rightarrow \\) \\( y_p(t) + C \\) is a solution",
				]],
			],
		],
		solutions: [
			[
				['title',[
					"Solutions",
				]],
				['detail',[
					`\\[ ${dydt} = f(t) \\]`,
					`\\[ ${int(`${dydt}`,'t')} = ${int('f(t)','t')} \\]`,
				]],
				['main',[
					`\\[ y = ${int('f(t)','t')} \\]`,
				]],
			],
		],
	},
	expGrowth: {
		parents: {
			autonomous: true,
			linearHomogeneous: true,
		},
		name: "exponential growth",
		htmlName: "exponential growth",
		importance: 2,
		equation: `${dydt} = k \\cdot y`,
		solutions: [
			[
				['title',[
					"Solution",
				]],
				['main',[
					`\\[ y(t) = y(0) e^{kt} \\]`,
				]],
			],
		],
	},
	logisticGrowth: {
		parents: {
			autonomous: true,
		},
		name: "logistic growth",
		htmlName: "logistic growth",
		importance: 2,
		equation: `${dydt} = k \\cdot y \\cdot \\left(1 - \\frac{y}{N}\\right)`,
	},
}
<commit_msg>main part of integrating factor solution<commit_after>'use strict'

const dd=(a,b)=>`\\frac{\\mathrm{d}${a}}{\\mathrm{d}${b}}`
const dydt=dd('y','t')
const int=(fx,x)=>`\\int\\!${fx}\\,\\mathrm{d}${x}`
const eqsol=(name)=>`<a href='https://en.wikipedia.org/wiki/Equilibrium_point'>${name}</a>`

module.exports={
	// TODO require all functions to be continuous on the domain?
	root: {
		parents: {},
		name: "first-order",
		importance: 0,
		equation: `${dydt} = f(t,y)`,
		properties: [
			[
				['main',[
					"Can test if \\(y_p(t)\\) is a solution by substituting \\(y = y_p\\) into the equation.",
				]],
			],
		]
	},
	separable: {
		parents: {
			root: true,
		},
		name: "first-order separable",
		htmlName: "first-order separable",
		importance: 1,
		equation: `${dydt} = f_1(t) \\cdot f_2(y)`,
		solutions: [
			[
				['title',[
					"Solutions",
				]],
				['detail',[
					`\\[ ${dydt} = f_1(t) \\cdot f_2(y) \\]`,
					`\\[ \\frac{1}{f_2(y)} \\cdot ${dydt} = f_1(t) \\]`,
					`\\[ ${int(`\\frac{1}{f_2(y)} \\cdot ${dydt}`,'t')} = ${int('f_1(t)','t')} \\]`,
				]],
				['main',[
					`\\[ ${int(`\\frac{1}{f_2(y)}`,'y')} = ${int('f_1(t)','t')} \\]`,
				]],
				['note',[
					`this may or may not include ${eqsol("equilibrium solutions")}`,
				]],
			],
			[
				['title',[
					eqsol("Equilibrium solutions"),
				]],
				['main',[
					`Solve \\( f_2(y) = 0 \\) for constant \\( y \\).`,
				]],
				//`domain is not necessary \\( -\\infty < t < +\\infty \\)`
			],
		],
	},
	autonomous: {
		parents: {
			separable: true,
		},
		name: "first-order autonomous",
		htmlName: "first-order autonomous",
		importance: 1,
		equation: `${dydt} = f(y)`,
		properties: [
			[
				['main',[
					"horizontal isoclines",
				]],
			],
			[
				['main',[
					"\\( y_p(t )\\) is a solution \\( \\Rightarrow \\) \\( y_p(t+C) \\) is a solution",
				]],
			],
		],
		solutions: [
			[
				['title',[
					"Solutions",
				]],
				['detail',[
					`\\[ ${dydt} = f(y) \\]`,
					`\\[ \\frac{1}{f(y)} \\cdot ${dydt} = 1 \\]`,
					`\\[ ${int(`\\frac{1}{f(y)} \\cdot ${dydt}`,'t')} = ${int('1','t')} \\]`,
				]],
				['main',[
					`\\[ ${int(`\\frac{1}{f(y)}`,'y')} = t \\]`,
				]],
				['note',[
					`this may or may not include ${eqsol("equilibrium solutions")}`,
				]],
			],
			[
				['title',[
					eqsol("Equilibrium solutions"),
				]],
				['main',[
					`Solve \\( f(y) = 0 \\) for constant \\( y \\)`,
				]],
				//`domain is \\( -\\infty < t < +\\infty \\)` // only if f(y) is continuous
			],
		],
	},
	bernoulli: {
		parents: {
			root: true,
		},
		name: "Bernoulli",
		htmlName: "Bernoulli",
		importance: 2,
		equation: `${dydt} + p(t) \\cdot y = q(t) \\cdot y^n`, // TODO we include n=0 and 1 here, others may exclude
	},
	linear: {
		parents: {
			bernoulli: true,
		},
		name: "first-order linear",
		htmlName: "first-order linear",
		importance: 1,
		equation: `${dydt} = a(t) \\cdot y + b(t)`,
		properties: [
			[
				['title',[
					"Extended linearity",
				]],
				['main',[
					`If \\( y_p(t) \\) is a solution of the original equation \\( ${dydt} = a(t) \\cdot y + b(t) \\)`,
					`and \\( y_h(t) \\) is a solution of its associated homogeneous equation \\( ${dydt} = a(t) \\cdot y \\),`,
					`then \\( k \\cdot y_h(t) + y_p(t) \\) is a solution of the original equation.`,
					`If additionally \\( y_h(t) \\neq 0 \\), then \\( k \\cdot y_h(t) + y_p(t) \\) is a general solution.`,
				]],
			],
			[
				['main',[
					`If \\( y_p(t) \\) and \\( y_q(t) \\) are solutions,`,
					`then \\( y_p(t) - y_q(t) \\) is a solution of the associated homogeneous equation \\( ${dydt} = a(t) \\cdot y \\).`,
				]],
			],
		],
		solutions: [
			[
				['title',[
					"Method of integrating factors",
				]],
				['main',[
					`Rewrite the equation as \\[ ${dydt} + g(t) \\cdot y = b(t) \\]`,
					`Introduce the integrating factor: \\[ \\mu(t) = e^{${int('g(t)','t')}} \\]`,
					`Integrate the equation multiplied by \\( \\mu(t) \\): \\[ \\mu(t) y = ${int('\\mu(t) b(t)','t')} + C \\]`,
				]],
			],
		],
	},
	linearHomogeneous: {
		parents: {
			separable: true,
			linear: true,
		},
		name: "first-order linear homogeneous",
		htmlName: "first-order linear homogeneous",
		importance: 1,
		equation: `${dydt} = a(t) \\cdot y`,
		properties: [
			[
				['title',[
					"Linearity",
				]],
				['main',[
					`If \\( y_h(t) \\) is a solution, then \\( k \\cdot y_h(t) \\) is a solution.`,
					`If additionally \\( y_h(t) \\neq 0 \\), then \\( k \\cdot y_h(t) \\) is a general solution.`,
				]],
			],
		],
		solutions: [
			[
				['title',[
					eqsol("Equilibrium solution"),
				]],
				['main',[
					`\\[ y(t) = 0 \\]`
				]],
			],
		],
	},
	separableInT: {
		parents: {
			separable: true,
			linear: true,
		},
		name: "first-order separable in independent variable",
		htmlName: "first-order separable in \\(t\\)",
		importance: 2,
		equation: `${dydt} = f(t)`,
		properties: [
			[
				['main',[
					"vertical isoclines",
				]],
			],
			[
				['main',[
					"\\( y_p(t) \\) is a solution \\( \\Rightarrow \\) \\( y_p(t) + C \\) is a solution",
				]],
			],
		],
		solutions: [
			[
				['title',[
					"Solutions",
				]],
				['detail',[
					`\\[ ${dydt} = f(t) \\]`,
					`\\[ ${int(`${dydt}`,'t')} = ${int('f(t)','t')} \\]`,
				]],
				['main',[
					`\\[ y = ${int('f(t)','t')} \\]`,
				]],
			],
		],
	},
	expGrowth: {
		parents: {
			autonomous: true,
			linearHomogeneous: true,
		},
		name: "exponential growth",
		htmlName: "exponential growth",
		importance: 2,
		equation: `${dydt} = k \\cdot y`,
		solutions: [
			[
				['title',[
					"Solution",
				]],
				['main',[
					`\\[ y(t) = y(0) e^{kt} \\]`,
				]],
			],
		],
	},
	logisticGrowth: {
		parents: {
			autonomous: true,
		},
		name: "logistic growth",
		htmlName: "logistic growth",
		importance: 2,
		equation: `${dydt} = k \\cdot y \\cdot \\left(1 - \\frac{y}{N}\\right)`,
	},
}
<|endoftext|>


<commit_before>var git = require('../');
var rimraf = require('rimraf');
var fs = require( 'fs' );

// Helper functions
var helper = {
  // Test if obj is a true function
  testFunction: function(test, obj, label) {
    // The object reports itself as a function
    test(typeof obj, 'function', label + ' reports as a function.');
    // This ensures the repo is actually a derivative of the Function [[Class]]
    test(toString.call(obj), '[object Function]', label + ' [[Class]] is of type function.');
  },
  // Test code and handle exception thrown
  testException: function(test, fun, label) {
    try {
      fun();
      test(false, label);
    }
    catch (ex) {
      test(true, label);
    }
  }
};

/**
 * Test that the commit object is present.
 *
 * @param  {Object} test
 */
exports.method = function(test){
  test.expect(2);
  helper.testFunction(test.equals, git.commit, 'Commmit');
  test.done();
};

/**
 * Test that
 *
 * @param  {Object} test
 */
exports.improperCommitId = function(test) {
  test.expect(1);
  git.repo('../.git', function(error, repository) {
    repository.commit('not a proper commit sha', function(error, commit) {
      test.notEqual(error, null, 'Error should occur');
      test.done();
    });
  });
};

var historyCountKnownSHA = 'fce88902e66c72b5b93e75bdb5ae717038b221f6';

/**
 * Test that retreiving walking a given commit's history works as expected.
 *
 * @param  {Object} test
 */
exports.history = function(test) {
  test.expect(368);
  git.repo('../.git', function(error, repository) {
    repository.commit(historyCountKnownSHA, function(error, commit) {
      test.equals(error, null, 'Getting latest branch commit should not error');
      var historyCount = 0;
      var expectedHistoryCount = 364;
      commit.history().on('commit', function(error, commit) {
        test.equals(error, null, 'There should be no errors');
        historyCount++;
      }).on('end', function(error, commits) {
        test.equals(error, null, 'There should be no errors');
        test.equals(historyCount, expectedHistoryCount, 'Manual count does not match expected');
        test.equals(commits.length, expectedHistoryCount, '"end" count does not match expected');

        test.done();
      });
    });
  });
};

/**
 * Test that retreiving master branch's HEAD commit works as expected.
 *
 * @param  {Object} test
 */
exports.masterHead = function(test) {
  test.expect(2);
  git.repo('../.git', function(error, repository) {
    repository.branch('master', function(error, branch) {

      test.equals(error, null, 'Getting branch should not error');

      repository.commit(branch.sha, function(error, commit) {

        test.equals(error, null, 'Getting latest branch commit should not error');

        test.done();
      });
    });
  });
};

/**
 * Test that retreiving parent works as expected.
 *
 * @param  {Object} test
 */
exports.parentSync = function(test) {
  test.expect(2);
  git.repo('../.git', function(error, repository) {
    repository.commit('2d71044741412280370cb0326c96d3a5a7b5dca1', function(error, commit) {
      test.equals(commit.parentCount, 1, 'Commit has exactly one parent');
      var parent = commit.parentSync(0);
      test.equals(parent.sha, 'e8876707938abf94d5cc02b0c4017c4fec2aa44e', 'Parent SHA should match expected value');
      test.done();
    });
  });
};

/**
 * Test that retreiving parent works as expected.
 *
 * @param  {Object} test
 */
exports.parent = function(test) {
  test.expect(2);
  git.repo('../.git', function(error, repository) {
    repository.commit('2d71044741412280370cb0326c96d3a5a7b5dca1', function(error, commit) {
      test.equals(commit.parentCount, 1, 'Commit has exactly one parent');
      commit.parent(0, function(error, parent) {
        if (error) throw error;
        test.equals(parent.sha, 'e8876707938abf94d5cc02b0c4017c4fec2aa44e', 'Parent SHA should match expected value');
        test.done();
      });

    });
  });
};

/**
 * Test that retrieving and walking a commit's tree works as expected.
 *
 * @param  {Object} test
 */
exports.tree = function(test) {
  test.expect(2);
  git.repo('../.git', function(error, repository) {

    repository.commit(historyCountKnownSHA, function(error, commit) {

      test.equals(error, null, 'Getting latest branch commit should not error');

      var commitTreeEntryCount = 0;
      var expectedCommitTreeEntryCount = 200;

      commit.tree().walk().on('entry', function(commit) {
        commitTreeEntryCount++;
      }).on('end', function(commits) {

        test.equals(commitTreeEntryCount, expectedCommitTreeEntryCount, 'Commit tree entry count does not match expected');

        test.done();
      });
    });
  });
};
<commit_msg>Updated convenience-commit tests<commit_after>var git = require('../');
var rimraf = require('rimraf');
var fs = require( 'fs' );

// Helper functions
var helper = {
  // Test if obj is a true function
  testFunction: function(test, obj, label) {
    // The object reports itself as a function
    test(typeof obj, 'function', label + ' reports as a function.');
    // This ensures the repo is actually a derivative of the Function [[Class]]
    test(toString.call(obj), '[object Function]', label + ' [[Class]] is of type function.');
  },
  // Test code and handle exception thrown
  testException: function(test, fun, label) {
    try {
      fun();
      test(false, label);
    }
    catch (ex) {
      test(true, label);
    }
  }
};

/**
 * Test that the commit object is present.
 *
 * @param  {Object} test
 */
exports.method = function(test){
  test.expect(2);
  helper.testFunction(test.equals, git.commit, 'Commmit');
  test.done();
};

/**
 * Test that
 *
 * @param  {Object} test
 */
exports.improperCommitId = function(test) {
  test.expect(1);
  git.repo('../.git', function(error, repository) {
    repository.commit('not a proper commit sha', function(error, commit) {
      test.notEqual(error, null, 'Error should occur');
      test.done();
    });
  });
};

var historyCountKnownSHA = 'fce88902e66c72b5b93e75bdb5ae717038b221f6';

/**
 * Test that retreiving walking a given commit's history works as expected.
 *
 * @param  {Object} test
 */
exports.history = function(test) {
  test.expect(368);
  git.repo('../.git', function(error, repository) {
    repository.commit(historyCountKnownSHA, function(error, commit) {
      test.equals(null, error, 'Getting latest branch commit should not error');
      var historyCount = 0;
      var expectedHistoryCount = 364;
      commit.history().on('commit', function(error, commit) {
        test.equals(null, error, 'There should be no errors');
        historyCount++;
      }).on('end', function(error, commits) {
        test.equals(null, error, 'There should be no errors');
        test.equals(historyCount, expectedHistoryCount, 'Manual count does not match expected');
        test.equals(commits.length, expectedHistoryCount, '"end" count does not match expected');

        test.done();
      });
    });
  });
};

/**
 * Test that retreiving master branch's HEAD commit works as expected.
 *
 * @param  {Object} test
 */
exports.masterHead = function(test) {
  test.expect(2);
  git.repo('../.git', function(error, repository) {
    repository.branch('master', function(error, branch) {

      test.equals(error, null, 'Getting branch should not error');

      repository.commit(branch.sha, function(error, commit) {

        test.equals(error, null, 'Getting latest branch commit should not error');

        test.done();
      });
    });
  });
};

/**
 * Test that retreiving parent works as expected.
 *
 * @param  {Object} test
 */
exports.parentSync = function(test) {
  test.expect(2);
  git.repo('../.git', function(error, repository) {
    repository.commit('2d71044741412280370cb0326c96d3a5a7b5dca1', function(error, commit) {
      test.equals(commit.parentCount, 1, 'Commit has exactly one parent');
      var parent = commit.parentSync(0);
      test.equals(parent.sha, 'e8876707938abf94d5cc02b0c4017c4fec2aa44e', 'Parent SHA should match expected value');
      test.done();
    });
  });
};

/**
 * Test that retreiving parent works as expected.
 *
 * @param  {Object} test
 */
exports.parent = function(test) {
  test.expect(2);
  git.repo('../.git', function(error, repository) {
    repository.commit('2d71044741412280370cb0326c96d3a5a7b5dca1', function(error, commit) {
      test.equals(commit.parentCount, 1, 'Commit has exactly one parent');
      commit.parent(0, function(error, parent) {
        if (error) throw error;
        test.equals(parent.sha, 'e8876707938abf94d5cc02b0c4017c4fec2aa44e', 'Parent SHA should match expected value');
        test.done();
      });

    });
  });
};

/**
 * Test that retrieving and walking a commit's tree works as expected.
 *
 * @param  {Object} test
 */
exports.tree = function(test) {
  test.expect(2);
  git.repo('../.git', function(error, repository) {

    repository.commit(historyCountKnownSHA, function(error, commit) {

      test.equals(error, null, 'Getting latest branch commit should not error');

      var commitTreeEntryCount = 0;
      var expectedCommitTreeEntryCount = 200;

      commit.tree().walk().on('entry', function(commit) {
        commitTreeEntryCount++;
      }).on('end', function(commits) {

        test.equals(commitTreeEntryCount, expectedCommitTreeEntryCount, 'Commit tree entry count does not match expected');

        test.done();
      });
    });
  });
};
<|endoftext|>


<commit_before>function update_color(){
    // Current time in time units set by the update interval.
    var current_time = Math.floor(new Date().getTime() / interval).toString();

    var hex = '#' + current_time.substr(current_time.length - 6);

    document.body.style.background = hex;
    document.getElementById('current-color').innerHTML = hex;
}

// Update interval in milliseconds.
var interval = 100;

window.onload = function(e){
    window.setInterval(
      'update_color()',
      interval
    );
};
<commit_msg>added use strict<commit_after>'use strict';

function update_color(){
    // Current time in time units set by the update interval.
    var current_time = Math.floor(new Date().getTime() / interval).toString();

    var hex = '#' + current_time.substr(current_time.length - 6);

    document.body.style.background = hex;
    document.getElementById('current-color').innerHTML = hex;
}

// Update interval in milliseconds.
var interval = 100;

window.onload = function(e){
    window.setInterval(
      'update_color()',
      interval
    );
};
<|endoftext|>