<commit_before>// ==UserScript==
// @name        PSNprofiles.net enhancements
// @author      Barokai | www.loigistal.at
// @icon        http://psnprofiles.com/forums/favicon.ico
// @namespace   http://www.loigistal.at/userscripts/
// @homepage    https://github.com/Barokai/psnprofiles-enhanced/
// @license     https://github.com/Barokai/psnprofiles-enhanced/blob/master/LICENSE
// @updateURL   https://github.com/Barokai/psnprofiles-enhanced/raw/master/psnprofiles-enhanced.user.js
// @version     0.84
// @description On guide pages: adds a button to hide earend trophies, their description and links and uses a new style for earned trophies, On all pages: adds update button, On game pages: persist search string
// @match       http://psnprofiles.com/*
// @grant       GM_addStyle
// @require     http://code.jquery.com/jquery-latest.js
// @copyright   2016+, Barokai
// ==/UserScript==

// better visible green for earned trophies, used before: #57FF3B
/*jshint multistr: true */
GM_addStyle("\
.tableofcontents li.earned {	\
background: #bada55 !important;  \
} \
.roadmap-trophies li.earned { \
background: #bada55 !important; \
} \
#toggleEarned { \
background-color: #bada55; \
} \
#toggleEarnedOld { \
height: 25px; \
background: none;\
border: none;\
color: white;\
margin: 0; \
padding: 0;\
}");

/* Guide enhancements ------------------------------------------------------- */
// TODO barokai: fix scrolling behavior when trophies are hidden and a trophy link is clicked in Guide Contens column (ID="TOCWrapper")
function addToggleEarnedButton() {
  // add class earned to all links which match earned trophies in overview-info box
  $('.earned > a').each(function() {
    var trophyName = $(this).text().trim();
    $('nobr > a:contains(' + trophyName + ')').addClass("earned");
  });

  // adds class "earned" to sections to hide them with the same toggle function
  $("img[class*='earned']").each(function() {
    $($(this).closest("div[class*='section-holder']")).addClass("earned");
  });

  // adds button for toggling to overview info box
  $(".overview-info").append('toggleearned');
  $(document).on("click", "#toggleEarned", toggleEarned);
}

function addToggleTypeButton() {
  // get trophy types (without spaces) - used as classes to toggle them later.
  var trophyTypes = $('table.invisible .tag').map(function(i, el) {
    var type = $(el);
    var typeName = type.text().replace(/\s+/g, '');
    // add toggle visibility to type boxes
    type.click(function(e) {
      toggleClass(e, typeName);
    });
    return typeName;
  }).get();

  // get corresponding trophies - start with the ones in overview box
  $('table.invisible td small').each(function(index) {
    var typeClassName = trophyTypes[index];
    var trophyHref = $("nobr a", $(this));
    // add class to all found hrefs for this section
    trophyHref.addClass(typeClassName);

    trophyHref.each(function() {
      var trophyName = $(this).text();

      // contents of roadmap
      $(".roadmap-trophies li:contains(" + trophyName + ")").addClass(typeClassName);

      // all the sections
      $("div[class*='element section-holder']:contains(" + trophyName + ")").addClass(typeClassName);

      // next the ones in the guide contents on the right
      $('#TOCList li:contains(' + trophyName + ')').addClass(typeClassName);

      // add class to trophies which are found in multible type sections
      $('table.invisible nobr a:contains(' + trophyName + ')').addClass(typeClassName);
    });
  });
}

function toggleEarned(e) {
  toggleClass(e, "earned");
}

function toggleClass(e, className) {
  var element;

  // get correct element (togglebutton's layout would be destroyed otherwise)
  if (e.target.parentNode.id != "toggleEarned") {
    element = e.target;
  } else {
    element = e.target.parentNode;
  }

  if (element.innerHTML.indexOf(" *") >= 0) {
    element.innerHTML = element.innerHTML.slice(0, -2);
  } else {
    element.innerHTML += " *";
  }

  $("." + className).toggle("slow", function(e) { /* Animation complete */ });
}
/* Guide enhancements end --------------------------------------------------- */

/* Profile enhancements ----------------------------------------------------- */

// thanks to serverTimeout for sort by rank (his profile: http://psnprofiles.com/forums/user/80890-servertimeout/)
// see his post here: http://psnprofiles.com/forums/topic/24324-sort-by-rank/?view=findpost&p=647509
function addSortByRank() {
  var dropdown = $('.dropdown').eq(2).find('.dropdown-menu');
  var buttonNameAsc = "Rank E-S";
  dropdown.append(
    $('
' + buttonNameAsc + '
').click(function(e) {
      sort(e, '+', buttonNameAsc); // + for ascending sort
    })
  );
  var buttonNameDesc = "Rank S-E";
  dropdown.append(
    $('
' + buttonNameDesc + '
').click(function(e) {
      sort(e, '-', buttonNameDesc); // - for descending sort
    })
  );
}

function sort(e, order, buttonName) {
  e.preventDefault();
  var trophyOrder = ['F', 'E', 'D', 'C', 'B', 'A', 'S'];
  var r;
  if (order === '+') {
    for (r = 0; r <= trophyOrder.length; r++) {
      // TODO: sort ranks by percent before
      $('.' + trophyOrder[r]).each(function() {
        $('#gamesTable').append($(this).closest('tr'));
      });
    }
  }
  if (order === '-') {
    for (r = trophyOrder.length; r >= 0; r--) {
      // TODO: sort ranks by percent before
      $('.' + trophyOrder[r]).each(function() {
        $('#gamesTable').append($(this).closest('tr'));
      });
    }
  }

  // set dropdown button name when new order was set
  $('.dropdown-toggle.order').text("Order (" + buttonName + ")");
}
/* Profile enhancements end ------------------------------------------------- */

/* Global enhancements ------------------------------------------------------ */
function addUpdateButton() {
  $('.navigation > ul').append("
Update
");
  // TODO barokai: check if successfully updated and redirect to the last visited page (where update was clicked)
}
/* Global enhancements end ---------------------------------------------------*/

/* Games page enhancements -------------------------------------------------- */

// thanks to dernop for this searchFix (his profile: http://psnprofiles.com/forums/user/45256-dernop/)
// see his post here: http://psnprofiles.com/forums/topic/32107-bugsoddities-in-the-games-search-feature/#entry777278
function gameSearchFix() {
  $('#searchGames').off('keyup');
  $('#searchGames').keyup(function() {
    window.clearTimeout('searchInt');
    var input = $(this);
    if (input.val().length > 1) {
      $('#loading').show();
      $('#closeButton').hide();

      var searchInt = window.setTimeout(
        function() {
          $('#pagination').hide();
          var q = encodeURIComponent(input.val());
          $.ajax({
            url: "/php/liveSearch.php?t=g&q=" + q,
            success: function(html) {
              window.location.hash = '#!' + q;
              $('#game_list').html(html);
              $('#loading').hide();
              $('#closeButton').show();
            }
          });
        }, 500);
    } else {
      $('#loading').hide();
    }
  });
  var query = decodeURIComponent(window.location.hash.replace('#!', ''));

  if (query.length > 0) {
    $('#searchGames').val(query).keyup();
  }
}

// TODO barokai: integrate other func in psnp
var psnp = {
  id: $('div.user-menu a.dropdown-toggle span').text(),
  _gamesTable: $('table#gamesTable'), // game table on user profile
  _gameList: $('table#game_list'),
  _profileBar: $('div.profile-bar')
};
// add percentage on mouseover or integrate into game row
// add mouse over information like percentage (last row), last played if available etc.
// thanks to dernop (again) - http://psnprofiles.com/forums/topic/35583-add-possibility-to-hide-earned-trophies-in-guides-with-userscript/#entry932561
$(function() {
  // initialize psnp page/DOM information

  // psnp properties
  $.extend(psnp, {
    isProfile: psnp._gamesTable[0] !== undefined && psnp._profileBar[0] !== undefined,
    isOwnProfile: $(psnp._profileBar).find('div.info').text().indexOf(psnp.id) > -1,
    isGameList: psnp._gameList.length == 1,
    myGames: JSON.parse(localStorage.getItem('_mygames')) || {}
  });

  psnp.updateMyGames = function(games) {
    var count = 0;
    $.each(games, function(i, e) {
      psnp.myGames[e.id] = e;
      count++;
    });
    localStorage.setItem('_mygames', JSON.stringify(psnp.myGames));
    console.log(count + " games added/updated to localstorage.");
  };

  // PROFILE / GAME LIST
  psnp.gameList = (function() {
    if (!psnp.isProfile)
      return undefined;
    var _games = [];
    // register mutationobserver for gameList to handle 'load-on-scroll'
    var obs = new MutationObserver(function(mutations) {
      parseGames(); // just re-parse all games if list has changed.
    });
    obs.observe(psnp._gamesTable[0], {
      childList: true,
      subtree: true
    });

    // parse PSNP games table (id, name, completion, # of trophies)
    function parseGames() {
      _games = [];
      psnp._gamesTable.find('tr:has(a.bold)').each(function(i, row) {
        var title = $(row).find('a.bold')[0];
        var game = {
          id: title.href.match(/\/trophies\/([^\/]+)/)[1],
          name: title.innerText,
          progress: $(row).find('div.progress_outer span').text(),
          completed: $(row).hasClass('completed'),
          platinum: $(row).hasClass('platinum'),
          gold: $(row).find('li.gold').text(),
          silver: $(row).find('li.silver').text(),
          bronze: $(row).find('li.bronze').text(),
        };

        if (!psnp.isOwnProfile && psnp.myGames[game.id]) {
          var img = $(row).find('img.trophy_image');
          img.removeClass('no-border').addClass('earned'); // mark owned games
        }
        _games.push(game);
      });

      // if this is our own profile, save the games list.
      if (psnp.isOwnProfile) {
        psnp.updateMyGames(_games);
      }
    }

    // parse game list initially
    parseGames();

    return {
      games: _games
    };
  })();

  // #GAMELIST#  global list of games (psnprofiles.com/games)

  // Mark owned game in "Games" list
  if (psnp.isGameList) {
    psnp._gameList.find('tr:has(a.bold)').each(function(i, row) {
      var title = $(row).find('a.bold')[0];
      var id = title.href.match(/\/trophies\/([^\/"]+)/)[1];
      var img = $(row).find('img.trophy_image');

      // mark owned games
      if (psnp.myGames[id]) {
        if (psnp.myGames[id].platinum)
          $(row).addClass('platinum'); // add platinum row style
        if (psnp.myGames[id].completed)
          $(row).addClass('completed'); // add completed row style

        img.removeClass('no-border').addClass('earned');
        if (psnp.myGames[id].progress != '100%')
          img.css('border-color', 'yellow'); //

        // add completion percentage
        var avgProgress = $(row).children('td:eq(4)').find('span.typo-top');
        console.log(avgProgress);
        avgProgress.text(psnp.myGames[id].progress + " / " + avgProgress.text());

      }
    });
  }
});

/* Games page enhancements end -----------------------------------------------*/

// helperfunction to determine if the url matches a certain segment
function matchesUrl(urlSegment) {
  return document.location.pathname.indexOf(urlSegment) === 0;
}

/* -------------------------------------------------------------------------- */
/* Apply enhancements to correct pages -------------------------------------- */
/* -------------------------------------------------------------------------- */

// add toggle button functionality to all guides (if any earned trophies were found
matchesUrl("/guide/") && addToggleTypeButton();
matchesUrl("/guide/") && $('.earned > a').length > 0 && addToggleEarnedButton();
// add searchFix to games page
matchesUrl("/games") && gameSearchFix();
// add update button to navigation on all psnprofile pages (if logged in)
psnp.id && addUpdateButton();
// add only on profile pages and others where a sort dropdown is
matchesUrl("/" + psnp.id) && addSortByRank();
<commit_msg>Updated URLs<commit_after>// ==UserScript==
// @name        PSNprofiles.net enhancements
// @author      Barokai | www.loigistal.at
// @icon        http://psnprofiles.com/forums/favicon.ico
// @namespace   http://www.loigistal.at/userscripts/
// @license     https://github.com/Barokai/psnprofiles-enhanced/blob/master/LICENSE
// @homepageURL http://barokai.github.io/psnprofiles-enhanced/
// @updateURL   https://github.com/Barokai/psnprofiles-enhanced/raw/master/psnprofiles-enhanced.user.js
// @supportURL  https://github.com/Barokai/psnprofiles-enhanced/issues/new
// @version     0.85
// @description On guide pages: adds a button to hide earend trophies, their description and links and uses a new style for earned trophies, On all pages: adds update button, On game pages: persist search string
// @match       http://psnprofiles.com/*
// @grant       GM_addStyle
// @require     http://code.jquery.com/jquery-latest.js
// @copyright   2016+, Barokai
// ==/UserScript==

// better visible green for earned trophies, used before: #57FF3B
/*jshint multistr: true */
GM_addStyle("\
.tableofcontents li.earned {	\
background: #bada55 !important;  \
} \
.roadmap-trophies li.earned { \
background: #bada55 !important; \
} \
#toggleEarned { \
background-color: #bada55; \
} \
#toggleEarnedOld { \
height: 25px; \
background: none;\
border: none;\
color: white;\
margin: 0; \
padding: 0;\
}");

/* Guide enhancements ------------------------------------------------------- */
// TODO barokai: fix scrolling behavior when trophies are hidden and a trophy link is clicked in Guide Contens column (ID="TOCWrapper")
function addToggleEarnedButton() {
  // add class earned to all links which match earned trophies in overview-info box
  $('.earned > a').each(function() {
    var trophyName = $(this).text().trim();
    $('nobr > a:contains(' + trophyName + ')').addClass("earned");
  });

  // adds class "earned" to sections to hide them with the same toggle function
  $("img[class*='earned']").each(function() {
    $($(this).closest("div[class*='section-holder']")).addClass("earned");
  });

  // adds button for toggling to overview info box
  $(".overview-info").append('toggleearned');
  $(document).on("click", "#toggleEarned", toggleEarned);
}

function addToggleTypeButton() {
  // get trophy types (without spaces) - used as classes to toggle them later.
  var trophyTypes = $('table.invisible .tag').map(function(i, el) {
    var type = $(el);
    var typeName = type.text().replace(/\s+/g, '');
    // add toggle visibility to type boxes
    type.click(function(e) {
      toggleClass(e, typeName);
    });
    return typeName;
  }).get();

  // get corresponding trophies - start with the ones in overview box
  $('table.invisible td small').each(function(index) {
    var typeClassName = trophyTypes[index];
    var trophyHref = $("nobr a", $(this));
    // add class to all found hrefs for this section
    trophyHref.addClass(typeClassName);

    trophyHref.each(function() {
      var trophyName = $(this).text();

      // contents of roadmap
      $(".roadmap-trophies li:contains(" + trophyName + ")").addClass(typeClassName);

      // all the sections
      $("div[class*='element section-holder']:contains(" + trophyName + ")").addClass(typeClassName);

      // next the ones in the guide contents on the right
      $('#TOCList li:contains(' + trophyName + ')').addClass(typeClassName);

      // add class to trophies which are found in multible type sections
      $('table.invisible nobr a:contains(' + trophyName + ')').addClass(typeClassName);
    });
  });
}

function toggleEarned(e) {
  toggleClass(e, "earned");
}

function toggleClass(e, className) {
  var element;

  // get correct element (togglebutton's layout would be destroyed otherwise)
  if (e.target.parentNode.id != "toggleEarned") {
    element = e.target;
  } else {
    element = e.target.parentNode;
  }

  if (element.innerHTML.indexOf(" *") >= 0) {
    element.innerHTML = element.innerHTML.slice(0, -2);
  } else {
    element.innerHTML += " *";
  }

  $("." + className).toggle("slow", function(e) { /* Animation complete */ });
}
/* Guide enhancements end --------------------------------------------------- */

/* Profile enhancements ----------------------------------------------------- */

// thanks to serverTimeout for sort by rank (his profile: http://psnprofiles.com/forums/user/80890-servertimeout/)
// see his post here: http://psnprofiles.com/forums/topic/24324-sort-by-rank/?view=findpost&p=647509
function addSortByRank() {
  var dropdown = $('.dropdown').eq(2).find('.dropdown-menu');
  var buttonNameAsc = "Rank E-S";
  dropdown.append(
    $('
' + buttonNameAsc + '
').click(function(e) {
      sort(e, '+', buttonNameAsc); // + for ascending sort
    })
  );
  var buttonNameDesc = "Rank S-E";
  dropdown.append(
    $('
' + buttonNameDesc + '
').click(function(e) {
      sort(e, '-', buttonNameDesc); // - for descending sort
    })
  );
}

function sort(e, order, buttonName) {
  e.preventDefault();
  var trophyOrder = ['F', 'E', 'D', 'C', 'B', 'A', 'S'];
  var r;
  if (order === '+') {
    for (r = 0; r <= trophyOrder.length; r++) {
      // TODO: sort ranks by percent before
      $('.' + trophyOrder[r]).each(function() {
        $('#gamesTable').append($(this).closest('tr'));
      });
    }
  }
  if (order === '-') {
    for (r = trophyOrder.length; r >= 0; r--) {
      // TODO: sort ranks by percent before
      $('.' + trophyOrder[r]).each(function() {
        $('#gamesTable').append($(this).closest('tr'));
      });
    }
  }

  // set dropdown button name when new order was set
  $('.dropdown-toggle.order').text("Order (" + buttonName + ")");
}
/* Profile enhancements end ------------------------------------------------- */

/* Global enhancements ------------------------------------------------------ */
function addUpdateButton() {
  $('.navigation > ul').append("
Update
");
  // TODO barokai: check if successfully updated and redirect to the last visited page (where update was clicked)
}
/* Global enhancements end ---------------------------------------------------*/

/* Games page enhancements -------------------------------------------------- */

// thanks to dernop for this searchFix (his profile: http://psnprofiles.com/forums/user/45256-dernop/)
// see his post here: http://psnprofiles.com/forums/topic/32107-bugsoddities-in-the-games-search-feature/#entry777278
function gameSearchFix() {
  $('#searchGames').off('keyup');
  $('#searchGames').keyup(function() {
    window.clearTimeout('searchInt');
    var input = $(this);
    if (input.val().length > 1) {
      $('#loading').show();
      $('#closeButton').hide();

      var searchInt = window.setTimeout(
        function() {
          $('#pagination').hide();
          var q = encodeURIComponent(input.val());
          $.ajax({
            url: "/php/liveSearch.php?t=g&q=" + q,
            success: function(html) {
              window.location.hash = '#!' + q;
              $('#game_list').html(html);
              $('#loading').hide();
              $('#closeButton').show();
            }
          });
        }, 500);
    } else {
      $('#loading').hide();
    }
  });
  var query = decodeURIComponent(window.location.hash.replace('#!', ''));

  if (query.length > 0) {
    $('#searchGames').val(query).keyup();
  }
}

// TODO barokai: integrate other func in psnp
var psnp = {
  id: $('div.user-menu a.dropdown-toggle span').text(),
  _gamesTable: $('table#gamesTable'), // game table on user profile
  _gameList: $('table#game_list'),
  _profileBar: $('div.profile-bar')
};
// add percentage on mouseover or integrate into game row
// add mouse over information like percentage (last row), last played if available etc.
// thanks to dernop (again) - http://psnprofiles.com/forums/topic/35583-add-possibility-to-hide-earned-trophies-in-guides-with-userscript/#entry932561
$(function() {
  // initialize psnp page/DOM information

  // psnp properties
  $.extend(psnp, {
    isProfile: psnp._gamesTable[0] !== undefined && psnp._profileBar[0] !== undefined,
    isOwnProfile: $(psnp._profileBar).find('div.info').text().indexOf(psnp.id) > -1,
    isGameList: psnp._gameList.length == 1,
    myGames: JSON.parse(localStorage.getItem('_mygames')) || {}
  });

  psnp.updateMyGames = function(games) {
    var count = 0;
    $.each(games, function(i, e) {
      psnp.myGames[e.id] = e;
      count++;
    });
    localStorage.setItem('_mygames', JSON.stringify(psnp.myGames));
    console.log(count + " games added/updated to localstorage.");
  };

  // PROFILE / GAME LIST
  psnp.gameList = (function() {
    if (!psnp.isProfile)
      return undefined;
    var _games = [];
    // register mutationobserver for gameList to handle 'load-on-scroll'
    var obs = new MutationObserver(function(mutations) {
      parseGames(); // just re-parse all games if list has changed.
    });
    obs.observe(psnp._gamesTable[0], {
      childList: true,
      subtree: true
    });

    // parse PSNP games table (id, name, completion, # of trophies)
    function parseGames() {
      _games = [];
      psnp._gamesTable.find('tr:has(a.bold)').each(function(i, row) {
        var title = $(row).find('a.bold')[0];
        var game = {
          id: title.href.match(/\/trophies\/([^\/]+)/)[1],
          name: title.innerText,
          progress: $(row).find('div.progress_outer span').text(),
          completed: $(row).hasClass('completed'),
          platinum: $(row).hasClass('platinum'),
          gold: $(row).find('li.gold').text(),
          silver: $(row).find('li.silver').text(),
          bronze: $(row).find('li.bronze').text(),
        };

        if (!psnp.isOwnProfile && psnp.myGames[game.id]) {
          var img = $(row).find('img.trophy_image');
          img.removeClass('no-border').addClass('earned'); // mark owned games
        }
        _games.push(game);
      });

      // if this is our own profile, save the games list.
      if (psnp.isOwnProfile) {
        psnp.updateMyGames(_games);
      }
    }

    // parse game list initially
    parseGames();

    return {
      games: _games
    };
  })();

  // #GAMELIST#  global list of games (psnprofiles.com/games)

  // Mark owned game in "Games" list
  if (psnp.isGameList) {
    psnp._gameList.find('tr:has(a.bold)').each(function(i, row) {
      var title = $(row).find('a.bold')[0];
      var id = title.href.match(/\/trophies\/([^\/"]+)/)[1];
      var img = $(row).find('img.trophy_image');

      // mark owned games
      if (psnp.myGames[id]) {
        if (psnp.myGames[id].platinum)
          $(row).addClass('platinum'); // add platinum row style
        if (psnp.myGames[id].completed)
          $(row).addClass('completed'); // add completed row style

        img.removeClass('no-border').addClass('earned');
        if (psnp.myGames[id].progress != '100%')
          img.css('border-color', 'yellow'); //

        // add completion percentage
        var avgProgress = $(row).children('td:eq(4)').find('span.typo-top');
        console.log(avgProgress);
        avgProgress.text(psnp.myGames[id].progress + " / " + avgProgress.text());

      }
    });
  }
});

/* Games page enhancements end -----------------------------------------------*/

// helperfunction to determine if the url matches a certain segment
function matchesUrl(urlSegment) {
  return document.location.pathname.indexOf(urlSegment) === 0;
}

/* -------------------------------------------------------------------------- */
/* Apply enhancements to correct pages -------------------------------------- */
/* -------------------------------------------------------------------------- */

// add toggle button functionality to all guides (if any earned trophies were found
matchesUrl("/guide/") && addToggleTypeButton();
matchesUrl("/guide/") && $('.earned > a').length > 0 && addToggleEarnedButton();
// add searchFix to games page
matchesUrl("/games") && gameSearchFix();
// add update button to navigation on all psnprofile pages (if logged in)
psnp.id && addUpdateButton();
// add only on profile pages and others where a sort dropdown is
matchesUrl("/" + psnp.id) && addSortByRank();
<|endoftext|>


<commit_before>class Board {
    // Creates a new board object
    // This is the stuff that should be coming from the view
    constructor(width, height, gameWidth, gameHeight, inputs, outputs, pieces){
        this.layout = new Array(gameWidth);
        for(var i = 0; i < gameWidth; i++){
            this.layout[i] = new Array(gameHeight);
        }
        this.width = width;
        this.height = height;
        this.inputs = inputs;
        this.outputs = outputs;
        this.pieces = pieces;
    }

    // Uses "private" vars so we also calc dim unit size
    set width(width) {
        this._width = width;
        this.updateBoardDim();
    }
    get width() {
        return this._width;
    }
    set height(height) {
        this._height = height;
        this.updateBoardDim();
    }
    get height() {
        return this._height;
    }

    // Dinamicaly set the unit scale of pieces based on the new width and height
    updateBoardDim() {
        // Find a scale unit on the board
        // This is the number of pixels a square takes up
        var unitWidth = (this.width - 20) / this.gameWidth;
        var unitHeight = (this.height - 30) / (this.gameHeight + 1);
        if (unitWidth < unitHeight) {
            this._unit = unitWidth;
        } else {
            this._unit = unitHeight;
        }
    }

    // Returns the private unit calculation
    get unit() {
        return this._unit;
    }

    // Commom values that are needed in a lot of places so can be got
    // as if they were variables themselves
    get boardWidth() { return this.unit * this.gameWidth; }
    get boardHeight() { return this.unit * this.gameHeight; }
    get boardX() { return (this.width - this.boardWidth) / 2; }
    get boardY() { return ((this.height - this.boardHeight - this.unit) / 2) - 5; }
    get gameWidth() { return this.layout.length; }
    get gameHeight() { return this.layout[0].length; }

    // These are common calculations that require extra bits of info such as x and y cords
    getBoardX(x) { return Math.trunc((x - this.boardX) / this.unit); }
    getBoardY(y) { return Math.trunc((y - this.boardY) / this.unit); }
    onBoard(x, y) { return 0 <= y && 0 <= x && y < this.gameHeight && x < this.gameWidth}

    /**
     * Returns a piece from the board and removes it from the board
     * based on x and y cordanate location
     *
     * @param x corordanate on the canvas that was drawn to
     * @param y corordanate on the canvas that was drawn to
     * @return game piece
     */
    getPiece(x, y) {
        // Start by seeing if the piece is in the y height of the dock
        var tmp = (y - this.boardY - this.boardHeight - 10);
        if (tmp > 0 && tmp < this.unit) {
            // Then check to see if there is a piece there for it
            tmp = this.getBoardX(x);
            if (0 <= tmp && tmp < this.pieces.length){
                // If there is remove the piece from the list and return it
                return this.pieces.splice(tmp, 1);
            }
        } else {
            // Start by seing if the x, y is on the board
            var cx = this.getBoardX(x);
            var cy = this.getBoardY(y);
            if (this.onBoard(cx, cy)) {
                // If it is remove it and return it
                tmp = this.layout[cx][cy];
                this.layout[cx][cy] = undefined;
                return tmp;
            }
        }
        return null;
    }

    /**
     * This sets a piece on the board based on screen cords to a piece id that is passed in
     *
     * @param x corodanate on the canvas that is drawn to
     * @param y corodanate on the canvas that is drawn to
     * @param p an id identifying what piece to place
     */
    setPiece(x, y, p) {
        // Make sure there is a piece and its not wire
        if (![0, null, undefined].includes(p)) {
            var cx = this.getBoardX(x);
            var cy = this.getBoardY(y);
            // Check to see if the location is on the board and the board doesn't already have a piece there
            if (this.onBoard(cx, cy) && [0, null, undefined].includes(this.layout[cx][cy])) {
                // If its good add it to the locaiton
                this.layout[cx][cy] = p;
            } else {
                // The spot was invalid so we should add it to the end of the palate so we don't loose it
                this.pieces.push(p);
            }
        }
    }

    /**
     * This is for the drawing of wires on the board
     *
     * @param x corodanate on the canvas that is drawn to
     * @param y corodanate on the canvas that is drawn to
     * @param p an id identifying what piece was last clicked on
     */
    toogleWire(x, y, piece) {
        // If the piece that was given is a wire or nothing
        if ([0, null, undefined].includes(piece)) {
            var cx = this.getBoardX(x);
            var cy = this.getBoardY(y);
            // If the spot was on the board and not a piece
            if (this.onBoard(cx, cy) && [0, null, undefined].includes(this.layout[cx][cy])) {
                // Then set it to the oposite of the piece given
                this.layout[cx][cy] = (piece == 0) ? undefined : 0;
            }
        }
    }

    draw(canvas){
        var color = false;
        for (var x = 0; x < this.gameWidth; x++) {
            if (this.gameWidth % 2 != 0) color = !color;
            for (var y = 0; y < this.gameHeight; y++) {
                color = !color;
                canvas.fillStyle = color ? "#111111" : "#222222";
                canvas.fillRect(
                    this.unit * x + this.boardX,
                    this.unit * y + this.boardY,
                    this.unit,
                    this.unit
                );
                var piece = pieceAsset(this.layout[x][y], false);
                if (piece == 0) {
                    drawWire(x, y, canvas, false);
                } else if (piece != null) {
                    canvas.drawImage(
                        pieceAsset(this.layout[x][y], false),
                        this.unit * x + this.boardX,
                        this.unit * y + this.boardY,
                        this.unit,
                        this.unit
                    );
                }
            }
        }
        for (var x = 0; x < this.gameWidth; x++) {
            color = !color;
            canvas.fillStyle = color ? "#111111" : "#222222";
            canvas.fillRect(
                this.unit * x + this.boardX,
                this.boardHeight + this.boardY + 10,
                this.unit,
                this.unit
            );
            if (x < this.pieces.length) {
                canvas.drawImage(
                    pieceAsset(this.pieces[x], false),
                    this.unit * x + this.boardX,
                    this.boardHeight + this.boardY + 10,
                    this.unit,
                    this.unit
                );
            }
        }
    }
    drawWire(x , y, canvas, bool) {
        var sides = new Array(4);
        // Left
        sides[0] = (x > 1 && [0, 1, 2, 3].includes(this.layout[x - 1][y]));
        // Right
        sides[1] = (x < this.gameWidth - 1 && [0, 3].includes(this.layout[x + 1][y]));
        // Top
        sides[2] = (y > 0 && [0, 1, 2, 3].includes(this.layout[x][y - 1]));
        // Bottom
        sides[3] = (y < this.gameHeight - 1 && [0, 1, 2, 3].includes(this.layout[x][y + 1]));

        // This is to test the sides
        console.log(sides);
        return sides;
    }
}

var board;
var canvas;
var mouseDown;
var mouseX, mouseY;
var pieceAssets;
var wireAssets;
var currentPiece;

// This will return the image that is associated with a piece number then the given state if on or off
function pieceAsset(num, bool) {
    if (num > 0) return pieceAssets[(2 * num) - (bool ? 2 : 1)];
}

document.addEventListener("DOMContentLoaded", function(event){
    canvas = document.getElementsByTagName("canvas")[0].getContext("2d");
    isMouseDown = false;
    var gameBoard = document.getElementById("game_board");
    var inputs = toLogicArray(gameBoard, "input");
    var outputs = toLogicArray(gameBoard, "output");
    var piecesHTML = Array.from(gameBoard.getElementsByClassName("pieces")[0].getElementsByTagName("div"));
    var pieces = new Array(piecesHTML.length);
    piecesHTML.forEach(
        function(element, index){
            pieces[index] = parseInt(element.innerHTML);
        }
    );

    board = new Board(
        window.innerWidth,
        window.innerHeight,
        parseInt(gameBoard.getElementsByClassName("width")[0].innerHTML),
        parseInt(gameBoard.getElementsByClassName("height")[0].innerHTML),
        inputs,
        outputs,
        pieces
    );
    console.log(board);

    // Read in all the images
    var images = [
        "/images/and-on.svg",
        "/images/and-off.svg",
        "/images/or-on.svg",
        "/images/or-off.svg",
        "/images/not-on.svg",
        "/images/not-off.svg"
    ];
    pieceAssets = new Array(images.length);
    images.forEach(
        function(element, index){
            pieceAssets[index] = new Image();
            pieceAssets[index].src = element;
        }
    );

    // Read in all the images
    images = [
        "/images/0-on.svg",
        "/images/0-off.svg",
        "/images/1-on.svg",
        "/images/1-off.svg",
        "/images/2c-on.svg",
        "/images/2c-off.svg",
        "/images/2s-on.svg",
        "/images/2s-off.svg",
        "/images/3-on.svg",
        "/images/3-off.svg",
        "/images/4-on.svg",
        "/images/4-off.svg"
    ];
    wireAssets = new Array(images.length);
    images.forEach(
        function(element, index){
            wireAssets[index] = new Image();
            wireAssets[index].src = element;
        }
    );

    repaint();
    // This gives it 100ms to actualy load the assets
    setInterval(repaint, 100);
});

// TODO replace this with JSON at some point
function toLogicArray(element, className){
    var inputsHTML = Array.from(element.getElementsByClassName(className));
    var inputs = new Array(inputsHTML.lenght);
    inputsHTML.forEach(
        function(input, inputIndex){
            var elementsHTML = Array.from(input.getElementsByTagName("div"));
            inputs[inputIndex] = new Array(elementsHTML.lenght);
            elementsHTML.forEach(
                function(element, elementIndex){
                    inputs[inputIndex][elementIndex] = parseInt(element.innerHTML);
                }
            );
        }
    );
    return inputs;
}

window.addEventListener("resize", function(event){
    board.width = window.innerWidth;
    board.height = window.innerHeight;
    repaint();
});

document.addEventListener("mousedown", function(event){
    mouseX = event.clientX;
    mouseY = event.clientY;
    mouseDown = true;
    currentPiece = board.getPiece(event.clientX, event.clientY);
    board.toogleWire(event.clientX, event.clientY, currentPiece);
    repaint();
});

document.addEventListener("mouseup", function(event){
    mouseDown = false;
    board.setPiece(event.clientX, event.clientY, currentPiece);
    repaint();
});

document.addEventListener("mousemove", function(event){
    if(mouseDown){
        mouseX = event.clientX;
        mouseY = event.clientY;
        board.toogleWire(event.clientX, event.clientY, currentPiece);
        repaint();
    }
});

function repaint(){
    clear(canvas);
    board.draw(canvas);
    if (mouseDown && ![0, null, undefined].includes(currentPiece)) {
        canvas.drawImage(pieceAsset(currentPiece, false), mouseX - (board.unit * 0.6), mouseY - (board.unit * 0.6), board.unit * 1.2, board.unit * 1.2);
    }
}

function clear(canvas){
    if (canvas != null){
        canvas.clearRect(0, 0, window.innerWidth, window.innerHeight);
    }
}
<commit_msg>Fixed over draw<commit_after>class Board {
    // Creates a new board object
    // This is the stuff that should be coming from the view
    constructor(width, height, gameWidth, gameHeight, inputs, outputs, pieces){
        this.layout = new Array(gameWidth);
        for(var i = 0; i < gameWidth; i++){
            this.layout[i] = new Array(gameHeight);
        }
        this.width = width;
        this.height = height;
        this.inputs = inputs;
        this.outputs = outputs;
        this.pieces = pieces;
    }

    // Uses "private" vars so we also calc dim unit size
    set width(width) {
        this._width = width;
        this.updateBoardDim();
    }
    get width() {
        return this._width;
    }
    set height(height) {
        this._height = height;
        this.updateBoardDim();
    }
    get height() {
        return this._height;
    }

    // Dinamicaly set the unit scale of pieces based on the new width and height
    updateBoardDim() {
        // Find a scale unit on the board
        // This is the number of pixels a square takes up
        var unitWidth = (this.width - 20) / this.gameWidth;
        var unitHeight = (this.height - 30) / (this.gameHeight + 1);
        if (unitWidth < unitHeight) {
            this._unit = unitWidth;
        } else {
            this._unit = unitHeight;
        }
    }

    // Returns the private unit calculation
    get unit() {
        return this._unit;
    }

    // Commom values that are needed in a lot of places so can be got
    // as if they were variables themselves
    get boardWidth() { return this.unit * this.gameWidth; }
    get boardHeight() { return this.unit * this.gameHeight; }
    get boardX() { return (this.width - this.boardWidth) / 2; }
    get boardY() { return ((this.height - this.boardHeight - this.unit) / 2) - 5; }
    get gameWidth() { return this.layout.length; }
    get gameHeight() { return this.layout[0].length; }

    // These are common calculations that require extra bits of info such as x and y cords
    getBoardX(x) { return Math.trunc((x - this.boardX) / this.unit); }
    getBoardY(y) { return Math.trunc((y - this.boardY) / this.unit); }
    onBoard(x, y) { return 0 <= y && 0 <= x && y < this.gameHeight && x < this.gameWidth}

    /**
     * Returns a piece from the board and removes it from the board
     * based on x and y cordanate location
     *
     * @param x corordanate on the canvas that was drawn to
     * @param y corordanate on the canvas that was drawn to
     * @return game piece
     */
    getPiece(x, y) {
        // Start by seeing if the piece is in the y height of the dock
        var tmp = (y - this.boardY - this.boardHeight - 10);
        if (tmp > 0 && tmp < this.unit) {
            // Then check to see if there is a piece there for it
            tmp = this.getBoardX(x);
            if (0 <= tmp && tmp < this.pieces.length){
                // If there is remove the piece from the list and return it
                return this.pieces.splice(tmp, 1);
            }
        } else {
            // Start by seing if the x, y is on the board
            var cx = this.getBoardX(x);
            var cy = this.getBoardY(y);
            if (this.onBoard(cx, cy)) {
                // If it is remove it and return it
                tmp = this.layout[cx][cy];
                this.layout[cx][cy] = undefined;
                return tmp;
            }
        }
        return null;
    }

    /**
     * This sets a piece on the board based on screen cords to a piece id that is passed in
     *
     * @param x corodanate on the canvas that is drawn to
     * @param y corodanate on the canvas that is drawn to
     * @param p an id identifying what piece to place
     */
    setPiece(x, y, p) {
        // Make sure there is a piece and its not wire
        if (![0, null, undefined].includes(p)) {
            var cx = this.getBoardX(x);
            var cy = this.getBoardY(y);
            // Check to see if the location is on the board and the board doesn't already have a piece there
            if (this.onBoard(cx, cy) && [0, null, undefined].includes(this.layout[cx][cy])) {
                // If its good add it to the locaiton
                this.layout[cx][cy] = p;
            } else {
                // The spot was invalid so we should add it to the end of the palate so we don't loose it
                this.pieces.push(p);
            }
        }
    }

    /**
     * This is for the drawing of wires on the board
     *
     * @param x corodanate on the canvas that is drawn to
     * @param y corodanate on the canvas that is drawn to
     * @param p an id identifying what piece was last clicked on
     */
    toogleWire(x, y, piece) {
        // If the piece that was given is a wire or nothing
        if ([0, null, undefined].includes(piece)) {
            var cx = this.getBoardX(x);
            var cy = this.getBoardY(y);
            // If the spot was on the board and not a piece
            if (this.onBoard(cx, cy) && [0, null, undefined].includes(this.layout[cx][cy])) {
                // Then set it to the oposite of the piece given
                this.layout[cx][cy] = (piece == 0) ? undefined : 0;
            }
        }
    }

    draw(canvas){
        var color = false;
        for (var x = 0; x < this.gameWidth; x++) {
            if (this.gameWidth % 2 != 0) color = !color;
            for (var y = 0; y < this.gameHeight; y++) {
                color = !color;
                canvas.fillStyle = color ? "#111111" : "#222222";
                canvas.fillRect(
                    this.unit * x + this.boardX,
                    this.unit * y + this.boardY,
                    this.unit,
                    this.unit
                );
                if (this.layout[x][y] == 0) {
                    this.drawWire(x, y, canvas, false);
                } else {
                    var piece = pieceAsset(this.layout[x][y], false);
                    if (piece != null) {
                        canvas.drawImage(
                            pieceAsset(this.layout[x][y], false),
                            this.unit * x + this.boardX,
                            this.unit * y + this.boardY,
                            this.unit,
                            this.unit
                        );
                    }
                }
            }
        }
        for (var x = 0; x < this.gameWidth; x++) {
            color = !color;
            canvas.fillStyle = color ? "#111111" : "#222222";
            canvas.fillRect(
                this.unit * x + this.boardX,
                this.boardHeight + this.boardY + 10,
                this.unit,
                this.unit
            );
            if (x < this.pieces.length) {
                canvas.drawImage(
                    pieceAsset(this.pieces[x], false),
                    this.unit * x + this.boardX,
                    this.boardHeight + this.boardY + 10,
                    this.unit,
                    this.unit
                );
            }
        }
    }

    drawWire(x , y, canvas, bool) {
        var sides = new Array(4);
        // Left
        sides[0] = (x > 1 && [0, 1, 2, 3].includes(this.layout[x - 1][y]));
        // Right
        sides[1] = (x < this.gameWidth - 1 && [0, 3].includes(this.layout[x + 1][y]));
        // Top
        sides[2] = (y > 0 && [0, 1, 2, 3].includes(this.layout[x][y - 1]));
        // Bottom
        sides[3] = (y < this.gameHeight - 1 && [0, 1, 2, 3].includes(this.layout[x][y + 1]));

        for (var side in sides) {
            console.log(side ? "1" : "0");
        }

        // This is to test the sides
        console.log(sides);
        return sides;
    }
}

var board;
var canvas;
var mouseDown;
var mouseX, mouseY;
var pieceAssets;
var wireAssets;
var currentPiece;

// This will return the image that is associated with a piece number then the given state if on or off
function pieceAsset(num, bool) {
    if (num > 0) return pieceAssets[(2 * num) - (bool ? 2 : 1)];
}

document.addEventListener("DOMContentLoaded", function(event){
    canvas = document.getElementsByTagName("canvas")[0].getContext("2d");
    isMouseDown = false;
    var gameBoard = document.getElementById("game_board");
    var inputs = toLogicArray(gameBoard, "input");
    var outputs = toLogicArray(gameBoard, "output");
    var piecesHTML = Array.from(gameBoard.getElementsByClassName("pieces")[0].getElementsByTagName("div"));
    var pieces = new Array(piecesHTML.length);
    piecesHTML.forEach(
        function(element, index){
            pieces[index] = parseInt(element.innerHTML);
        }
    );

    board = new Board(
        window.innerWidth,
        window.innerHeight,
        parseInt(gameBoard.getElementsByClassName("width")[0].innerHTML),
        parseInt(gameBoard.getElementsByClassName("height")[0].innerHTML),
        inputs,
        outputs,
        pieces
    );
    console.log(board);

    // Read in all the images
    var images = [
        "/images/and-on.svg",
        "/images/and-off.svg",
        "/images/or-on.svg",
        "/images/or-off.svg",
        "/images/not-on.svg",
        "/images/not-off.svg"
    ];
    pieceAssets = new Array(images.length);
    images.forEach(
        function(element, index){
            pieceAssets[index] = new Image();
            pieceAssets[index].src = element;
        }
    );

    // Read in all the images
    images = [
        "/images/0-on.svg",
        "/images/0-off.svg",
        "/images/1-on.svg",
        "/images/1-off.svg",
        "/images/2c-on.svg",
        "/images/2c-off.svg",
        "/images/2s-on.svg",
        "/images/2s-off.svg",
        "/images/3-on.svg",
        "/images/3-off.svg",
        "/images/4-on.svg",
        "/images/4-off.svg"
    ];
    wireAssets = new Array(images.length);
    images.forEach(
        function(element, index){
            wireAssets[index] = new Image();
            wireAssets[index].src = element;
        }
    );

    repaint();
    // This gives it 100ms to actualy load the assets
    setTimeout(repaint, 10);
});

// TODO replace this with JSON at some point
function toLogicArray(element, className){
    var inputsHTML = Array.from(element.getElementsByClassName(className));
    var inputs = new Array(inputsHTML.lenght);
    inputsHTML.forEach(
        function(input, inputIndex){
            var elementsHTML = Array.from(input.getElementsByTagName("div"));
            inputs[inputIndex] = new Array(elementsHTML.lenght);
            elementsHTML.forEach(
                function(element, elementIndex){
                    inputs[inputIndex][elementIndex] = parseInt(element.innerHTML);
                }
            );
        }
    );
    return inputs;
}

window.addEventListener("resize", function(event){
    board.width = window.innerWidth;
    board.height = window.innerHeight;
    repaint();
});

document.addEventListener("mousedown", function(event){
    mouseX = event.clientX;
    mouseY = event.clientY;
    mouseDown = true;
    currentPiece = board.getPiece(event.clientX, event.clientY);
    board.toogleWire(event.clientX, event.clientY, currentPiece);
    repaint();
});

document.addEventListener("mouseup", function(event){
    mouseDown = false;
    board.setPiece(event.clientX, event.clientY, currentPiece);
    repaint();
});

document.addEventListener("mousemove", function(event){
    if(mouseDown){
        mouseX = event.clientX;
        mouseY = event.clientY;
        board.toogleWire(event.clientX, event.clientY, currentPiece);
        repaint();
    }
});

function repaint(){
    console.log("This was called");
    clear(canvas);
    board.draw(canvas);
    if (mouseDown && ![0, null, undefined].includes(currentPiece)) {
        canvas.drawImage(pieceAsset(currentPiece, false), mouseX - (board.unit * 0.6), mouseY - (board.unit * 0.6), board.unit * 1.2, board.unit * 1.2);
    }
}

function clear(canvas){
    if (canvas != null){
        canvas.clearRect(0, 0, window.innerWidth, window.innerHeight);
    }
}
<|endoftext|>


<commit_before>'use strict';

var layoutNarrator = new Narrator();
var layoutUser = new User ();

layoutNarrator.saveGuest = function() {
    this.guest = layoutUser;
};

layoutNarrator.getGuest();
layoutNarrator.popContent();

layoutNarrator.getFontSize = function () {
    var fontSizeProp = document.getElementById('input').value;
    this.guest.fontSize = fontSizeProp;
};
layoutNarrator.getPFont = function () {
    var pFontProp = document.getElementById('input').value;
    this.guest.pFont = pFontProp;
};
layoutNarrator.getMargin = function () {
    var marginProp = document.getElementById('input').value;
    this.guest.margin = marginProp;
};
layoutNarrator.getFloat = function () {
    var floatProp = document.getElementById('input').value;
    this.guest.float = floatProp;
};
layoutNarrator.talkAt = [];
layoutNarrator.listenTo = [];
layoutNarrator.moveOn = [];

// layoutNarrator.talkAtUser = function (speech) {
//     this.talkLoc.innerText = speech;
// };

// var submitButton = document.getElementById('submit');
// submitButton.addEventListener('click', submitHandler);

// function submitHandler() {
// /* Submit Button Transition to next page */
//     event.preventDefault();
//     layoutUser.userSubmission();
//     layoutNarrator.saveGuest();
//     layoutNarrator.locallyStoreUser();
//     window.location.href= './color.html';
// }<commit_msg>instantiate layout phase objects, though not entirely accurately<commit_after>'use strict';

var layoutNarrator = new Narrator();
var layoutUser = new User ();

layoutNarrator.saveGuest = function() {
    this.guest = layoutUser;
};

layoutNarrator.getGuest();
layoutNarrator.popContent();

//edit these
layoutNarrator.getFontSize = function () {
    var fontSizeProp = document.getElementById('input').value;
    this.guest.fontSize = fontSizeProp;
};
layoutNarrator.getPFont = function () {
    var pFontProp = document.getElementById('input').value;
    this.guest.pFont = pFontProp;
};
layoutNarrator.getMargin = function () {
    var marginProp = document.getElementById('input').value;
    this.guest.margin = marginProp;
};
layoutNarrator.getFloat = function () {
    var floatProp = document.getElementById('input').value;
    this.guest.float = floatProp;
};

var layoutPhaseOne = new Phase('Great! Look how cute that (marsupial) is. We’ve added the content, so, let’s look at the layout. We\'ll go through the whole process and if you don\'t like the formatting, you can hit the reset button to start over. Now, we\’re going to add borders to all of our HTML elements. This will help you better understand what is known as the Box Model.', 'Okay', null, null, null);
var layoutPhaseTwo = new Phase('Basically every element on your webpage is in a box. We’ve added these borders to show you how your elements interact with one another (don’t worry, we’ll get rid of them after we’re done formatting the page). We want our web pages to look appealing by giving them some white space. So, let’s add some margins, make your choice below.', 'Small', null, null, 'Large');
var layoutPhaseThree = new Phase('Doesn’t that look better? We’ve centered the content on our page, utilizing white space. We want our header to really stand out. Let\'s make it a little bigger by increasing the pixel size. Right now our font is set to 24px. Try typing in 32 for the pixel value of the header.', 'Submit', 'font size', null, null);
var layoutPhaseFour = new Phase('Great, our header is set. When you have images with text that relates to it, you want to access that visually. The float property will allow us to do this by putting the text next to the image. Click on one of the buttons and see what happens!', 'Left', null, null, 'Right');
var layoutPhaseFive = new Phase('Now that our content is formatted, let\'s choose the font. Currently, the paragraph font is (Times) and we want to make it sans-serif to match the rest of our page. Select a font from the menu below.', 'Submit', 'font dropdown', null, null );
var layoutPhaseSix = new Phase('We\'re almost there! The last step is to create a navigation bar from the list that you created on the last page. First, we need to get rid of the bullet points, which is a list style. In the field below, type \'none\' to remove the bullet points.', 'Submit', 'inputNone', null, null);
var layoutPhaseSeven = new Phase('Lastly, we want a horizontal navigation bar. Right now the list is stacked, which is a block dislay. Let\'s change the display to inline.', 'Submit', null, null, null);
var layoutPhaseEight = new Phase('Great job! We\'ve formatted our page. Let\'s add some color to make it pop.', 'Move on', null, null, 'Reset');

// //8 items in here
// layoutNarrator.talkAt = ['Great! Look how cute that (marsupial) is. We’ve added the content, so, let’s look at the layout. We\'ll go through the whole process and if you don\'t like the formatting, you can hit the reset button to start over. Now, we\’re going to add borders to all of our HTML elements. This will help you better understand what is known as the Box Model.', 'Basically every element on your webpage is in a box. We’ve added these borders to show you how your elements interact with one another (don’t worry, we’ll get rid of them after we’re done formatting the page). We want our web pages to look appealing by giving them some white space. So, let’s add some margins, make your choice below.', 'Doesn’t that look better? We’ve centered the content on our page, utilizing white space. We want our header to really stand out. Let\'s make it a little bigger by increasing the pixel size. Right now our font is set to 24px. Try typing in 32 for the pixel value of the header.', 'Great, our header is set. When you have images with text that relates to it, you want to access that visually. The float property will allow us to do this by putting the text next to the image. Click on one of the buttons and see what happens!', 'Now that our content is formatted, let\'s choose the font. Currently, the paragraph font is (Times) and we want to make it sans-serif to match the rest of our page. Select a font from the menu below.', 'We\'re almost there! The last step is to create a navigation bar from the list that you created on the last page. First, we need to get rid of the bullet points, which is a list style. In the field below, type \'none\' to remove the bullet points.', 'Lastly, we want a horizontal navigation bar. Right now the list is stacked, which is a block dislay. Let\'s change the display to inline.', 'Great job! We\'ve formatted our page. Let\'s add some color to make it pop.'];

// layoutNarrator.listenTo = ['',];

// layoutNarrator.moveOn = ['Okay',];

// for (var i = 0; i < layoutNarrator.talkAt.length; i++) {
//     for (var j = 0; j < layoutNarrator.listenTo.length; j++) {
//         for (var k = 0; layoutNarrator.moveOn.length; k++) {

//         }
//     }
// }

// layoutNarrator.talkAtUser = function (speech) {
//     this.talkLoc.innerText = speech;
// };

// var submitButton = document.getElementById('submit');
// submitButton.addEventListener('click', submitHandler);

// function submitHandler() {
// /* Submit Button Transition to next page */
//     event.preventDefault();
//     layoutUser.userSubmission();
//     layoutNarrator.saveGuest();
//     layoutNarrator.locallyStoreUser();
//     window.location.href= './color.html';
// }<|endoftext|>


<commit_before>var configs = {

  // filter with
    // filters: [
    // ]
  // per branch (if nested) or total:
    // variables: {
    //  key: varFunc,
    //  key: varFunc
    // }
  // nest with
    // nest by [keyFunc1, keyFunc2]
    // postNest: mapFunc
    // don't flatten: false/true
  // if flattened or unnested
    // sortBy: [
    //  -key,
    //  key,
    //  sortFunc,
    // ]

  'uof-by-year': {
    chartType: 'lineChart',
    keyFunc: function(d){ return d.date.getFullYear(); },
    dataMapAdjust: addMissingYears,
    x: 'year',
    xFunc: function(b){ return b[0].date.getFullYear(); },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'uof-type-of-call': {
    chartType: 'flagHistogram',
    filter: last12Months,
    keyFunc: function(d){ return d.serviceType; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].serviceType; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'uof-reason': {
    chartType: 'flagHistogram',
    filter: last12Months,
    keyFunc: function(d){ return d.useOfForceReason; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].useOfForceReason; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'uof-citizen-weapon': {
    chartType: 'flagHistogram',
    filter: last12Months,
    keyFunc: function(d){ return d.residentWeaponUsed; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].residentWeaponUsed; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'uof-map': {
    chartType: 'map',
    filter: function(b){
      return last12Months(b).filter(function(d){
       if( d.censusTract ){ return true; } else { return false; }
      });
    },
    dontFlatten: true,
    keyFunc: function(d){ return d.censusTract; },
    x: 'censusTract',
    xFunc: function(b){ return b[0].censusTract; },
    y: 'count',
    yFunc: function(b){ return b.length; }
    },

  'uof-by-shift': {
    chartType: 'flagHistogram',
    filter: last12Months,
    keyFunc: function(d){ return d.shift; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].shift; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'uof-by-inc-district': {
    chartType: 'flagHistogram',
    filter: last12Months,
    keyFunc: function(d){ return d.district; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].district; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'uof-force-type': {
    chartType: 'flagHistogram',
    filter: last12Months,
    keyFunc: function(d){ return d.officerForceType; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].officerForceType; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'uof-officer-injuries': {
    chartType: 'percent',
    filter: last12Months,
    keyFunc: function(d){ return d.officerInjured; },
    x: "injured",
    xFunc: function (b) { return b.length; },
    y: "hospitalized",
    yFunc: function (b){
      var hospitalizations = b.filter(function(d){
        return d.officerHospitalized == "true";
      });
      return hospitalizations.length;
    },
    dataMapAdjust: function (dataMap){
      dataMap.remove("");
      dataMap.remove("false");
      dataMap.get("true").total = last12Months(allRows).length;
    },
    },

  'uof-resident-injuries': {
    chartType: 'percent',
    filter: last12Months,
    keyFunc: function(d){ return d.residentInjured; },
    x: "injured",
    xFunc: function (b) { return b.length; },
    y: "hospitalized",
    yFunc: function (b){
      var hospitalizations = b.filter(function(d){
        return d.residentHospitalized == "true";
      });
      return hospitalizations.length;
    },
    dataMapAdjust: function (dataMap){
      dataMap.remove("");
      dataMap.remove("false");
      dataMap.get("true").total = last12Months(allRows).length;
    },
    },

  'uof-dispositions': {
    chartType: 'flagHistogram',
    filter: last12Months,
    keyFunc: function(d){ return d.disposition; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].disposition; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'uof-dispositions-outcomes': {
    },

  'pd-resident-demographics': {
    chartType: 'flagHistogram',
    },

  'uof-race': {
    chartType: 'matrix',
    },

  'uof-per-officer': {
    chartType: 'flagHistogram',
    },

  'uof-officer-experience': {
    chartType: 'flagHistogram',
    filter: last12Months,
    keyFunc: function(d){ return experienceBuckets(d.officerYearsOfService); },
    sortWith: function(d){ return parseInt(d.years); },
    x: 'years',
    xFunc: function(b){ return experienceBuckets(b[0].officerYearsOfService); },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'complaints-by-year': {
    chartType: 'lineChart',
    keyFunc: function(d){ return d.date.getFullYear(); },
    dataMapAdjust: addMissingYears,
    x: 'year',
    xFunc: function(b){ return b[0].date.getFullYear(); },
    y: 'count',
    yFunc: function(b){ return b.length; },
    postDraw: function(){
      // add note to final year
    }
    },

  'complaints-by-month': {
    chartType: 'lineChart',
    filter: function(rows){
      return allegationsToComplaints(last12Months(rows));
    },
    keyFunc: function(d){ return d3.time.format('%Y %m')(d.date); },
    sortWith: function(d){ return d.month; },
    x: 'month',
    xFunc: function(b){ return d3.time.month.floor(b[0].date); },
    xTickFormat: function(d){
      var fmt = d3.time.format('%b %Y');
      return fmt(new Date(d));
    },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'complaints-by-allegation': {
    filter: last12Months,
    chartType: 'flagHistogram',
    keyFunc: function(d){ return d.allegation; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].allegation; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'complaints-by-allegation-type': {
    filter: last12Months,
    chartType: 'flagHistogram',
    keyFunc: function(d){ return d.allegationType; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].allegationType; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'complaints-by-precinct': {
    filter: last12Months,
    chartType: 'flagHistogram',
    keyFunc: function(d){ return d.precinct; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].precinct; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'complaints-by-disposition': {
    filter: last12Months,
    chartType: 'flagHistogram',
    keyFunc: function(d){ return d.disposition; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].disposition; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'complaints-by-demographic': {
    filter: last12Months,
    chartType: 'matrix',
    dataFunc: raceMatrix,
  },

  'complaints-by-officer': {
    chartType: 'flagHistogram',
    x: "label",
    y: "count",
    dataFunc: officerComplaintsCount,
  },

};

d3.csv(
  csv_url,
  function(error, rows){
    // parse the raw csv data
    var parsed_rows = parseData(rows);
    allRows = rows;
    console.log("parsed data", parsed_rows);

    // deal with each chart configuration
    charts.forEach(function(name){

      // get configuration
      var config = configs[name];

      if( config.chartType ){
        // get class name for parent div
        config.parent = '.' + name;
        console.log("making", config.parent, "with", config);
        drawChart(parsed_rows, config);
      }

    });
  }
);
<commit_msg>renames<commit_after>var configs = {

  // filter with
    // filters: [
    // ]
  // per branch (if nested) or total:
    // variables: {
    //  key: varFunc,
    //  key: varFunc
    // }
  // nest with
    // nest by [keyFunc1, keyFunc2]
    // postNest: mapFunc
    // don't flatten: false/true
  // if flattened or unnested
    // sortBy: [
    //  -key,
    //  key,
    //  sortFunc,
    // ]

  'uof-by-year': {
    chartType: 'lineChart',
    keyFunc: function(d){ return d.date.getFullYear(); },
    dataMapAdjust: addMissingYears,
    x: 'year',
    xFunc: function(b){ return b[0].date.getFullYear(); },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'uof-type-of-call': {
    chartType: 'flagHistogram',
    filter: last12Months,
    keyFunc: function(d){ return d.serviceType; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].serviceType; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'uof-reason': {
    chartType: 'flagHistogram',
    filter: last12Months,
    keyFunc: function(d){ return d.useOfForceReason; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].useOfForceReason; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'uof-citizen-weapon': {
    chartType: 'flagHistogram',
    filter: last12Months,
    keyFunc: function(d){ return d.residentWeaponUsed; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].residentWeaponUsed; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'uof-map': {
    chartType: 'map',
    filter: function(b){
      return last12Months(b).filter(function(d){
       if( d.censusTract ){ return true; } else { return false; }
      });
    },
    dontFlatten: true,
    keyFunc: function(d){ return d.censusTract; },
    x: 'censusTract',
    xFunc: function(b){ return b[0].censusTract; },
    y: 'count',
    yFunc: function(b){ return b.length; }
    },

  'uof-by-shift': {
    chartType: 'flagHistogram',
    filter: last12Months,
    keyFunc: function(d){ return d.shift; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].shift; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'uof-by-inc-district': {
    chartType: 'flagHistogram',
    filter: last12Months,
    keyFunc: function(d){ return d.district; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].district; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'uof-force-type': {
    chartType: 'flagHistogram',
    filter: last12Months,
    keyFunc: function(d){ return d.officerForceType; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].officerForceType; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'uof-officer-injuries': {
    chartType: 'percent',
    filter: last12Months,
    keyFunc: function(d){ return d.officerInjured; },
    x: "injured",
    xFunc: function (b) { return b.length; },
    y: "hospitalized",
    yFunc: function (b){
      var hospitalizations = b.filter(function(d){
        return d.officerHospitalized == "true";
      });
      return hospitalizations.length;
    },
    dataMapAdjust: function (dataMap){
      dataMap.remove("");
      dataMap.remove("false");
      dataMap.get("true").total = last12Months(allRows).length;
    },
    },

  'uof-resident-injuries': {
    chartType: 'percent',
    filter: last12Months,
    keyFunc: function(d){ return d.residentInjured; },
    x: "injured",
    xFunc: function (b) { return b.length; },
    y: "hospitalized",
    yFunc: function (b){
      var hospitalizations = b.filter(function(d){
        return d.residentHospitalized == "true";
      });
      return hospitalizations.length;
    },
    dataMapAdjust: function (dataMap){
      dataMap.remove("");
      dataMap.remove("false");
      dataMap.get("true").total = last12Months(allRows).length;
    },
    },

  'uof-dispositions': {
    chartType: 'flagHistogram',
    filter: last12Months,
    keyFunc: function(d){ return d.finding; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].finding; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'uof-dispositions-outcomes': {
    },

  'pd-resident-demographics': {
    chartType: 'flagHistogram',
    },

  'uof-race': {
    chartType: 'matrix',
    },

  'uof-per-officer': {
    chartType: 'flagHistogram',
    },

  'uof-officer-experience': {
    chartType: 'flagHistogram',
    filter: last12Months,
    keyFunc: function(d){ return experienceBuckets(d.officerYearsOfService); },
    sortWith: function(d){ return parseInt(d.years); },
    x: 'years',
    xFunc: function(b){ return experienceBuckets(b[0].officerYearsOfService); },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'complaints-by-year': {
    chartType: 'lineChart',
    keyFunc: function(d){ return d.date.getFullYear(); },
    dataMapAdjust: addMissingYears,
    x: 'year',
    xFunc: function(b){ return b[0].date.getFullYear(); },
    y: 'count',
    yFunc: function(b){ return b.length; },
    postDraw: function(){
      // add note to final year
    }
    },

  'complaints-by-month': {
    chartType: 'lineChart',
    filter: function(rows){
      return allegationsToComplaints(last12Months(rows));
    },
    keyFunc: function(d){ return d3.time.format('%Y %m')(d.date); },
    sortWith: function(d){ return d.month; },
    x: 'month',
    xFunc: function(b){ return d3.time.month.floor(b[0].date); },
    xTickFormat: function(d){
      var fmt = d3.time.format('%b %Y');
      return fmt(new Date(d));
    },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'complaints-by-allegation': {
    filter: last12Months,
    chartType: 'flagHistogram',
    keyFunc: function(d){ return d.allegation; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].allegation; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'complaints-by-allegation-type': {
    filter: last12Months,
    chartType: 'flagHistogram',
    keyFunc: function(d){ return d.allegationType; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].allegationType; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'complaints-by-precinct': {
    filter: last12Months,
    chartType: 'flagHistogram',
    keyFunc: function(d){ return d.district; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].district; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'complaints-by-disposition': {
    filter: last12Months,
    chartType: 'flagHistogram',
    keyFunc: function(d){ return d.finding; },
    sortWith: function(d){ return -d.count; },
    x: 'type',
    xFunc: function(b){ return b[0].finding; },
    y: 'count',
    yFunc: function(b){ return b.length; },
    },

  'complaints-by-demographic': {
    filter: last12Months,
    chartType: 'matrix',
    dataFunc: raceMatrix,
  },

  'complaints-by-officer': {
    chartType: 'flagHistogram',
    x: "label",
    y: "count",
    dataFunc: officerComplaintsCount,
  },

};

d3.csv(
  csv_url,
  function(error, rows){
    // parse the raw csv data
    var parsed_rows = parseData(rows);
    allRows = rows;
    console.log("parsed data", parsed_rows);

    // deal with each chart configuration
    charts.forEach(function(name){

      // get configuration
      var config = configs[name];

      if( config.chartType ){
        // get class name for parent div
        config.parent = '.' + name;
        console.log("making", config.parent, "with", config);
        drawChart(parsed_rows, config);
      }

    });
  }
);
<|endoftext|>


<commit_before>function getOffset( el ) {
  var _x = 0, _y = 0

  while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
    _x += el.offsetLeft - el.scrollLeft
    _y += el.offsetTop - el.scrollTop
    el = el.offsetParent
  }
  return { top: _y, left: _x }
}

var offset = getOffset(document.getElementById('chart'))

var w = window.innerWidth - offset.left,
    h = window.innerHeight - offset.top,
    fill = d3.scale.category20()

var cp = d3.select("#chart").append("div")
           .attr("id", "controlpanel")

var btns = cp.append("div")
           .attr("class", "btn-group")

btns.append("button")
    .attr("class", "btn active left")
    .attr("value", "clients")
    .text("Clients")
    .on("click", update_graph)

btns.append("button")
    .attr("class", "btn active right")
    .attr("value", "vpn")
    .text("VPN")
    .on("click", update_graph)

cp.append("label")
  .text("Knoten hervorheben:")

cp.append("br")

cp.append("input")
  .on("keyup", function(){show_node(this.value)})
  .on("change", function(){show_node(this.value)})

function show_node(mac) {
  d3.selectAll("#chart .node")
    .classed("marked", false)

  if (mac.length == 0)
    return

  d3.selectAll("#chart .node")
    .each( function(d) {
      if (d.id == mac)
        d3.select(this)
          .classed("marked", true)
    })
}

var hashstr = window.location.hash.substring(1)

function isConnected(a, b) {
  return linkedByIndex[a.index + "," + b.index] ||
         linkedByIndex[b.index + "," + a.index] ||
         a.index == b.index
}

function fade(opacity) {
  return function(d) {
    vis.selectAll("g.node")
       .style("stroke-opacity", function(o) {
         var connected = isConnected(d, o)

         if (connected && opacity != 1)
           d3.select(this)
             .classed("highlight", true)
         else
           d3.select(this)
             .classed("highlight", false)

         thisOpacity = connected?1:opacity
         this.setAttribute('fill-opacity', thisOpacity)
         return thisOpacity
       })

    vis.selectAll(".link *")
       .style("stroke-opacity", function(o) {
         return o.source === d || o.target === d ? 1 : opacity
       })
  }
}

function show_node_info(d) {
  if (typeof nodeinfo !== 'undefined')
    nodeinfo.remove();

  nodeinfo = d3.select("#chart")
               .append("div")
               .attr("id", "nodeinfo")
  nodeinfo.append("h1")
          .text(d.name)
  nodeinfo.append("p")
          .text("primary: " + d.id)
  nodeinfo.append("p")
          .text("macs: " + d.macs)
  nodeinfo.append("p")
          .text(d.gps)
}

function update_graph() {
  jQuery(this).toggleClass("active")
  var value = jQuery(this).val()
  visible[value] = jQuery(this).hasClass("active")
  update(data)
}

var vis = d3.select("#chart").append("svg")
            .attr("width", w)
            .attr("height", h)

vis.append("g").attr("class", "links")

vis.append("g").attr("class", "nodes")

var linkedByIndex

var force = d3.layout.force()
              .charge(-100)
              .gravity(0.02)
              .friction(0.75)
              .theta(0.1)
              .size([w, h])
              .linkDistance(function (d) { return d.distance; })
              .linkStrength(function (d) { return d.strength; })

force.on("tick", function() {
  var size = force.size()
  var nodes = force.nodes()
  var n = nodes.length
  for (i = 0; i < n; i++) {
    var o = nodes[i]
    if (!o.fixed) {
      if (o.x < 0) o.x = 10
      if (o.x > size[0]) o.x = size[0] - 10
      if (o.y < 0) o.y = 10
      if (o.y > size[1]) o.y = size[1] - 10
    }
  }

  var link = vis.selectAll(".link")

  link.selectAll("line")
      .attr("x1", function(d) { return d.source.x })
      .attr("y1", function(d) { return d.source.y })
      .attr("x2", function(d) { return d.target.x })
      .attr("y2", function(d) { return d.target.y })

  link.selectAll(".label")
      .attr("transform", function(d) {
        π = Math.PI
        Δx = d.source.x - d.target.x
        Δy = d.source.y - d.target.y
        m = Δy/Δx
        α = Math.atan(m)
        α += Δx<0?π:0
        sin = Math.sin(α)
        cos = Math.cos(α)
        x = (Math.min(d.source.x, d.target.x) + Math.abs(Δx) / 2)
        y = (Math.min(d.source.y, d.target.y) + Math.abs(Δy) / 2)
        return "matrix(" + [cos, sin, -sin, cos, x, y].join(",") + ")"
      })

  vis.selectAll(".node").attr("transform", function(d) {
    return "translate(" + d.x + "," + d.y + ")";
  })
})

var data

var visible = {clients: true, vpn: true}

d3.json("nodes.json", function(json) {
  data = json

  // start force-layout early as it will
  // replace indexes to source and target
  // in data.links with actual elements from
  // nodes
  force.nodes(data.nodes)
       .links(data.links)
       .start()

  data.links.forEach(function(d) {
    var node, other

    if (d.source.group == 2) {
      node = d.target;
      other = d.source;
    }

    if (d.target.group == 2) {
      node = d.source;
      other = d.target;
    }

    if (node) {
      if (node.uplinks === undefined)
        node.uplinks = new Array();

      node.uplinks.push(other);
    }
  })

  update(data)
})

function update(src) {
  var links = force.links(data.links
                   .filter(function (d) {
                     if (!visible.clients && (d.source.group == 3 || d.target.group == 3))
                       return false

                     if (!visible.vpn && (d.source.group == 2 || d.target.group == 2))
                       return false

                     return true
                   })
               ).links()

  var link = vis.select("g.links")
                .selectAll("g.link")
                .data(links, function(d) {
                    return d.id
                })

  var linkEnter = link.enter().append("g")
                      .attr("class", "link")

  linkEnter.append("line")
    .style("stroke-width", function(d) {
      return Math.min(1, d.strength * 2)
    })

  linkEnter.filter(function (d) {
        return d.quality != "TT" && d.quality != "1.000"
      })
      .append("path")
      .attr("class", "label")
      .attr("d", d3.svg.zigzag()
                       .amplitude(function (d) {
                         return Math.pow((1 - 1/d.quality), 0.5) * 8;
                       })
                       .len(30)
                       .angularFrequency(4)
      )

  link.exit().remove()

  var nodes = force.nodes(data.nodes
                .filter(function (d) {
                  if (!visible.vpn && d.group == 2)
                    return false

                  if (!visible.vpn && d.group == 3 && d.uplinks)
                    return false

                  if (!visible.clients && d.group == 3)
                    return false

                  return true
                })
              ).nodes()

  var node = vis.select("g.nodes")
                .selectAll("g.node")
                .data(nodes,
                  function(d) {
                    return d.id
                  }
                )
  var nodeEnter = node.enter().append("g")
                .attr("class", "node")
                .on("mouseover", fade(.2))
                .on("mouseout", fade(1))
                .on("click", show_node_info)
                .call(force.drag)

  nodeEnter.append("ellipse")
    .attr("rx", function(d) {
      if (d.group == 3) return 4
      else return Math.max(10, d.name.length * 5)
    })
    .attr("ry", function(d) {
      if (d.group == 3) return 4
      else return 10
    })
    .style("fill", function(d) {
      if (d.group == 3) return fill(d.group)
      else return ""
    })
    .style("stroke", function(d) {
      return fill(d.group)
    })

  nodeEnter.filter(function(d) {
      return d.group != 3
    })
    .append("text")
    .attr("text-anchor", "middle")
    .attr("y", "4px")
    .text(function(d) { return d.name })

  nodeEnter.append("title")
    .text(function(d) { return d.macs })

  if (!visible.vpn) {
    var uplink_info = node.filter(function (d) {
      if (d.uplinks !== undefined)
        return d.uplinks.length > 0
      else
        return false
    })
    .append("g")
    .attr("class", "uplinks")

    uplink_info.append("path")
      .attr("d","m -2.8850049,-13.182327"
          + "c 7.5369165,0.200772 12.1529864,-1.294922 12.3338513,-10.639456"
          + "l 2.2140476,1.018191 -3.3137621,-5.293097 -3.2945999,5.20893 2.4339957,-0.995747"
          + "c -0.4041883,5.76426 -1.1549641,10.561363 -10.3735326,10.701179 z")
      .style("fill", "#333")

    uplink_info.append("text")
      .attr("text-anchor", "middle")
      .attr("y", 3 - 20)
      .text(function (d) {return d.uplinks.length})
  } else {
    node.selectAll(".uplinks").remove()
  }

  node.exit().remove()

  force.start()

  linkedByIndex = {}

  links.forEach(function(d) {
    linkedByIndex[d.source.index + "," + d.target.index] = 1
  })

  if (hashstr.length != 0)
    show_node(hashstr)
}
<commit_msg>experimental update<commit_after>function getOffset( el ) {
  var _x = 0, _y = 0

  while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
    _x += el.offsetLeft - el.scrollLeft
    _y += el.offsetTop - el.scrollTop
    el = el.offsetParent
  }
  return { top: _y, left: _x }
}

var offset = getOffset(document.getElementById('chart'))

var w = window.innerWidth - offset.left,
    h = window.innerHeight - offset.top,
    fill = d3.scale.category20()

var cp = d3.select("#chart").append("div")
           .attr("id", "controlpanel")

cp.append("button")
    .attr("class", "btn")
    .attr("value", "reload")
    .text("Aktualisieren")
    .on("click", reload)

var btns = cp.append("div")
           .attr("class", "btn-group")

btns.append("button")
    .attr("class", "btn active left")
    .attr("value", "clients")
    .text("Clients")
    .on("click", update_graph)

btns.append("button")
    .attr("class", "btn active right")
    .attr("value", "vpn")
    .text("VPN")
    .on("click", update_graph)

cp.append("label")
  .text("Knoten hervorheben:")

cp.append("br")

cp.append("input")
  .on("keyup", function(){show_node(this.value)})
  .on("change", function(){show_node(this.value)})

function show_node(mac) {
  d3.selectAll("#chart .node")
    .classed("marked", false)

  if (mac.length == 0)
    return

  d3.selectAll("#chart .node")
    .each( function(d) {
      if (d.id == mac)
        d3.select(this)
          .classed("marked", true)
    })
}

var hashstr = window.location.hash.substring(1)

function isConnected(a, b) {
  return linkedByIndex[a.index + "," + b.index] ||
         linkedByIndex[b.index + "," + a.index] ||
         a.index == b.index
}

function fade(opacity) {
  return function(d) {
    vis.selectAll("g.node")
       .style("stroke-opacity", function(o) {
         var connected = isConnected(d, o)

         if (connected && opacity != 1)
           d3.select(this)
             .classed("highlight", true)
         else
           d3.select(this)
             .classed("highlight", false)

         thisOpacity = connected?1:opacity
         this.setAttribute('fill-opacity', thisOpacity)
         return thisOpacity
       })

    vis.selectAll(".link *")
       .style("stroke-opacity", function(o) {
         return o.source === d || o.target === d ? 1 : opacity
       })
  }
}

function show_node_info(d) {
  if (typeof nodeinfo !== 'undefined')
    nodeinfo.remove();

  nodeinfo = d3.select("#chart")
               .append("div")
               .attr("id", "nodeinfo")
  nodeinfo.append("h1")
          .text(d.name)
  nodeinfo.append("p")
          .text("primary: " + d.id)
  nodeinfo.append("p")
          .text("macs: " + d.macs)
  nodeinfo.append("p")
          .text(d.gps)
}

function update_graph() {
  jQuery(this).toggleClass("active")
  var value = jQuery(this).val()
  visible[value] = jQuery(this).hasClass("active")
  update()
}

var vis = d3.select("#chart").append("svg")
            .attr("width", w)
            .attr("height", h)

vis.append("g").attr("class", "links")

vis.append("g").attr("class", "nodes")

var linkedByIndex

var force = d3.layout.force()
              .charge(-100)
              .gravity(0.02)
              .friction(0.75)
              .theta(0.1)
              .size([w, h])
              .linkDistance(function (d) { return d.distance; })
              .linkStrength(function (d) { return d.strength; })

force.on("tick", function() {
  var size = force.size()
  var nodes = force.nodes()
  var n = nodes.length
  for (i = 0; i < n; i++) {
    var o = nodes[i]
    if (!o.fixed) {
      if (o.x < 0) o.x = 10
      if (o.x > size[0]) o.x = size[0] - 10
      if (o.y < 0) o.y = 10
      if (o.y > size[1]) o.y = size[1] - 10
    }
  }

  var link = vis.selectAll(".link")

  link.selectAll("line")
      .attr("x1", function(d) { return d.source.x })
      .attr("y1", function(d) { return d.source.y })
      .attr("x2", function(d) { return d.target.x })
      .attr("y2", function(d) { return d.target.y })

  link.selectAll(".label")
      .attr("transform", function(d) {
        π = Math.PI
        Δx = d.source.x - d.target.x
        Δy = d.source.y - d.target.y
        m = Δy/Δx
        α = Math.atan(m)
        α += Δx<0?π:0
        sin = Math.sin(α)
        cos = Math.cos(α)
        x = (Math.min(d.source.x, d.target.x) + Math.abs(Δx) / 2)
        y = (Math.min(d.source.y, d.target.y) + Math.abs(Δy) / 2)
        return "matrix(" + [cos, sin, -sin, cos, x, y].join(",") + ")"
      })

  vis.selectAll(".node").attr("transform", function(d) {
    return "translate(" + d.x + "," + d.y + ")";
  })
})

var data

var visible = {clients: true, vpn: true}

function reload() {
  d3.json("nodes.json", function(json) {

    json.links.forEach( function(d) {
      if (typeof d.source == "number") d.source = json.nodes[d.source];
      if (typeof d.target == "number") d.target = json.nodes[d.target];
    })

    json.links.forEach(function(d) {
      var node, other

      if (d.source.group == 2) {
        node = d.target;
        other = d.source;
      }

      if (d.target.group == 2) {
        node = d.source;
        other = d.target;
      }

      if (node) {
        if (node.uplinks === undefined)
          node.uplinks = new Array();

        node.uplinks.push(other);
      }
    })

    // update existing nodes with new info
    json.nodes.forEach(function(d, i) {
      var n
      force.nodes().forEach(function(x) {if (x.id == d.id) n = x})
      if (n) {
        for (var key in d)
          if (d.hasOwnProperty(key))
            n[key] = d[key]

        json.nodes[i] = n
      }
    })

    json.links.forEach(function(d) {
      var n
      force.links().forEach(function(x) {if (x.id == d.id) n = x})
      if (n) {
        d.source = n.source
        d.target = n.target
      }
    })

    data = json

    update()
  })
}

function update() {
  var links = data.links
                   .filter(function (d) {
                     if (!visible.clients && (d.source.group == 3 || d.target.group == 3))
                       return false

                     if (!visible.vpn && (d.source.group == 2 || d.target.group == 2))
                       return false

                     return true
                   })

  var link = vis.select("g.links")
                .selectAll("g.link")
                .data(links, function(d) {
                    return d.id
                })

  var linkEnter = link.enter().append("g")
                      .attr("class", "link")

  linkEnter.append("line")

  link.selectAll("line")
      .style("stroke-width", function(d) {
        return Math.min(1, d.strength * 2)
      })

  link.selectAll("path.label")
      .remove()

  link.filter(function (d) {
        return d.quality != "TT" && d.quality != "1.000"
      })
      .append("path")
      .attr("class", "label")
      .attr("d", d3.svg.zigzag()
                       .amplitude(function (d) {
                         return Math.pow((1 - 1/d.quality), 0.5) * 8;
                       })
                       .len(30)
                       .angularFrequency(4)
      )

  link.exit().remove()

  var nodes = data.nodes.filter(function (d) {
                  if (!visible.vpn && d.group == 2)
                    return false

                  if (!visible.vpn && d.group == 3 && d.uplinks)
                    return false

                  if (!visible.clients && d.group == 3)
                    return false

                  return true
                })

  var node = vis.select("g.nodes")
                .selectAll("g.node")
                .data(nodes,
                  function(d) {
                    return d.id
                  }
                )

  var nodeEnter = node.enter().append("g")
                .attr("class", "node")
                .on("mouseover", fade(.2))
                .on("mouseout", fade(1))
                .on("click", show_node_info)
                .call(force.drag)

  nodeEnter.append("ellipse")

  node.selectAll("ellipse")
    .attr("rx", function(d) {
      if (d.group == 3) return 4
      else return Math.max(10, d.name.length * 5)
    })
    .attr("ry", function(d) {
      if (d.group == 3) return 4
      else return 10
    })
    .style("fill", function(d) {
      if (d.group == 3) return fill(d.group)
      else return ""
    })
    .style("stroke", function(d) {
      return fill(d.group)
    })

  nodeEnter.filter(function(d) {
      return d.group != 3
    })
    .append("text")
    .attr("text-anchor", "middle")
    .attr("y", "4px")

  node.selectAll("text")
      .text(function(d) { return d.name })

  nodeEnter.append("title")

  node.selectAll("title")
    .text(function(d) { return d.macs })

  if (!visible.vpn) {
    var uplink_info = node.filter(function (d) {
      if (d.uplinks !== undefined)
        return d.uplinks.length > 0
      else
        return false
    })
    .append("g")
    .attr("class", "uplinks")

    uplink_info.append("path")
      .attr("d","m -2.8850049,-13.182327"
          + "c 7.5369165,0.200772 12.1529864,-1.294922 12.3338513,-10.639456"
          + "l 2.2140476,1.018191 -3.3137621,-5.293097 -3.2945999,5.20893 2.4339957,-0.995747"
          + "c -0.4041883,5.76426 -1.1549641,10.561363 -10.3735326,10.701179 z")
      .style("fill", "#333")

    uplink_info.append("text")
      .attr("text-anchor", "middle")
      .attr("y", 3 - 20)
      .text(function (d) {return d.uplinks.length})
  } else {
    node.selectAll(".uplinks").remove()
  }

  node.exit().remove()

  force.nodes(nodes)
       .links(links)
       .start()

  linkedByIndex = {}

  links.forEach(function(d) {
    linkedByIndex[d.source.index + "," + d.target.index] = 1
  })

  if (hashstr.length != 0)
    show_node(hashstr)
}

reload()
<|endoftext|>


<commit_before>/**
 * Unit test framework used with thali-tape.
 */

'use strict';

var util = require('util');
var TestFramework = require('./TestFramework');

function UnitTestFramework(testConfig) {
  UnitTestFramework.super_.call(this, testConfig);
}

util.inherits(UnitTestFramework, TestManager);

UnitTestFramework.prototype.startTests = function(platform, tests) {

  var toComplete;
  var devices = this.devices[platform];

  function doTest(test, cb) {
    // Perform a single test
    function doNext(stage) {
      // We need to have seen all devices report in before we
      // can proceed to the next stage
      if (--toComplete == 0) {
        toComplete = devices.length;
        devices.forEach(function(device) {
          // Tell each device to proceed to the next stage
          device.socket.emit(stage, test);
        });
      }
    }

    // Add event handlers for each stage of a single test
    // Test proceed in the order shown below
    toComplete = devices.length;
    devices.forEach(function(device) {

      // The device has completed setup for this test
      device.socket.once("setup_complete", function() {
        doNext("start_test");
      });

      // The device has completed it's test
      device.socket.once("test_complete", function() {
        doNext('teardown');
      });

      // The device has completed teardown for this test
      device.socket.once("teardown_complete", function() {
        cb();
      });

      // Start setup for this test
      device.socket.emit("setup", test);
    });
  }

  function nextTest() {
    // Pop the completed test off the list
    // and move to next test
    tests.shift();
    if (tests.length) {
      process.nextTick(function() {
        doTest(tests[0], nextTest);
      });
    } else {
      console.log("ALL DONE");
    }
  }

  toComplete = devices.length;
  devices.forEach(function(device) {
    // Wait for devices to signal they've scheduled their
    // test runs and then begin
    device.socket.once("schedule_complete", function() {
      if (--toComplete == 0) {
        doTest(tests[0], nextTest);
      }
    });

    // Tell devices to set tests up to run in the order we supply
    device.socket.emit("schedule", JSON.stringify(tests));
  });
}

module.exports = UnitTestFramework;
<commit_msg>Inherit from correct object<commit_after>/**
 * Unit test framework used with thali-tape.
 */

'use strict';

var util = require('util');
var TestFramework = require('./TestFramework');

function UnitTestFramework(testConfig) {
  UnitTestFramework.super_.call(this, testConfig);
}

util.inherits(UnitTestFramework, TestFramework);

UnitTestFramework.prototype.startTests = function(platform, tests) {

  var toComplete;
  var devices = this.devices[platform];

  function doTest(test, cb) {
    // Perform a single test
    function doNext(stage) {
      // We need to have seen all devices report in before we
      // can proceed to the next stage
      if (--toComplete == 0) {
        toComplete = devices.length;
        devices.forEach(function(device) {
          // Tell each device to proceed to the next stage
          device.socket.emit(stage, test);
        });
      }
    }

    // Add event handlers for each stage of a single test
    // Test proceed in the order shown below
    toComplete = devices.length;
    devices.forEach(function(device) {

      // The device has completed setup for this test
      device.socket.once("setup_complete", function() {
        doNext("start_test");
      });

      // The device has completed it's test
      device.socket.once("test_complete", function() {
        doNext('teardown');
      });

      // The device has completed teardown for this test
      device.socket.once("teardown_complete", function() {
        cb();
      });

      // Start setup for this test
      device.socket.emit("setup", test);
    });
  }

  function nextTest() {
    // Pop the completed test off the list
    // and move to next test
    tests.shift();
    if (tests.length) {
      process.nextTick(function() {
        doTest(tests[0], nextTest);
      });
    } else {
      console.log("ALL DONE");
    }
  }

  toComplete = devices.length;
  devices.forEach(function(device) {
    // Wait for devices to signal they've scheduled their
    // test runs and then begin
    device.socket.once("schedule_complete", function() {
      if (--toComplete == 0) {
        doTest(tests[0], nextTest);
      }
    });

    // Tell devices to set tests up to run in the order we supply
    device.socket.emit("schedule", JSON.stringify(tests));
  });
}

module.exports = UnitTestFramework;
<|endoftext|>