people/a/AlammarJay/js/nn_calc.js

Summary

Maintainability
F
3 wks
Test Coverage
(function (global, factory) {
  typeof exports === "object" && typeof module !== "undefined" ? factory() :
    typeof define === "function" && define.amd ? define(factory) :
      (factory())
}(this, (function () {
  "use strict"

  function graphInputNodes(data) {

    var grapher = this
    // JOIN
    var inputGroups = this.neuralNetworkG.selectAll(this.svgElement + " .input-group")
      .data(data)

    // EXIT old elements not present in new data.
    inputGroups.exit()
      .attr("class", "exit")
      .remove()

    // UPDATE old elements present in new data.
    inputGroups.attr("class", "input-group")
      .attr("transform", function (d) {
        return "translate(" +
          (d.x)
          + ","
          + (d.y) + ")"
      }.bind(this))

    inputGroups.select("#input-name")
      .text(function (d) {
        return "X" + (d.index + 1)
      })


    // ENTER new elements present in new data.
    var inputs = inputGroups.enter()
      .append("g")
      .attr("class", "input-group")
      .attr("transform", function (d) {
        return "translate(" +
          (d.x)
          + ","
          + (d.y) + ")"
      }.bind(this))
      .on("mouseover", function (d) {

        // Highlight the lines connected to this node
        d3.selectAll(grapher.svgElement + " line.input-" + d.index)
          .classed("nn-arrow-highlighted", true)

        // Highlight the weights connected to the node
        var weightGroups = d3.selectAll(grapher.svgElement + " .input-" + d.index + "-weight")
        weightGroups.selectAll("ellipse")
          .classed("nn-node-highlighted", true)
        var layerInputs = d3.selectAll(grapher.svgElement + " .layer-1-input-groups .input-" + d.index)
          .classed("layer-input-value-highlighted", true)
        //.classed("layer-input-value-highlighted", true);
      })
      .on("mouseout", function (d) {


        // Stop highlighting the lines connected to this node
        d3.selectAll(grapher.svgElement + " .input-" + d.index)
          .classed("nn-arrow-highlighted", false)


        // Highlight the weights connected to the node
        var weightGroups = d3.selectAll(grapher.svgElement + " .input-" + d.index + "-weight")
        weightGroups.selectAll("ellipse")
          .classed("nn-node-highlighted", false)

        var layerInputs = d3.selectAll(grapher.svgElement + " .layer-1-input-groups .input-" + d.index)
          .classed("layer-input-value-highlighted", false)
      })

    inputs
      .append("circle")
      .attr("class", function (d) {
        if (d.type == "input") return "outlined-input-node nn-node"
        else if (d.type == "bias") return "outlined-bias-node nn-node"
      })
      .attr("r", this.nodeRadius)
      .attr("cx", 0)
      .attr("cy", 0)

    inputs.append("text")
      .attr("id", "input-name")
      .attr("class", "node-text")
      .attr("text-anchor", "middle")
      .attr("x", 0)
      .attr("y", 5)
      .text(function (d) {
        if (d.type == "input")
          if (d.index == 0)
            return "Age"
          else
            return "Sex"
        else if (d.type == "bias") return "Bias"
      })

    inputGroups.moveUp()
  }


  function updateInputNodesText(newinputs) {
    var inputTexts = this.neuralNetworkG.selectAll(this.svgElement + " .input-group .node-text")
    // Display the text in the input nodes
    inputTexts.text(function (d) {
      if (d.type == "input") {
        return newinputs[d.index]
      } else if (d.type == "bias") return "+1"
    })
  }


  function getInputNodeCoordinates(inputNodeCount, inputLayerX, getYCoordinateOfNodeInInputLayer) {

    var inputLayerCoordinates = d3.range(inputNodeCount).map(function (i) {
      var y = getYCoordinateOfNodeInInputLayer(i)
      return { x: inputLayerX, y: y, index: i, type: 'input' }
    }.bind(this))

    //  Add the bias node to the end of the input layer
    inputLayerCoordinates.push({
      x: inputLayerX, y: getYCoordinateOfNodeInInputLayer(inputNodeCount),
      index: inputNodeCount, type: "bias"
    })

    return inputLayerCoordinates
  }

  /**
   * Created by alammar on 2/7/17.
   */

  function graphWeightNodes(data) {
    var radius = this.nodeRadius * this.weightNodeSizeScale
    var grapher = this
    var t = d3.transition()
      .duration(750)

    // JOIN
    var groupElements = this.neuralNetworkG.selectAll(this.svgElement + " .weight-group")
      .data(data)

    // EXIT old elements not present in new data.
    groupElements.exit()
      .attr("class", "exit")
      .remove()

    // UPDATE old elements present in new data.
    groupElements.attr("class", function (d) {
      return "weight-group input-" + d.inputIndex + "-weight output-" + d.outputIndex + "-weight"
    })
      .attr("transform", function (d) {
        return "translate(" +
          (d.x)
          + ","
          + (d.y) + ")"
      })

    groupElements.select("#weight0Value")
      .text(function (d) {
        return "W" + (d.outputIndex + 1) + "," + (d.inputIndex + 1)
      })

    // ENTER new elements present in new data.
    var groups = groupElements.enter()
      .append("g")
      .attr("class", function (d) {
        return "weight-group input-" + d.inputIndex + "-weight output-" + d.outputIndex + "-weight"
      })
      .attr("transform", function (d) {
        return "translate(" +
          (d.x)
          + ","
          + (d.y) + ")"
      })
      .on("mouseover", function (d) {

        // Expand the node
        var g = d3.select(this)
        g.select("ellipse")
          .transition()
          .duration(50)
          .attr("rx", 50)

        // Make text darker
        g.select("text")
          .classed("weightNodeText-highlighted", true)
          .transition()
          .text(function (d) {
            return d.value
          })
        //nn-node-highlighted
        g.moveUp()
        // Show tooltip
        //grapher.tooltipDiv.transition()
        //    .duration(100)
        //    .style("opacity", 1);
        //grapher.tooltipDiv.html(d.value )
        //    .style("left", (d3.event.pageX) + "px")
        //    .style("top", (d3.event.pageY - 28) + "px");
      })
      .on("mouseout", function (d) {
        var g = d3.select(this)
        g.select("ellipse")
          .transition()
          .duration(50)
          .attr("rx", 22)


        g.select("text")
          .classed("weightNodeText-highlighted", false)
          .transition()
          .delay(30)
          .attr("font-size", "12px")
          .text(function (d) {
            return numberFormatter(d.value)
          })

        //grapher.tooltipDiv.transition()
        //    .duration(200)
        //    .style("opacity", 0);
      })

    groups
      .append("ellipse")
      .attr("class", function (d) {
        if (d.type == "weight") return "outlined-weight-node nn-node"
        else if (d.type == "bias") return "outlined-bias-node nn-node"
      })
      .attr("rx", radius * this.weightNodeWidthRatio)
      .attr("ry", radius)
      .attr("cx", 0)
      .attr("cy", 0)


    groups.append("text")
      .attr("id", "weight0Value")
      .attr("class", "weightNodeText")
      .attr("text-anchor", "middle")
      .attr("y", 3)
      .text(function (d) {
        return numberFormatter(d.value)
      })

    groupElements.moveUp()
  }


  function getWeightNodeCoordinates(inputLayerNodeCount,
                                    inputNodeCount, outputNodeCount,
                                    inputLayerCoordinates, weightLayerXCoordinates,
                                    outputLayerCoordinates,
                                    biasLayerX, inputLayerX) {

    // Calculate the angles of the all the Input-to_Bias lines
    var lineAngles = [],
      weightLayerCoordinates = [],
      weightLayerYCoordinates = []

    for (var j = 0; j < outputNodeCount; j++)
      for (var i = 0; i < inputLayerNodeCount; i++) {

        var opposite = inputLayerCoordinates[i].y - outputLayerCoordinates[j].y,
          adjacent = biasLayerX - inputLayerX,
          angle = Math.atan(opposite / adjacent),
          yCoordinate = inputLayerCoordinates[i].y -
            Math.tan(angle) *
            (weightLayerXCoordinates[j] - inputLayerX)

        if (j == 0) {
          weightLayerYCoordinates[i] = []
          lineAngles[i] = []
        }
        lineAngles[i][j] = angle
        weightLayerYCoordinates[i][j] = yCoordinate

        if (i < inputNodeCount) // Weight
          weightLayerCoordinates.push({
            x: this.weightLayerXCoordinates[j],
            y: yCoordinate,
            outputIndex: j,
            inputIndex: i,
            value: this.weights[i][j],
            type: "weight"
          })
        else // Bias
          weightLayerCoordinates.push({
            x: this.weightLayerXCoordinates[j],
            y: yCoordinate,
            outputIndex: j,
            inputIndex: i,
            value: this.biases[j],
            type: "bias"
          })
      }
    return {
      lineAngles: lineAngles,
      weightLayerCoordinates: weightLayerCoordinates
      //weightLayerYCoordinates: weightLayerYCoordinates
    }
  }

  /**
   * Created by alammar on 2/7/17.
   */
  function graphNode(data) {

    var grapher = this
    // JOIN
    var inputGroups = this.neuralNetworkG.selectAll(this.svgElement + " .output-group")
      .data(data)

    // EXIT old elements not present in new data.
    inputGroups.exit()
      .attr("class", "exit")
      .remove()

    // UPDATE old elements present in new data.
    inputGroups.attr("class", "output-group")
      .attr("transform", function (d) {
        return "translate(" +
          (d.x)
          + ","
          + (d.y) + ")"
      }.bind(this))

    inputGroups.select("#output-name")
      .text(function (d) {
        return "Y" + (d.index + 1)
      })


    // ENTER new elements present in new data.
    var inputs = inputGroups.enter()
      .append("g")
      .attr("class", "output-group")
      .attr("transform", function (d) {
        return "translate(" +
          (d.x)
          + ","
          + (d.y) + ")"
      }.bind(this))
      .on("mouseover", function (d) {

        // Highlight the lines connected to this node
        d3.selectAll(grapher.svgElement + " line.output-" + d.index)
          .classed("nn-arrow-highlighted", true)

        // Highlight the weights connected to the node
        var weightGroups = d3.selectAll(grapher.svgElement + " .output-" + d.index + "-weight")
        weightGroups.selectAll("ellipse")
          .classed("nn-node-highlighted", true)


        var layerInputs = d3.selectAll(grapher.svgElement + " .layer-1-input-groups .output-" + d.index)
          .classed("layer-input-value-highlighted", true)
        var xValue = d.x

        // Set up the tooltip to show the inputs and their sum
        var rows = "<table class=\"input-calculation\">", total = 0,
          classToColorNumber = "weight-layer-input"
        rows = rows + "<tr><th style='text-align:center'>#</th><th>input value</th></tr> "
        for (var i = 0; i < grapher.layerInputs[1].length; i++) {

          if (i + 1 == grapher.layerInputs[1].length)
            classToColorNumber = "bias-layer-input"

          rows = rows + "<tr><td class='importance-10'>" + (i + 1) + "</td><td class='number " +
            classToColorNumber + "'>" +
            roundPrecision(grapher.layerInputs[1][i][d.index], 5) + "</td></tr>"

          total = total + grapher.layerInputs[1][i][d.index]
        }
        rows = rows + "<tr class='total-row'><td class='importance-10'>Sum:</td><td class='sum-value number'>"
          + roundPrecision(total, 5) + "</td></tr>"
        rows = rows + "</table>"


        var matrix = this.getScreenCTM()
          .translate(+this.getAttribute("cx"), +this.getAttribute("cy"))
        //tooltip.html(d)
        //    .style("left", (window.pageXOffset + matrix.e + 15) + "px")
        //    .style("top", (window.pageYOffset + matrix.f - 30) + "px");

        grapher.tooltipDiv.transition()
          .duration(100)
          .style("opacity", 1)
        grapher.tooltipDiv.html(rows)
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY) + "px")

      })
      .on("mouseout", function (d) {

        // Stop highlighting the lines connected to this node
        d3.selectAll(grapher.svgElement + " .output-" + d.index)
          .classed("nn-arrow-highlighted", false)


        // Stop highlighting the weights connected to the node
        var weightGroups = d3.selectAll(grapher.svgElement + " .output-" + d.index + "-weight")
        weightGroups.selectAll("ellipse")
          .classed("nn-node-highlighted", false)


        // Stop highlighting the weights connected to the node
        var layerInputs = d3.selectAll(grapher.svgElement + " .layer-1-input-groups .output-" + d.index)
          .classed("layer-input-value-highlighted", false)
        //weightGroups.selectAll("ellipse")
        //    .classed("nn-node-highlighted", false);


        grapher.tooltipDiv.transition()
          .duration(200)
          .style("opacity", 0)
      })


    inputs
      .append("circle")
      .attr("class", "outlined-output-node nn-node")
      .attr("r", this.nodeRadius)
      .attr("cx", 0)
      .attr("cy", 0)

    inputs.append("text")
      .attr("id", "output-name")
      .attr("class", "node-text")
      .attr("text-anchor", "middle")
      .attr("x", 0)
      .attr("y", 5)
    //.text(function(d){
    //    return "Y" + (d.index +1)
    //});


  }


  function updateOutputNodesText() {
    var outputTexts = this.neuralNetworkG.selectAll(this.svgElement + " .output-group .node-text")
    outputTexts.text(function (d) {
      //return roundPrecision(this.layerValues[1][d.index], 2);
      return numberFormatter(this.layerValues[1][d.index], 2)
    }.bind(this))
  }


  function getOutputNodeCoordinates(outputNodeCount, biasLayerX, getYCoordinateOfNodeInOutputLayer) {
    var outputLayerCoordinates = d3.range(outputNodeCount).map(function (i) {
      var y = getYCoordinateOfNodeInOutputLayer(i)
      return { x: biasLayerX, y: y, index: i, type: 'input' }
    }.bind(this))

    return outputLayerCoordinates
  }

  /**
   * Created by alammar on 2/7/17.
   */
  function graphLayerInputs(layerInputsArray, layerNumber) {


    var layerInputs = layerInputsArray[layerNumber], layerInputsObjects = []

    for (var i = 0; i < layerInputs.length; i++)
      for (var j = 0; j < layerInputs[i].length; j++) {
        layerInputsObjects.push({
          input: i,
          output: j,
          value: layerInputs[i][j],
          angle: this.lineAngles[i][j],
          type: i == (layerInputs.length - 1) ? "bias" : "input"
        })
      }


    var layerInputGroups = this.neuralNetworkG.selectAll(this.svgElement + " .layer-" + layerNumber + "-input-groups")
      .data(layerInputsObjects)

    layerInputGroups.select("text").text(function (d) {
      return numberFormatter(d.value, 5)
    })
    //var texts = layerInputGroups.selectAll("text");

    //texts.text(function(d){
    //    console.log(d.value, numberFormatter(d.value, 5))
    //    return numberFormatter(d.value, 5);
    //});


    // Possible change: Make into its own function. Create elements on initialization, only update text on input change
    var grapher = this, xCoordinate = 225, offset = 4
    var layerInputselements = layerInputGroups.enter()
      .append("g")
      .attrs({
        class: "layer-" + layerNumber + "-input-groups",
        transform: function (d) {
          // Some geometry to calculate the y coordinate of the input value
          // The Y coordinate relative to the input node is really the opposite of the angle
          // Its length is the tan of the angle * the adjacent
          var yRelativeToInputNode = Math.tan(grapher.lineAngles[d.input][d.output]) *
              (xCoordinate - grapher.inputLayerX),
            // Now to transform the relative Y to absolute Y
            yCoordinate = grapher.inputLayerCoordinates[d.input].y - yRelativeToInputNode - offset,
            opposite = grapher.outputLayerCoordinates[d.output].y - yCoordinate,
            adjacent = grapher.lineAngles[d.input][d.output] != 0 ? opposite / Math.tan(-grapher.lineAngles[d.input][d.output]) : 0.0001,
            adjustedXCoordinate = xCoordinate - adjacent

          return "translate(" + xCoordinate + "," + yCoordinate + ")"
        }
      })


    layerInputselements.append("text")
      .attrs({
        class: function (d) {
          var colorClass = d.type == "input" ? "layer-input-value-from-input" : "layer-input-value-from-bias"
          return "layer-" + layerNumber + "-inputs layer-input-value output-" + d.output + " input-" + d.input + " " + colorClass
        },
        "text-anchor": "middle",
        x: 0,
        y: 0,
        transform: function (d) {
          return "rotate(" + (-Math.degrees(d.angle)) + ")"
        }
      })
      .text(function (d) {
        return numberFormatter(d.value, 5)
        //if (d.value > 1) return Math.round(d.value);
        //if (d.value > 0.0001) return roundPrecision(d.value, 5);
        //else
        //    return d.value.toExponential(2)
      })
  }


  function updateLayerInputs(layerInputsArray, layerNumber) {
    var layerInputtext = this.neuralNetworkG.selectAll(this.svgElement + " .layer-" + layerNumber + "-input-groups layer-" + layerNumber + "-input-groups text")

    layerInputtext.text(function (d) {
      return numberFormatter(layerInputsArray[1][d.index], 5)
    }.bind(this))

  }

  /**
   * Created by alammar on 2/7/17.
   */

  function graphSoftmax(data) {

    var softmax = this.neuralNetworkG.selectAll(this.svgElement + " .softmax")
      .data([data])


    var softmaxGroup = softmax.enter()
      .append("g")
      .attr("class", "softmax")
      .attr("transform", function (d) {
        return "translate(" +
          (d.x)
          + ","
          + (d.y) + ")"
      })

    // Define function to create and open tooltip
    var activate = function (d) {
      if (viz.layerValues[1]) {
        // Set up the tooltip to show the inputs and their sum
        var rows = "<table class=\"softmax-calculation\">", total = 0
        rows = rows + "<tr>" +
          "<th>Softmax input</th>" +
          "<th>Calculation</th>" +
          "<th>Softmax output</th>" +
          "<th>Class</th>" +
          "</tr>"

        var sum_term = viz.layerValues[1].map(function (d) {
          return 'e^{' + numberFormatter(d, 2) + '}'
        }).join(" + ")
        var exponential_terms = []
        var softMaxTopResult = viz.softmaxOutputs.indexOf(d3.max(viz.softmaxOutputs))
        for (var i = 0; i < viz.layerValues[1].length; i++) {
          exponential_terms[i] = " e^{" + numberFormatter(viz.layerValues[1][i], 2) + "}"
          var eq = katex.renderToString("\\frac{" + exponential_terms[i] + "}{" + sum_term + "}")
          // If the node has the top softmax value, highlight it
          var highlight_cell = softMaxTopResult == i ? "highlighted" : ""

          var node_row = "<tr>" +
            "<td class='input'>" + numberFormatter(viz.layerValues[1][i], 2) + "</td>" +
            "<td class='calculation'> " + eq + "</td>" +
            "<td class='" + highlight_cell + "'>" + numberFormatter(viz.softmaxOutputs[i], 2) + "</td>" +
            "<td class='class-name " + highlight_cell + "'>" + viz.classes[i] + "</td>" +
            "</tr>"

          rows = rows + node_row
        }

        viz.tooltipDiv.transition()
          .duration(100)
          .style("opacity", 1)
        viz.tooltipDiv.html(rows)
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY) + "px")
      } else {
        var html = "Hover-over or tap one of the row in the table, then you'll see the softmax calculation here."

        viz.tooltipDiv.transition()
          .duration(100)
          .style("opacity", 1)
        viz.tooltipDiv.html(html)
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY) + "px")
      }
    }

    // Define function to close tooltip
    var deactivate = function (d) {
      viz.tooltipDiv.transition()
        .duration(200)
        .style("opacity", 0)
    }


    var viz = this
    softmaxGroup
      .append("rect")
      .attr("class", "outlined-softmax-node nn-node")
      .attr("width", this.nodeRadius * 1.5)
      .attr("height", this.neuralNetworkHeight)
      .attr("rx", this.nodeRadius / 4)
      .attr("ry", this.nodeRadius / 4)
      .attr("x", 0 - this.nodeRadius / 2)
      .attr("y", 0)
      .on("mouseover", activate)
      .on("touchenter", activate)
      .on("touchmove", function (d) {
        d3.event.preventDefault()
      })
      .on("mouseout", deactivate)
      .on("touchleave", deactivate)


    softmaxGroup.append("text")
      .attr("id", "biasValue")
      .attr("text-anchor", "middle")
      .attr("x", this.neuralNetworkHeight / 2)
      .attr("y", -2)
      .attr("transform", "rotate(90)")
      .text("softmax")

  }


  function graphSoftmaxOutputs(softmaxOutputs) {
    var grapher = this
    var data = []
    for (var i = 0; i < this.softmaxOutputs.length; i++) {
      data.push({
        index: i,
        value: softmaxOutputs[i]
      })
    }

    var maxValueIndex = d3.max(this.softmaxOutputs)

    var softmaxOutputGroups = this.neuralNetworkG.selectAll(this.svgElement + " .softmax-output-group")
      .data(data)

    softmaxOutputGroups.select("text")
      .attr("class", function (d) {
        if (d.value == maxValueIndex)
          return "highlighted-softmax-output"
        else
          return "dim-softmax-output"
      })
      .text(function (d) {

        return numberFormatter(d.value * 100, 2) + "%"
      })

    var softmaxOutputElements = softmaxOutputGroups.enter()
      .append("g")
      .attrs({
        class: "softmax-output-group",
        transform: function (d) {
          return "translate(" + (grapher.softmaxOutputsX - 8) + ", " + (grapher.getYCoordinateOfNodeInOutputLayer(d.index) + 5) + ")"
        }
        //"font-size": function(d){return 10 + d.value * 8 }
      })

    softmaxOutputElements.append("text")
      .attr("class", function (d) {
        if (d.value == maxValueIndex)
          return "highlighted-softmax-output"
        else
          return "dim-softmax-output"

      })
      .text(function (d) {
        return numberFormatter(d.value * 100, 2) + "%"
      })
  }


  function highlightTopSoftmaxOutput() {

    var highlightOrDimClass = "", topClassIndex = -1
    if (this.softmaxOutputs) {
      topClassIndex = this.softmaxOutputs.indexOf(d3.max(this.softmaxOutputs))
    }

    var softmaxOutputClassName = this.neuralNetworkG.selectAll(this.svgElement + " .softmax-output-class-name")
    softmaxOutputClassName
      .attr("class", function (d, i) {
        var classValue = "softmax-output-class-name "

        if (d.outputNode == topClassIndex)
          classValue = classValue + "highlighted-softmax-output"
        else
          classValue = classValue + "dim-softmax-output"

        return classValue
      })

  }


  softmax = function (input) {

    if (!(input instanceof Array))
      throw (TypeError("Input passed into softmax() is not an array. softmax() expects an array. Input: " + input, "nnVizUtils.js"))

    var output = [], expSum = 0

    // Calculate the sum of exp's
    for (var i = 0; i < input.length; i++) {
      if (!(typeof (input[i]) == "number")) {
        throw (TypeError("An element in the array passed to softmax() is not a number. softmax() expects an array of numbers. Input: " + input[i], "nnVizUtils.js"))
      }
      expSum = expSum + Math.exp(input[i])
    }

    for (var j = 0; j < input.length; j++)
      output[j] = Math.exp(input[j]) / expSum

    return output
  }

  /**
   * Created by alammar on 2/11/17.
   */



  function graphSigmoid(data) {

    var sigmoid = this.neuralNetworkG.selectAll(this.svgElement + " .sigmoid")
      .data([data])


    var sigmoidGroup = sigmoid.enter()
      .append("g")
      .attr("class", "sigmoid activation")
      .attr("transform", function (d) {
        return "translate(" +
          (d.x)
          + ","
          + (d.y) + ")"
      })


    var viz = this,
      height = this.neuralNetworkHeight / 2,
      width = this.nodeRadius * 1.5,
      yDisplacement = -(height / 2)

    // Define function to create and open tooltip
    var activate = function (d) {
      console.log("INSIDE", viz.layerValues[1])
      if (viz.layerValues[1]) {
        // Set up the tooltip to show the inputs and their sum
        var rows = "<table class=\"softmax-calculation\">", total = 0,
          inputValue = roundPrecision(viz.layerValues[1][0], 5)
        rows = rows + "<tr>" +
          "<th>sigmoid input</th>" +
          "<th>Calculation</th>" +
          "<th>sigmoid output</th>" +
          "</tr>"

        var sum_term = viz.layerValues[1].map(function (d) {
          return 'e^{' + numberFormatter(d, 2) + '}'
        }).join(" + ")
        var exponential_terms = []

        // The following line generates the html, I then copy it here hardcoded to highlight the terms I need to highlight
        //var eq = katex.renderToString("\\frac{1}{1 + e^{-(" + inputValue + ")}}");
        var eq = "<span class=\"katex\"><span class=\"katex-mathml\"><math><semantics><mrow><mfrac><mrow><mn>1</mn></mrow><mrow><mn>1</mn><mo>+</mo><msup><mi>e</mi><mrow><mo>−</mo><mo>(</mo><mo>−</mo><mn>0</mn><mi mathvariant=\"normal\">.</mi><mn>5</mn><mn>6</mn><mn>4</mn><mn>3</mn><mn>3</mn><mo>)</mo></mrow></msup></mrow></mfrac></mrow><annotation encoding=\"application/x-tex\">\frac{1}{1 + e^{-(-0.56433)}}</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"strut\" style=\"height:0.845108em;\"></span><span class=\"strut bottom\" style=\"height:1.2907389999999999em;vertical-align:-0.4456309999999999em;\"></span><span class=\"base textstyle uncramped\"><span class=\"mord reset-textstyle textstyle uncramped\"><span class=\"sizing reset-size5 size5 reset-textstyle textstyle uncramped nulldelimiter\"></span><span class=\"mfrac\"><span class=\"vlist\"><span style=\"top:0.38729999999999987em;\"><span class=\"fontsize-ensurer reset-size5 size5\"><span style=\"font-size:0em;\">​</span></span><span class=\"reset-textstyle scriptstyle cramped\"><span class=\"mord scriptstyle cramped\"><span class=\"mord mathrm\">1</span><span class=\"mbin\">+</span><span class=\"mord\"><span class=\"mord mathit\">e</span><span class=\"vlist\"><span style=\"top:-0.289em;margin-right:0.07142857142857144em;\"><span class=\"fontsize-ensurer reset-size5 size5\"><span style=\"font-size:0em;\">​</span></span><span class=\"reset-scriptstyle scriptscriptstyle cramped\"><span class=\"mord scriptscriptstyle cramped\"><span class=\"mord\">−</span><span class=\"mopen\">(</span><span class=\"mord\"></span>         <span class=\"mord mathrm input\">" + inputValue + "</span>         <span class=\"mclose\">)</span></span></span></span><span class=\"baseline-fix\"><span class=\"fontsize-ensurer reset-size5 size5\"><span style=\"font-size:0em;\">​</span></span>​</span></span></span></span></span></span><span style=\"top:-0.22999999999999998em;\"><span class=\"fontsize-ensurer reset-size5 size5\"><span style=\"font-size:0em;\">​</span></span><span class=\"reset-textstyle textstyle uncramped frac-line\"></span></span><span style=\"top:-0.394em;\"><span class=\"fontsize-ensurer reset-size5 size5\"><span style=\"font-size:0em;\">​</span></span><span class=\"reset-textstyle scriptstyle uncramped\"><span class=\"mord scriptstyle uncramped\"><span class=\"mord mathrm\">1</span></span></span></span><span class=\"baseline-fix\"><span class=\"fontsize-ensurer reset-size5 size5\"><span style=\"font-size:0em;\">​</span></span>​</span></span></span><span class=\"sizing reset-size5 size5 reset-textstyle textstyle uncramped nulldelimiter\"></span></span></span></span></span>"

        var node_row = "<tr>" +
          "<td class='input'>" + inputValue + "</td>" +
          "<td class='calculation'> " + eq + "</td>" +
          "<td>" + numberFormatter(viz.sigmoidOutput, 2) + "</td>" +
          "</tr>"

        rows = rows + node_row


        viz.tooltipDiv.transition()
          .duration(100)
          .style("opacity", 1)
        viz.tooltipDiv.html(rows)
          .style("left", (d3.event.pageX - 250) + "px")
          .style("top", (d3.event.pageY) + "px")

      } else {
        var html = "Hover-over or tap one of the row in the table, then you'll see the sigmoid calculation here."

        viz.tooltipDiv.transition()
          .duration(100)
          .style("opacity", 1)
        viz.tooltipDiv.html(html)
          .style("left", (d3.event.pageX - 250) + "px")
          .style("top", (d3.event.pageY) + "px")
      }
    }

// Define function to close tooltip
    var deactivate = function (d) {
      viz.tooltipDiv.transition()
        .duration(200)
        .style("opacity", 0)
    }

    sigmoidGroup
      .append("rect")
      .attr("class", "outlined-sigmoid-node nn-node")
      .attr("width", width)
      .attr("height", height)
      .attr("rx", this.nodeRadius / 4)
      .attr("ry", this.nodeRadius / 4)
      .attr("x", -width / 2)
      .attr("y", yDisplacement)
      .on("mouseover", activate)
      .on("touchenter", activate)
      .on("touchmove", function (d) {
        d3.event.preventDefault()
      })
      .on("mouseout", deactivate)
      .on("touchleave", deactivate)


    sigmoidGroup.append("text")
      .attr("id", "sigmoid-label")
      .attr("text-anchor", "middle")
      .attr("x", 0)
      .attr("y", -2)
      //.attr("transform", "rotate(90)")
      .text("σ")


  }


  function graphSigmoidOutput(sigmoidOutput) {
    var grapher = this
    var data = [{i: 0, value: sigmoidOutput}]

    var maxValueIndex = 0

    var sigmoidOutputGroups = this.neuralNetworkG.selectAll(this.svgElement + " .sigmoid-output-group")
      .data(data)

    sigmoidOutputGroups.select("text")
      .attr("class", "sigmoid-output")
      .text(function (d) {
        return numberFormatter(d.value * 100, 2) + "%"
      })

    var sigmoidOutputElements = sigmoidOutputGroups.enter()
      .append("g")
      .attrs({
        class: "sigmoid-output-group",
        transform: function (d) {
          return "translate(" + (grapher.softmaxOutputsX - 8) + ", " + (grapher.getYCoordinateOfNodeInOutputLayer(d.index) + 5) + ")"
        }
      })

    sigmoidOutputElements.append("text")
      .attr("class", "sigmoid-output")
      .text(function (d) {
        return numberFormatter(d.value * 100, 2) + "%"
      })
  }


  function calculateSigmoid(data) {
    return 1 / (1 + Math.exp(-data))
  }


  function updateSigmoid(input) {
    this.sigmoidOutput = calculateSigmoid(input)

    // Display sigmoid and final output
    this.graphSigmoidOutput(this.sigmoidOutput)

    this.highlightTopSoftmaxOutput()
  }

  /**
   * Created by alammar on 3/10/17.
   */



  function getInputToBiasLinesData(inputLayerNodeCount, outputNodeCount,
                                   inputLayerCoordinates, outputLayerCoordinates) {
    var inputToBiasLines = []

    // Calculate the coordiantes of the lines from input to bias
    for (var i = 0; i < inputLayerNodeCount; i++)
      for (var j = 0; j < outputNodeCount; j++) {
        inputToBiasLines.push({
          x1: inputLayerCoordinates[i].x,
          y1: inputLayerCoordinates[i].y,
          x2: outputLayerCoordinates[j].x,
          y2: outputLayerCoordinates[j].y,
          inputIndex: i,
          outputIndex: j
        })
      }

    return inputToBiasLines
  }


  function getBiasToSoftmaxLinesData(outputNodeCount, outputLayerCoordinates, activationLayerX, nodeRadius) {

    var BiasToSoftmaxLines = []
    // Calculate the coordiantes of the lines from input to bias
    for (var i = 0; i < outputNodeCount; i++)
      BiasToSoftmaxLines.push({
        x1: outputLayerCoordinates[i].x,
        y1: outputLayerCoordinates[i].y,
        x2: activationLayerX - nodeRadius,
        y2: outputLayerCoordinates[i].y
      })

    console.log("BiasToSoftmaxLines", BiasToSoftmaxLines)
    return BiasToSoftmaxLines
  }


  function graphInputToBiasLines(data) {

    var inputToBiasLines = this.neuralNetworkG.selectAll(this.svgElement + " .input-to-bias-line")
      .data(data)

    inputToBiasLines.exit()
      .remove()

    var classes = "nn-arrow input-to-bias-line"

    inputToBiasLines
      .attrs({
        class: function (d) {
          return classes + " input-" + d.inputIndex + " output-" + d.outputIndex
        },
        x1: function (d) {
          return d.x1
        }, // From the center of the input node
        y1: function (d) {
          return d.y1
        },
        x2: function (d) {
          return d.x2
        },    // To the center of the bias node
        y2: function (d) {
          return d.y2
        }
      })

    inputToBiasLines.enter()
      .append("line")
      .attrs({
        "class": function (d) {
          return classes + " input-" + d.inputIndex + " output-" + d.outputIndex
        },
        "x1": function (d) {
          return d.x1
        }, // From the center of the input node
        "y1": function (d) {
          return d.y1
        },
        "x2": function (d) {
          return d.x2
        },    // To the center of the bias node
        "y2": function (d) {
          return d.y2
        }
      })

  }


  function getSoftmaxtoOutputLinesData(outputNodeCount, activationLayerX, nodeRadius, outputLayerCoordinates,
                                       outputLayerX, classes) {

    var softmaxtoOutputLines = []
    // Calculate the coordiantes of the lines from input to bias
    for (var i = 0; i < outputNodeCount; i++)
      softmaxtoOutputLines.push({
        x1: activationLayerX + nodeRadius + 6,
        y1: outputLayerCoordinates[i].y,
        x2: outputLayerX - nodeRadius,
        y2: outputLayerCoordinates[i].y,
        outputNode: i,
        class_name: classes[i]
      })

    return softmaxtoOutputLines

  }


  function graphBiasToSoftmaxArrows(data) {

    var inputToBiasLines = this.neuralNetworkG.selectAll(this.svgElement + " .bias-to-softmax-line")
      .data(data)

    inputToBiasLines.exit()
      .remove()

    inputToBiasLines
      .attrs({
        class: "nn-arrow bias-to-softmax-line",
        "marker-end": "url(#arrow)",
        x1: function (d) {
          return d.x1
        }, // From the center of the input node
        y1: function (d) {
          return d.y1
        },
        x2: function (d) {
          return d.x2
        },    // To the center of the bias node
        y2: function (d) {
          return d.y2
        }
      })

    inputToBiasLines.enter()
      .append("line")
      .attrs({
        "class": "nn-arrow bias-to-softmax-line",
        "marker-end": "url(#arrow)",
        "x1": function (d) {
          return d.x1
        }, // From the center of the input node
        "y1": function (d) {
          return d.y1
        },
        "x2": function (d) {
          return d.x2
        },    // To the center of the bias node
        "y2": function (d) {
          return d.y2
        }
      })

  }


  function graphSoftmaxToOutputArrows(data) {

    var inputToBiasLines = this.neuralNetworkG.selectAll(this.svgElement + " .softmax-to-output-line")
      .data(data)

    inputToBiasLines.exit()
      .remove()

    inputToBiasLines
      .attrs({
        class: "nn-arrow softmax-to-output-line",
        "marker-end": "url(#arrow)",
        x1: function (d) {
          return d.x1
        }, // From the center of the input node
        y1: function (d) {
          return d.y1
        },
        x2: function (d) {
          return d.x2
        },    // To the center of the bias node
        y2: function (d) {
          return d.y2
        }
      })

    inputToBiasLines.enter()
      .append("line")
      .attrs({
        "class": "nn-arrow softmax-to-output-line",
        "marker-end": "url(#arrow)",
        "x1": function (d) {
          return d.x1
        }, // From the center of the input node
        "y1": function (d) {
          return d.y1
        },
        "x2": function (d) {
          return d.x2
        },    // To the center of the bias node
        "y2": function (d) {
          return d.y2
        }
      })


    var softmaxOutputClassName = this.neuralNetworkG.selectAll(this.svgElement + " .softmax-output-class-name")
      .data(data)

    softmaxOutputClassName.exit()
      .remove()

    softmaxOutputClassName
      .append("text")
      .attrs({
        "class": function (d) {
          return "softmax-output-class-name " + d.highlightedOrDim
        },
        "x": function (d) {
          return d.x1
        }, // From the center of the input node
        "y": function (d) {
          return d.y1 + 25
        }
      })
      .text(function (d, i) {
        return d.class_name
      })

    softmaxOutputClassName.enter()
      .append("text")
      .attrs({
        "class": "softmax-output-class-name",
        "x": function (d) {
          return d.x1
        }, // From the center of the input node
        "y": function (d) {
          return d.y1 + 25
        }
      })
      .text(function (d, i) {
        return d.class_name
      })


  }

  /**
   * Created by alammar on 3/12/17.
   */

// Linear scale between this.neuralNetworkMargin.left and this.neuralNetworkWidth + this.neuralNetworkMargin.left + this.properties.outputRegionWidth
  function getLayerXScale(numberOfLayers, layersRegionWidth, outputRegionWidth, marginLeft, nodeRadius) {
    var returnFunction
    if (numberOfLayers == 1) { // If only one node, draw it in the center
      returnFunction = function () {
        return (layersRegionWidth / 2) + marginLeft
      }
    } else { // Otherwise, separate the vertical space linearly
      returnFunction = d3.scaleLinear()
        .domain([0, numberOfLayers - 1])
        .range([marginLeft + nodeRadius,
          layersRegionWidth - nodeRadius - outputRegionWidth])
    }
    return returnFunction
  }


  function getNodeYCoordinate(numberOfNodesInLayer, chartAreaHeight, marginTop, nodeRadius) {
    var returnFunction
    if (numberOfNodesInLayer == 1) { // If only one node, draw it in the center
      returnFunction = function () {
        return (chartAreaHeight / 2) + marginTop
      }
    } else { // Otherwise, separate the vertical space linearly
      returnFunction = d3.scaleLinear()
        .domain([0, numberOfNodesInLayer - 1])
        .range([marginTop + nodeRadius,
          chartAreaHeight - nodeRadius])
    }
    return returnFunction
  }

  /**
   * Created by alammar on 3/12/17.
   */
  var input = {
    getNodeRenderingDetails: function (layer, properties) {

      var inputAndBiasNodeCount = layer.nodes
      if (layer.bias == true)
        inputAndBiasNodeCount++
      console.log("inputAndBiasNodeCount", inputAndBiasNodeCount)

      // Create a scale to determine the Y coordinate of each node
      var layerNodesYScale = getNodeYCoordinate(inputAndBiasNodeCount, properties.activeRegionHeight,
        properties.margins.top, properties.nodeRadius)


      var layerCoordinates = getInputNodeCoordinates$1(layer.nodes, layer.x, layerNodesYScale)
      return layerCoordinates
    },

    draw: function (layer, properties, activeRegionSVGGroup) {
      var data = layer.nodeRenderingDetails,
        containerElement = properties.containerElement
      var inputGroups = activeRegionSVGGroup.selectAll(properties.containerElement + " .input-group")
        .data(data)

      // EXIT old elements not present in new data.
      inputGroups.exit()
        .attr("class", "exit")
        .remove()

      // UPDATE old elements present in new data.
      inputGroups.attr("class", "input-group")
        .attr("transform", function (d) {
          return "translate(" +
            (d.x)
            + ","
            + (d.y) + ")"
        }.bind(this))

      inputGroups.select("#input-name")
        .text(function (d) {
          return "X" + (d.index + 1)
        })


      // ENTER new elements present in new data.
      var inputs = inputGroups.enter()
        .append("g")
        .attr("class", "input-group")
        .attr("transform", function (d) {
          return "translate(" +
            (d.x)
            + ","
            + (d.y) + ")"
        }.bind(this))
        .on("mouseover", function (d) {

          // Highlight the lines connected to this node
          d3.selectAll(properties.containerElement + " line.input-" + d.index)
            .classed("nn-arrow-highlighted", true)

          // Highlight the weights connected to the node
          var weightGroups = d3.selectAll(properties.containerElement + " .input-" + d.index + "-weight")
          weightGroups.selectAll("ellipse")
            .classed("nn-node-highlighted", true)
          var layerInputs = d3.selectAll(properties.containerElement + " .layer-1-input-groups .input-" + d.index)
            .classed("layer-input-value-highlighted", true)
        })
        .on("mouseout", function (d) {

          // Stop highlighting the lines connected to this node
          d3.selectAll(properties.containerElement + " .input-" + d.index)
            .classed("nn-arrow-highlighted", false)

          // Highlight the weights connected to the node
          var weightGroups = d3.selectAll(properties.containerElement + " .input-" + d.index + "-weight")
          weightGroups.selectAll("ellipse")
            .classed("nn-node-highlighted", false)

          var layerInputs = d3.selectAll(properties.containerElement + " .layer-1-input-groups .input-" + d.index)
            .classed("layer-input-value-highlighted", false)
        })

      inputs
        .append("circle")
        .attr("class", function (d) {
          if (d.type == "input") return "outlined-input-node nn-node"
          else if (d.type == "bias") return "outlined-bias-node nn-node"
        })
        .attr("r", properties.nodeRadius)
        .attr("cx", 0)
        .attr("cy", 0)

      inputs.append("text")
        .attr("id", "input-name")
        .attr("class", "node-text")
        .attr("text-anchor", "middle")
        .attr("x", 0)
        .attr("y", 5)
        .text(function (d) {
          if (d.type == "input")
            if (d.index == 0)
              return "Age"
            else
              return "Sex"
          else if (d.type == "bias") return "Bias"
        })

      inputGroups.moveUp()
    }
  }


  function getInputNodeCoordinates$1(inputNodeCount, inputLayerX, getYCoordinateOfNodeInInputLayer) {

    var inputLayerCoordinates = d3.range(inputNodeCount).map(function (i) {
      var y = getYCoordinateOfNodeInInputLayer(i)
      return { x: inputLayerX, y: y, index: i, type: 'input' }
    })

    //  Add the bias node to the end of the input layer
    inputLayerCoordinates.push({
      x: inputLayerX, y: getYCoordinateOfNodeInInputLayer(inputNodeCount),
      index: inputNodeCount, type: "bias"
    })

    return inputLayerCoordinates
  }

  /**
   * Created by alammar on 3/13/17.
   */
  var block = {
    getNodeRenderingDetails: function (layer, properties) {
      var blockRenderingDetails = {
        x: layer.x,
        y: (properties.activeRegionHeight / 2) + properties.margins.top,
        index: 0,
        type: "block",
        activation: layer.activation
      }
      return blockRenderingDetails
    },

    draw: function (layer, properties, activeRegionSVGGroup) {

      // JOIN
      // Each node has an SVG group of its own. In that group is an SVG circle and SVG text label
      var nodeGroups = activeRegionSVGGroup.selectAll(properties.containerElement + " .layer-" + layer.index)
        .data([layer.nodeRenderingDetails])
      console.log(layer.nodeRenderingDetails)


      // ENTER new elements present in new data.
      var nodes = nodeGroups.enter()
        .append("g")
        .attr("class", "layer-" + layer.index + " layer-group")
        .attr("transform", function (d) {
          return "translate(" +
            (d.x)
            + ","
            + (d.y) + ")"
        })
      console.log(nodes)


      nodes
        .append("rect")
        .attr("class", "outlined-sigmoid-node nn-node")
        .attr("rx", properties.nodeRadius)
        .attr("width", properties.nodeRadius * 2)
        .attr("height", properties.activeRegionHeight)
        .attr("x", -properties.nodeRadius)
        .attr("y", -properties.activeRegionHeight / 2)


      nodes.append("text")
        //.attr("id", "sigmoid-label")
        //.attr("class", "node-text")
        .attr("text-anchor", "middle")
        .attr("x", 0)
        .attr("y", properties.activeRegionHeight / 2 - 12)
        .text(layer.nodes)


      nodes.append("svg:image")
        .attr("x", -15)
        .attr("y", -properties.activeRegionHeight / 2 + 12)
        .attr("width", 30)
        .attr("height", 10)
        .attr("xlink:href", "../../images/relu-symbol.png")
    }
  }

  /**
   * Created by alammar on 3/12/17.
   */
  var nodes = {
    getNodeRenderingDetails: function (layer, properties) {

      var layerNodesYScale = getNodeYCoordinate(layer.nodes, properties.activeRegionHeight,
        properties.margins.top, properties.nodeRadius)

      var layerCoordinates = getNodeCoordinates(layer.nodes, layer.x, layerNodesYScale)
      return layerCoordinates
    },

    draw: function (layer, properties, activeRegionSVGGroup, layerInputs, tooltipDiv) {
      console.log("layer", layer)
      var data = layer.nodeRenderingDetails,
        containerElement = properties.containerElement

      // JOIN
      // Each node has an SVG group of its own. In that group is an SVG circle and SVG text label
      var nodeGroups = activeRegionSVGGroup.selectAll(containerElement + " .layer-" + layer.index)
        .data(data)

      // EXIT old elements not present in new data.
      nodeGroups.exit()
        .attr("class", "exit")
        .remove()

      // UPDATE old elements present in new data.
      nodeGroups.attr("class", "layer-" + layer.index + " layer-group")
        // Change the location in case the number of nodes has changed
        .attr("transform", function (d) {
          return "translate(" +
            (d.x)
            + ","
            + (d.y) + ")"
        }.bind(this))

      nodeGroups.select("#output-name")
        .text(function (d) {
          return "Y" + (d.index + 1)
        })


      // ENTER new elements present in new data.
      var inputs = nodeGroups.enter()
        .append("g")
        .attr("class", ".layer-" + layer.index + " layer-group")
        .attr("transform", function (d) {
          return "translate(" +
            (d.x)
            + ","
            + (d.y) + ")"
        })
        .on("mouseover", function (d) {

            if (layerInputs != "") {

              // Highlight the lines connected to this node
              d3.selectAll(activeRegionSVGGroup + " line.output-" + d.index)
                .classed("nn-arrow-highlighted", true)

              // Highlight the weights connected to the node
              var weightGroups = d3.selectAll(activeRegionSVGGroup + " .output-" + d.index + "-weight")
              weightGroups.selectAll("ellipse")
                .classed("nn-node-highlighted", true)


              var layerInputs = d3.selectAll(activeRegionSVGGroup + " .layer-1-input-groups .output-" + d.index)
                .classed("layer-input-value-highlighted", true)
              var xValue = d.x

              // Set up the tooltip to show the inputs and their sum
              var rows = "<table class=\"input-calculation\">", total = 0,
                classToColorNumber = "weight-layer-input"
              rows = rows + "<tr><th style='text-align:center'>#</th><th>input value</th></tr> "
              for (var i = 0; i < layerInputs[1].length; i++) {

                if (i + 1 == layerInputs[1].length)
                  classToColorNumber = "bias-layer-input"

                rows = rows + "<tr><td class='importance-10'>" + (i + 1) + "</td><td class='number " +
                  classToColorNumber + "'>" +
                  roundPrecision(layerInputs[1][i][d.index], 5) + "</td></tr>"

                total = total + layerInputs[1][i][d.index]
              }
              rows = rows + "<tr class='total-row'><td class='importance-10'>Sum:</td><td class='sum-value number'>"
                + roundPrecision(total, 5) + "</td></tr>"
              rows = rows + "</table>"


              var matrix = this.getScreenCTM()
                .translate(+this.getAttribute("cx"), +this.getAttribute("cy"))
              //tooltip.html(d)
              //    .style("left", (window.pageXOffset + matrix.e + 15) + "px")
              //    .style("top", (window.pageYOffset + matrix.f - 30) + "px");

              tooltipDiv.transition()
                .duration(100)
                .style("opacity", 1)
              tooltipDiv.html(rows)
                .style("left", (d3.event.pageX) + "px")
                .style("top", (d3.event.pageY) + "px")
            }


          }
        )
        .on("mouseout", function (d) {

          if (layerInputs != "") {
            // Stop highlighting the lines connected to this node
            d3.selectAll(activeRegionSVGGroup + " .output-" + d.index)
              .classed("nn-arrow-highlighted", false)


            // Stop highlighting the weights connected to the node
            var weightGroups = d3.selectAll(activeRegionSVGGroup + " .output-" + d.index + "-weight")
            weightGroups.selectAll("ellipse")
              .classed("nn-node-highlighted", false)


            // Stop highlighting the weights connected to the node
            var layerInputs = d3.selectAll(activeRegionSVGGroup + " .layer-1-input-groups .output-" + d.index)
              .classed("layer-input-value-highlighted", false)
            //weightGroups.selectAll("ellipse")
            //    .classed("nn-node-highlighted", false);


            tooltipDiv.transition()
              .duration(200)
              .style("opacity", 0)
          }
        })


      inputs
        .append("circle")
        .attr("class", "outlined-output-node nn-node")
        .attr("r", properties.nodeRadius)
        .attr("cx", 0)
        .attr("cy", 0)

      inputs.append("text")
        .attr("id", "output-name")
        .attr("class", "node-text")
        .attr("text-anchor", "middle")
        .attr("x", 0)
        .attr("y", 5)


    }
  }


  function getNodeCoordinates(nodeCount, layerXCoordinate, getYCoordinateOfNodeLayer) {


    var nodeLayerCoordinates = d3.range(nodeCount).map(function (i) {
      var y = getYCoordinateOfNodeLayer(i)
      return { x: layerXCoordinate, y: y, index: i, type: 'input' }
    })

    return nodeLayerCoordinates

  }

  /**
   * Created by alammar on 3/13/17.
   */
  var sigmoid = {
    getNodeRenderingDetails: function (layer, properties) {


    },

    draw: function (layer, properties, activeRegionSVGGroup) {

    }
  }

  /**
   * Created by alammar on 12/23/16.
   */

  var renderers = {
    input: input,
    block: block,
    defaultLayer: nodes,
    sigmoid: sigmoid
  }


  var neuralNetworkCalculationViz = function (inputs, inputNodeCount, outputNodeCount, activation, containerElement, weights, biases, tableElement,
                                              trainingSet, classes) {


    this.properties = {
      graphWidth: 500,
      graphHeight: 300,
      margins: {top: 10, right: 10, bottom: 10, left: 10},
      outputRegionWidth: 50,
      nodeRadius: 25,
      containerElement: inputs.containerElement,
      nodeThreashold: 7, // If more than this, display as block rather than nodes
      get activeRegionWidth() {
        return this.graphWidth - this.margins.left - this.margins.right
      },
      get activeRegionHeight() {
        return this.graphHeight - this.margins.top - this.margins.bottom
      }

    }

    if (inputs) { // New abstraction
      //containerElement, graphWidth, graphHeight, margins
      this.initializeGraph(inputs.containerElement, this.properties)
      this.drawGraph(inputs)

    } else { // Old ugly code

      this.inputNodeCount = inputNodeCount
      this.inputLayerNodeCount = inputNodeCount + 1
      this.outputNodeCount = outputNodeCount
      this.activation = activation
      this.svgElement = containerElement
      this.graphWidth = 500
      this.graphHeight = 300
      this.nodeRadius = 25
      this.weightNodeSizeScale = 0.4
      this.weightNodeWidthRatio = 2.2
      this.weights = weights
      this.biases = biases
      weights.push(biases)
      this.weightsAndBiasWeights = weights // We will treat biases as an input node with value 1, with the bias value as its weight
      this.layerInputs = []
      this.layerValues = []
      this.tableElement = tableElement
      this.trainingSet = trainingSet
      this.classes = classes

      this.initializeGraphOld()
      this.drawGraphOld()
      this.initializeTable(trainingSet)
    }
  }


// Input
  neuralNetworkCalculationViz.prototype.graphInputNodes = graphInputNodes
  neuralNetworkCalculationViz.prototype.updateInputNodesText = updateInputNodesText
  neuralNetworkCalculationViz.prototype.getInputNodeCoordinates = getInputNodeCoordinates
// Weight
  neuralNetworkCalculationViz.prototype.graphWeightNodes = graphWeightNodes
  neuralNetworkCalculationViz.prototype.getWeightNodeCoordinates = getWeightNodeCoordinates
// Node / output
  neuralNetworkCalculationViz.prototype.graphBiasNodes = graphNode
  neuralNetworkCalculationViz.prototype.updateOutputNodesText = updateOutputNodesText
  neuralNetworkCalculationViz.prototype.getOutputNodeCoordinates = getOutputNodeCoordinates
// Layer inputs
  neuralNetworkCalculationViz.prototype.graphLayerInputs = graphLayerInputs
  neuralNetworkCalculationViz.prototype.updateLayerInputs = updateLayerInputs
// Softmax
  neuralNetworkCalculationViz.prototype.graphSoftmax = graphSoftmax
  neuralNetworkCalculationViz.prototype.graphSoftmaxOutputs = graphSoftmaxOutputs
  neuralNetworkCalculationViz.prototype.highlightTopSoftmaxOutput = highlightTopSoftmaxOutput
// Sigmoid
  neuralNetworkCalculationViz.prototype.graphSigmoid = graphSigmoid
  neuralNetworkCalculationViz.prototype.graphSigmoidOutput = graphSigmoidOutput
  neuralNetworkCalculationViz.prototype.updateSigmoid = updateSigmoid
// Connection lines
  neuralNetworkCalculationViz.prototype.getInputToBiasLinesData = getInputToBiasLinesData
  neuralNetworkCalculationViz.prototype.graphInputToBiasLines = graphInputToBiasLines
  neuralNetworkCalculationViz.prototype.getBiasToSoftmaxLinesData = getBiasToSoftmaxLinesData
  neuralNetworkCalculationViz.prototype.graphBiasToSoftmaxArrows = graphBiasToSoftmaxArrows
  neuralNetworkCalculationViz.prototype.getSoftmaxtoOutputLinesData = getSoftmaxtoOutputLinesData
  neuralNetworkCalculationViz.prototype.graphSoftmaxToOutputArrows = graphSoftmaxToOutputArrows


  neuralNetworkCalculationViz.prototype.updateNodeCount = function (nodeType, newValue) {
    if (nodeType == "input") this.inputNodeCount = newValue
    else if (nodeType == "output") this.outputNodeCount = newValue

    this.drawGraph()
  }


  neuralNetworkCalculationViz.prototype.initializeGraph = function (containerElement, properties) {
    var nnGraphHolder = d3.select(containerElement) // select the 'body' element
      .append("svg")           // append an SVG element to the body
      .attr("width", properties.graphWidth)      // make the SVG element 449 pixels wide
      .attr("height", properties.graphHeight)    // make the SVG element 249 pixels high


    this.neuralNetworkWidth = +nnGraphHolder.attr("width") - this.properties.margins.left -
      this.properties.margins.right,
      this.neuralNetworkHeight = +nnGraphHolder.attr("height") - this.properties.margins.top -
        this.properties.margins.bottom,

      this.neuralNetworkG = nnGraphHolder.append("g")
    this.activeRegionSVGGroup = this.neuralNetworkG

    // Define arrow head
    // https://bl.ocks.org/tomgp/d59de83f771ca2b6f1d4
    var defs = nnGraphHolder.append("defs")
    defs.append("marker")
      .attrs({
        "id": "arrow",
        "viewBox": "0 -5 10 10",
        "refX": 5,
        "refY": 0,
        "markerWidth": 4,
        "markerHeight": 4,
        "orient": "auto"
      })
      .append("path")
      .attr("d", "M0,-5L10,0L0,5")
      .attr("class", "arrowHead")

    this.tooltipDiv = d3.select(".nn-tooltip")
    //.append("div")
    //.attr("class", "nn-tooltip")
    //.style("opacity", 0);
  }


  neuralNetworkCalculationViz.prototype.initializeGraphOld = function () {
    this.nnGraphHolder = d3.select(this.svgElement) // select the 'body' element
      .append("svg")           // append an SVG element to the body
      .attr("width", this.graphWidth)      // make the SVG element 449 pixels wide
      .attr("height", this.graphHeight)    // make the SVG element 249 pixels high
    this.neuralNetworkMargin = {top: 10, right: 10, bottom: 10, left: 10},
      this.neuralNetworkWidth = +this.nnGraphHolder.attr("width") - this.neuralNetworkMargin.left - this.neuralNetworkMargin.right,
      this.neuralNetworkHeight = +this.nnGraphHolder.attr("height") - this.neuralNetworkMargin.top - this.neuralNetworkMargin.bottom,
      this.neuralNetworkG = this.nnGraphHolder.append("g")

    // Define arrow head
    // https://bl.ocks.org/tomgp/d59de83f771ca2b6f1d4
    var defs = this.nnGraphHolder.append("defs")
    defs.append("marker")
      .attrs({
        "id": "arrow",
        "viewBox": "0 -5 10 10",
        "refX": 5,
        "refY": 0,
        "markerWidth": 4,
        "markerHeight": 4,
        "orient": "auto"
      })
      .append("path")
      .attr("d", "M0,-5L10,0L0,5")
      .attr("class", "arrowHead")

    this.tooltipDiv = d3.select(".nn-tooltip")
    //.append("div")
    //.attr("class", "nn-tooltip")
    //.style("opacity", 0);
  }


  neuralNetworkCalculationViz.prototype.drawGraph = function (inputs) {

    // How many layers?
    var layers = inputs.layers,
      numberOfLayers = inputs.layers.length,
      // Linear scale between this.neuralNetworkMargin.left and this.neuralNetworkWidth + this.neuralNetworkMargin.left + this.properties.outputRegionWidth
      layerXScale = getLayerXScale(numberOfLayers, this.properties.activeRegionWidth, this.properties.margins.left,
        this.properties.outputRegionWidth, this.properties.nodeRadius)


    for (var i = 0; i < numberOfLayers; i++) {

      layers[i].index = i

      // Use scale to set the X coordinates of each layer
      layers[i].x = layerXScale(i)

      // Assign a layer-renderer that knows how to draw
      layers[i].renderer = (typeof layers[i].type !== "undefined") ? renderers[layers[i].type] : renderers["defaultLayer"]
      //layers[i].renderer =  (layers[i].type == 'input') ? renderers["input"] : renderers["defaultLayer"];
      console.log("renderer: ", layers[i].renderer)
    }

//
//var graphData = {
//    layers: [
//        {
//            nodes: 3,
//            type: "input",
//            bias: true
//        },
//        {
//            nodes: 1
//        },
//        {
//            type: "sigmoid",
//            nodes: 1
//        }
//    ],
//    containerElement: "#neural-network-architecture-viz",
//    weights: true,
//    weightValues: false
//};

    // Calculate Y coordinates of nodes in each layer
    for (var j = 0; j < numberOfLayers; j++) {

      layers[j].nodeRenderingDetails = layers[j].renderer.getNodeRenderingDetails(layers[j], this.properties)
    }

    // Connection regions


    // Draw all the things
    for (var m = 0; m < numberOfLayers; m++) {

      layers[m].renderer.draw(layers[m], this.properties, this.activeRegionSVGGroup, this.layerInputs, this.tooltipDiv)
    }

  }


  neuralNetworkCalculationViz.prototype.getNodeRenderingDetails = function (layer, properties) {
    //if( )
    switch (layer.type) {
      case "input": // Special consideration for bias nodes
        layer.nodeRenderingDetails = input.getNodeRenderingDetails(layer, properties)


        break

      default:
      //console.log("DEFAULT");
      //
      //var layerNodesYScale = getNodeYCoordinate(layer.nodes, properties.activeRegionHeight,
      //    properties.margins.top, properties.nodeRadius);
      //
      //var layerCoordinates = this.getInputNodeCoordinates(layer.nodes, layer.x, layerNodesYScale);
    }
  }


  neuralNetworkCalculationViz.prototype.drawGraphOld = function () {

    var grapher = this
    // Let's calculate our coordinates for all the nodes
    // Let's start with the X coordiantes for each layer
    var inputLayerX = this.neuralNetworkMargin.left + this.nodeRadius, // X value of input layer
      biasLayerX = this.neuralNetworkWidth * 2 / 3 - 20, //
      outputLayerX = this.neuralNetworkWidth - this.nodeRadius + this.neuralNetworkMargin.left,
      activationLayerX = (outputLayerX + biasLayerX) / 2

    this.inputLayerX = inputLayerX
    this.softmaxOutputsX = outputLayerX


    // Calculate the Y coordinate for each node in each layer
    // 1- Set a scale for each layer
    this.getYCoordinateOfNodeInInputLayer = getNodeYCoordinate(this.inputNodeCount + 1, this.neuralNetworkHeight,
      this.neuralNetworkMargin.top, this.nodeRadius)

    this.getYCoordinateOfNodeInOutputLayer = getNodeYCoordinate(this.outputNodeCount, this.neuralNetworkHeight,
      this.neuralNetworkMargin.top, this.nodeRadius)


    // 2- Primary layers (Y depends only on how many nodes in the layer)
    //  2.1 - Input layergetLayerXScale
    //  Calculate Y coordinates for each node
    this.inputLayerCoordinates = this.getInputNodeCoordinates(this.inputNodeCount, inputLayerX,
      this.getYCoordinateOfNodeInInputLayer)

    //  2.2 - Output layer
    this.outputLayerCoordinates = this.getOutputNodeCoordinates(this.outputNodeCount, biasLayerX,
      this.getYCoordinateOfNodeInOutputLayer)


    // ======================================================================
    // Weights

    this.weightLayerXCoordinates = this.getWeightLayerXCoordinates(grapher.nodeRadius, grapher.weightNodeWidthRatio,
      this.outputNodeCount, inputLayerX)


    // Weight Coordinates
    var weightNodeData = this.getWeightNodeCoordinates(this.inputLayerNodeCount,
      this.inputNodeCount, this.outputNodeCount, this.inputLayerCoordinates, this.weightLayerXCoordinates,
      this.outputLayerCoordinates, biasLayerX, inputLayerX)
    this.lineAngles = weightNodeData.lineAngles
    this.weightLayerCoordinates = weightNodeData.weightLayerCoordinates


    this.inputToBiasLines = this.getInputToBiasLinesData(this.inputLayerNodeCount, this.outputNodeCount,
      this.inputLayerCoordinates, this.outputLayerCoordinates)


    //var connectionsArea1Data = [
    //    {x: this.inputLayerCoordinates[0].x + grapher.nodeRadius, y: this.inputLayerCoordinates[0].y},
    //    {x: this.inputLayerCoordinates[this.inputLayerNodeCount-1].x + grapher.nodeRadius, y: this.inputLayerCoordinates[this.inputLayerNodeCount-1].y},
    //    {x: this.outputLayerCoordinates[this.outputNodeCount-1].x, y: this.outputLayerCoordinates[this.outputNodeCount-1].y},
    //    {x: this.outputLayerCoordinates[0].x, y: this.outputLayerCoordinates[0].y}
    //];
    //
    //
    //var line = d3.line()
    //    .x(function(d) { return d.x; })
    //    .y(function(d) { return d.y; })
    //    .curve(d3.curveLinearClosed);
    //
    //var connections = this.neuralNetworkG
    //    .append("path")
    //    .attr("d", line(connectionsArea1Data))
    //    //.datum(connectionsArea1Data)
    //    .attr("class", "input-to-bias-line")
    //    .attr("fill", "#f0f0f0")
    //    .attr("stroke", "#f0f0f0");


    this.BiasToSoftmaxLines = this.getBiasToSoftmaxLinesData(this.outputNodeCount, this.outputLayerCoordinates,
      activationLayerX, grapher.nodeRadius)


    this.softmaxtoOutputLines = this.getSoftmaxtoOutputLinesData(this.outputNodeCount, activationLayerX,
      grapher.nodeRadius, this.outputLayerCoordinates, outputLayerX, this.classes)


    // Graph all the things
    this.graphSoftmaxToOutputArrows(this.softmaxtoOutputLines)
    this.graphBiasToSoftmaxArrows(this.BiasToSoftmaxLines)

    this.graphInputToBiasLines(this.inputToBiasLines)

    this.graphInputNodes(this.inputLayerCoordinates)
    this.graphWeightNodes(this.weightLayerCoordinates)
    //this.graphOutputNodes(this.outputLayerCoordinates);

    this.graphBiasNodes(this.outputLayerCoordinates)
    this.graphActivation(this.activation, activationLayerX)


    var biasNodeY = this.neuralNetworkMargin.top + this.neuralNetworkHeight / 2 - this.nodeRadius


  }


  neuralNetworkCalculationViz.prototype.graphActivation = function (activationFunction, activationLayerXCoordinate) {
    if (activationFunction == "softmax") {
      var softmaxCoordinates = {x: activationLayerXCoordinate, y: this.neuralNetworkMargin.top}
      this.graphSoftmax(softmaxCoordinates)

    } else if (activationFunction == "sigmoid") {
      var sigmoidCoordinates = {
        x: activationLayerXCoordinate,
        y: (this.neuralNetworkHeight / 2) + this.neuralNetworkMargin.top
      }
      this.graphSigmoid(sigmoidCoordinates)
    }
  }


  neuralNetworkCalculationViz.prototype.feedforward = function (layerNumber) {
    // Calculate the values scaled by the weights (the input to the next layer)
    for (var i = 0; i < this.weightsAndBiasWeights.length; i++)
      for (var j = 0; j < this.weightsAndBiasWeights[i].length; j++) {

        if (j == 0) this.layerInputs[layerNumber][i] = []
        this.layerInputs[layerNumber][i][j] = this.weightsAndBiasWeights[i][j] * this.inputs[i]

        // Calculate the output nodes
        if (i == 0) this.layerValues[1][j] = 0

        this.layerValues[layerNumber][j] = this.layerValues[1][j] + this.layerInputs[layerNumber][i][j]
      }
  }


  neuralNetworkCalculationViz.prototype.initializeTable = function (trainingSet) {


    var viz = this
    var table = d3.select(this.tableElement)
      .append("table")
      .attr("class", "collapsed-style")

    this.selectedPerson = false
    var columns = [
      {
        head: " ", cl: "title", html: function (d, i) {
          return "<input type=\"radio\" name=\"person\"  " +
            "class=\"radio_" + i + "\">"
        }
      },
      {head: "Age", cl: "title", html: ƒ("age")},
      {head: "Sex", cl: "center", html: ƒ("sex")},
      {head: "Survived", cl: "center", html: ƒ("survived")}
    ]

    // Table header  onclick="function(){ viz.selectedPerson = viz.trainingSet['+i+'] ; }"
    table.append("thead").append("tr")
      .selectAll("th")
      .data(columns).enter()
      .append("th")
      .attr("class", ƒ("cl"))
      .text(ƒ("head"))

    // Table body
    table.append("tbody")
      .selectAll("tr")
      .data(trainingSet).enter()
      .append("tr")
      .on("mouseover", function (d) {
        viz.setInput([d.age, d.sex])
      })
      .on("mouseout", function (d) {
        if (viz.selectedPerson)
          viz.setInput(viz.selectedPerson)

      })
      .on("click", function (d) {
        d3.select("radio_" + d.age + "_" + d.sex)
          .attr("checked", "true")
      })
      .selectAll("td")
      .data(function (row, i) {
        // evaluate column objects against the current row
        return columns.map(function (c) {
          var cell = {}
          d3.keys(c).forEach(function (k) {
            cell[k] = typeof c[k] == "function" ? c[k](row, i) : c[k]
          })
          return cell
        })
      })
      .enter()
      .append("td")
      .html(ƒ("html"))
      .attr("class", ƒ("cl"))


  }


  neuralNetworkCalculationViz.prototype.getWeightLayerXCoordinates = function (nodeRadius, weightNodeWidthRatio,
                                                                               outputNodeCount, inputLayerX) {


    var weightMarginLeft = nodeRadius + nodeRadius * weightNodeWidthRatio - 15

    // calculate weight layer x coordinate (one "layer" per output)   // Scales for the axes
    var weightNodeX = d3.scaleLinear()
      .domain([0, outputNodeCount])
      .range([inputLayerX + weightMarginLeft, inputLayerX + weightMarginLeft]) //biasLayerX - weightMarginRight ]);

    var weightLayerXCoordinates = d3.range(outputNodeCount).map(function (i) {
      return weightNodeX(i)
    })

    return weightLayerXCoordinates
  }

  neuralNetworkCalculationViz.prototype.updateActivation = function (activationInput, activation) {

    if (activation == "softmax") {
      // Calculate the softmax values and final output
      this.softmaxOutputs = softmax(activationInput)

      // Display softmax and final output
      this.graphSoftmaxOutputs(this.softmaxOutputs)
    } else if (activation == "sigmoid") {
      this.updateSigmoid(activationInput)
    }

  }


  neuralNetworkCalculationViz.prototype.setInput = function (input$$1) {


    this.inputs = input$$1
    this.layerInputs[1] = []
    this.layerValues[1] = []
    this.inputs.push(1) // This will be our input bias node

    this.updateInputNodesText(input$$1)

    // Multiply inputs by weights and calculate the next layer value
    this.feedforward(1)

    // Display layer 1 input values above their lines
    this.graphLayerInputs(this.layerInputs, 1)

    // Display the text on the output nodes
    this.updateOutputNodesText()


    // Update activation layer
    this.updateActivation(this.layerValues[1], this.activation)

    // Highlight the class name with the highest softmax score
    this.highlightTopSoftmaxOutput()

  }


  var weights = [[-0.016852],
    [0.704039]]
  var biases = [-0.116309]
//
//var weights = [[-0.020234],
//    [1.023300]];
//var biases = [-0.119178];


//var weights = [[-0.11510573],
//    [0.19847003]];
//var biases = [-0.07722114];
//
//var weights = [[-0.11510573, -0.08358583],
//    [0.19847003, -1.00007236]];
//var biases = [-0.07722114, 0.07722116];

//var classes=["Survived", "Didn't survive"];

  var trainingSet = [
    {age: 22, sex: 0, survived: 0},
    {age: 38, sex: 1, survived: 1},
    {age: 26, sex: 1, survived: 1},
    {age: 35, sex: 1, survived: 1},
    {age: 35, sex: 0, survived: 0},
    {age: 14, sex: 1, survived: 0},
    {age: 25, sex: 0, survived: 0},
    {age: 54, sex: 0, survived: 0}

  ]
  var classes = ["Survived"]

//var weights = [[2.14149564e-04, -2.14149914e-04],
//    [5.12748193e-05, -5.12747974e-05]];
//var biases = [1.19155184e-05, -1.19155284e-05];

  var nnCalculationViz = new neuralNetworkCalculationViz(false, 2, 1, "sigmoid", "#neural-network-calculation-viz", weights, biases,
    "#neural-network-calculation-table", trainingSet, classes)


//var input = [22,  0];
//nnCalculationViz.setInput(input);

  var graphData = {
    layers: [
      {
        nodes: 2,
        type: "input",
        bias: true
      },
      {
        nodes: 8,
        activation: "relu",
        type: "block"
      },
      {
        nodes: 1
      }
      //{
      //    type: "sigmoid",
      //    nodes: 1
      //}
    ],
    containerElement: "#neural-network-architecture-viz",
    weights: true,
    weightValues: false
  }

  var nnArchitectureViz = new neuralNetworkCalculationViz(graphData)

})))