Caleydo Web & AngularJS: Matrix

A simple demo on how to integrate Caleydo Web's matrix vis into an AngularJS app.

by flek

HTML

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stampit/2.1.0/stampit.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.14/d3.min.js"></script>
<script src="https://rawgit.com/Caleydo/caleydo_web/02056f5408fc33402f9de79aa75ddfd097188195/bundle.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.5.1/lodash.min.js"></script>
<div id="app" ng-app="matrixApp" ng-controller="matrixAppCtrl as app">
  <table id="table" ng-mouseleave="app.leaveTableBody()">
    <thead>
      <tr>
        <th
          ng-if="app.matrix.columnIds || app.matrix.rowIds"
          ng-mouseenter="app.leaveTableBody()"></th>
        <th
          ng-class="{ 'soft-highlight': app.hoveringColumn === $index, 'selected': app.columnSelected($index) }"
          ng-click="app.selectColumn($index)"
          ng-mouseenter="app.enterColumn($index)"
          ng-repeat="id in app.matrix.columnIds track by $index" class="ids">{{ app.matrix.columnHeader($index) }}</th>
      </tr>
    </thead>
    <tbody>
      <tr ng-repeat="row in app.matrix.rowIds track by $index">
        <td
          class="ids"
          ng-class="{ 'soft-highlight': app.hoveringRow === $parent.$index, 'selected': app.rowSelected($parent.$index) }"
          ng-if="app.matrix.rowIds.length"
          ng-mouseenter="app.enterRow($index)"
          ng-click="app.selectRow($index)">{{ app.matrix.rowHeader($index) }}</td>
        <td
          class="cell"
          ng-class="{
            'soft-highlight-horizontally': app.hoveringRow === $parent.$index,
            'soft-highlight-vertically': app.hoveringColumn === $index,
            'selected': app.cellSelected($index, $parent.$index)
          }"
          ng-repeat="cell in app.matrix.columnIds track by $index"
          ng-mouseenter="app.enterCell($parent.$index, $index)"
          ng-click="app.selectCell($index, $parent.$index)">
 ...

SCSS

@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700);

$colorBlindBlue: #50b4e9;
$colorBlindYellow: #f0e442;

$colorDirectHoverBg: rgba(0, 0, 0, 0.2);
$colorIndirectHoverBg: rgba(0, 0, 0, 0.1);
$colorSelectedBg: $colorBlindYellow;

#app {
  font-family: 'Source Sans Pro', sans-serif;
  font-size: 14px;
}

#table {
  .ids {
    font-weight: bold;
    color: #000;
    
    &:hover {
      cursor: pointer;
    }
    
    &.soft-highlight {
      background: $colorIndirectHoverBg;
    }
    
    &.selected {
      background: lighten($colorSelectedBg, 15%);
    }
  }
  
  .cell {
    color: #333;
    
    &:hover {
      color: #000;
      background: $colorDirectHoverBg;
      box-shadow: none !important;
    }
    
    &.soft-highlight-horizontally {
      box-shadow: inset 0 2px 0 0 $colorIndirectHoverBg, inset 0 -2px 0 0 $colorIndirectHoverBg;
    }
    
    &.soft-select-horizontally {
      box-shadow: inset 0 2px 0 0 $colorSelectedBg, inset 0 -2px 0 0 $colorSelectedBg;
    }
    
    &.soft-highlight-vertically {
      box-shadow: inset 2px 0 0 0 $colorIndirectHoverBg, inset -2px 0 0 0 $colorIndirectHoverBg;
    }
    
    &.soft-select-vertically {
      box-shadow: inset 2px 0 0 0 $colorSelectedBg, inset -2px 0 0 0 $colorSelectedBg;
    }
    
    &.soft-select-horizontally.soft-select-vertically {
      box-shadow: inset 0 0 0 2px $colorSelectedBg;
    }
  
    &:last-child {
      &.soft-highlight-horizontally {
        box-shadow: inset 0 2px 0 0 $colorIndirectHoverBg, inset 0 -2px 0 0 $colorIndirectHoverBg, inset -2px 0 0 0 $colorIndirectHoverBg;
      }
      &.soft-select-horizontally {
        box-shadow: inset 0 2px 0 0 $colorSelectedBg, inset 0 -2px 0 0 $colorSelectedBg, inset -2px 0 0 0 $colorSelectedBg;
      }
    }
  
    &.selected {
      background: $colorSelectedBg;
    }
  }
  
  td, th {
    min-width: 2rem;
    padding: 0.25rem;
    text-align: center;
  }
  
  input {
    width: 2rem;
    text-align: center;
   ...

JavaScript

// Dummy data
var DUMMY_DATA = [['ID','a','b','c','d','e'],
  ['1', 10, 9.14, 1, 2, 4],
  ['2', 8, 8.14, 1, 2, 4],
  ['3', 13, 8.74, 1, 2, 4],
  ['4', 9, 8.77, 1, 2, 4],
  ['5', 11, 9.26, 1, 2, 4],
  ['6', 14, 8.1, 1, 2, 4],
  ['7', 6, 6.13, 1, 2, 4],
  ['8', 4, 3.1, 1, 2, 4],
  ['9', 12, 9.13, 1, 2, 4],
  ['10', 7, 7.26, 1, 2, 4],
  ['11', 5, 4.74, 1, 2, 4]];

// Very simple event factory
var eventStamp = stampit.init(function() {
  var stack = {};

  this.on = function (event, callback) {
    if (event in stack) {
    	stack[event].push(callback);
    } else {
    	stack[event] = [callback];
    }
    return this;
  };
  
  this.trigger = function (event, payload) {
  	if (event in stack) {
    	for (var i = stack[event].length; i--;) {
    		stack[event][i](payload);
      }
    }
    return this;
  };
});

// Generic matrix factory function
var matrixStamp = stampit.init(function(stamp) {
	var THAT = stamp.instance;
  
  var data = stamp.args[0];
  var columnIds = stamp.args[1];
  var rowIds = stamp.args[2];
  
  // Update the column sums
  function updateColumnSums () {
    var sums = [];
    for (
    	var row = rowIds ? 1 : 0,
      		numRows = data.length;
      row < numRows;
      row++
    ) {
      for (
        var column = columnIds ? 1 : 0,
        		numColumns = data[row].length;
        column < numColumns;
        column++
      ) {
      	var realColumn = columnIds ? column - 1 : column;
        sums[realColumn] = sums[realColumn] ? sums[realColumn] + data[row][column] : data[row][column];
      }
    }
    THAT.columnSums = sums;
  }
  
  // Initialize column sums
  updateColumnSums();
  
  // Generates an empty array of length # columns
  // Note that this is barely a helper array to support _ngRepeat_.
  this.columnIds = (function () {
  	return [].constructor(columnIds && data.length ? rowIds ? data[0].length - 1 : data[0].length : 0);
  }());
  
  // Returns column header entry
  this.columnHeader = function (column) {
  	return columnIds...