Escargot Chart using D3

Aim

Import and parse a text file using HTML and JS. Update a D3 barchart as the words of the text file are processed.

Background

This idea was dreamed up while writing notes on a completely different task (see Piping Python using Bash. I called it an Escargot Chart because it runs at a snails pace. Each letter of the alphabet is a contender to win the race. As words are processed the bar grows in size. There are some rules; 1) only words with more than 2 letters are accepted, 2) no stop words are allowed (e.g. the, on, at), and 3) the first letter has to be in the alphabet.

All I wanted to get out of this was to see if I could use HTML to add a text file and to loop through the words to update a chart. I spent next to no time tweaking javascript and there is nothing special about my version of this chart, most of the D3 implementation follows "Lets Make a Bar Chart" by Mike Bostock .

This gif was created using Peek (https://github.com/phw/peek)

index.html

<!DOCTYPE html>
<html>

  <head> 
    <meta charset='utf-8'/>
    <link rel="stylesheet" href="base.css">
    <script src="data.js"></script>
    <script src="stopwords.js"></script>
    <script src="d3.min.js"></script>
  </head>

  <body>
    <h1>Escargot Chart</h1>
    <div class='inputzone'>
      <input type='file' id='files' name='files[]'/>
      <output id='list'></output>
      <p id='counter'>Total: 0</p>
      <p id="qty">Count: 0</p>
      <button type="button" 
         onclick="countWords()">
         Run
       </button>      
    </div>

    <div class='container'>
      <div class='chart'></div>
    </div>

    <script src="FileParser.js"></script>

  </body>
</html>

base.css

.container {
  height: 400px;
  width: 500px;
}

.chart {
  font: 10px sans-serif;
  background-color: lightgreen;
  color: black;
  border: solid;
  border-width: 1px;
}

data.js

var data = {
  "a": 0,
  "b": 0,
  "c": 0,
  "d": 0,
  "e": 0,
  "f": 0,
  "g": 0,
  "h": 0,
  "i": 0,
  "j": 0,
  "k": 0,
  "l": 0,
  "m": 0,
  "n": 0,
  "o": 0,
  "p": 0,
  "q": 0,
  "r": 0,
  "s": 0,
  "t": 0,
  "u": 0,
  "v": 0,
  "w": 0,
  "x": 0,
  "y": 0,
  "z": 0,
};

FileParser.js

// Requires data object
var wordsList = [];
var alphabet = Object.keys(data);
var ESCARGOT = Object.values(data)



// D3 CHART
var margin = {top: 10, right: 30, bottom: 10, left: 30}
var width = 500 - margin.left - margin.right;
    height = 400 - margin.top - margin.bottom;
    barHeight = 10;

// donRange placeholder updated in handleFileSelect()
var domRange = 5000; 
var svg = d3.select(".chart")
    .append("svg")
      .attr("width", width + margin.left + margin.right)
      .attr("height", barHeight * ESCARGOT.length + margin.top + margin.bottom)
    .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); 

var text = svg.selectAll(".text")
    .data(alphabet)
    .attr("transform", function(d, i) { 
       return "translate(0," + i * barHeight + ")"; });

text.enter().append("text")
    .attr("class", "text")
    .text(function(d) { return d })
    .attr("x", -10)
    .attr("y", function(d, i) { return i * barHeight-2 + margin.top})
    .attr("font-family", "sans-serif")
    .attr("font-size", "10px")
    .attr("fill", "black")
    .attr("text-anchor", "middle")
    .attr("alignment-baseline", "middle")



function countWordsByLetter(arr, obj) {
  /*
  This function is called ONLY in the 
  handleFileSelect method. It is used 
  to set the scale of the D3 chart.

  That is, it forces an update to 
  domRange variable.

  arr is an array containing strings.
  obj is an object containing the 
  alphabet as keys.
  */ 
  temp = Object.assign({}, obj);
  for (var i=0; i<arr.length; i++) {
    var letter = arr[i][0]
    if (alphabet.includes(letter)) {
       temp[letter] = temp[letter] + 1
    }
  }
  return Math.max.apply(null, Object.values(temp))
}


function handleFileSelect(evt) {
  /*
  This function handles the selected 
  file. It is also responsible for 
  parsing the text in gthe document.

  The method of parsing text could/should 
  be drastically improved.
  */
  var file = evt.target.files[0];

  if (file) {
    var r = new FileReader();
    r.onload = function(e) {
        var contents = e.target.result;             
        var ct = r.result;
        // split on new lines and space
        var words = ct.split(/-\n|\s/);
        // lowercase
        words = words.map(v => v.toLowerCase());
        // first letter match alphabet
        var wordMatch = [];
        for (var item=0; item<words.length; item++){
          var target =  words[item]
          var letter = target[0]
          if (target.length > 2 && 
              alphabet.includes(letter) &&
              stopwords.indexOf(target) != -1) {
            wordMatch.push(target)
          }
        }
        // add to global variable
        wordsList.push(wordMatch)
        // set domRange for plotting scales
        domRange = countWordsByLetter(wordMatch, data)
        document.getElementById('counter').innerHTML = 
          "Total: " + wordMatch.length
   }
    r.readAsText(file);

  } else { 
    alert("Failed to load file");
  }
}



// D3 DRAW & UPDATE
function draw(dat) {
  var x = d3.scaleLinear()
    .domain([0, domRange])
    .range([0, width]);

  var bars = svg.selectAll(".bar")
    .data(dat)
    .attr("transform", function(d, i) { 
       return "translate(0," + i * barHeight + ")"; });

  bars.exit()
    .transition()
      .duration(300)
    .attr("width", function(d) { return x(d) })
    .attr("height", barHeight - 1)
    .style('fill-opacity', 1e-6)
    .remove();

  bars.enter().append("rect")
    .attr("class", "bar")
    .attr("width", function(d) { return x(d) })
    .attr("height", barHeight - 1);

  bars.transition().duration(300)
    .attr("width", function(d) { return x(d) })
    .attr("height", barHeight - 1);
}



function countWords() {
  var total = wordsList[0].length;
  var count = 0;

  var x = setInterval(function() {      
      var d = count + 1;
      if (d <= total) {
        document.getElementById('qty').innerHTML = 
          "Count: " + d;
        // slice word from list
        var w = wordsList[0][d][0]
        // add count of 1 in data
        data[w] = data[w] + 1
        count++;
        draw(Object.values(data));
      }
      if (d > total) {
        document.getElementById('qty').innerHTML = 
          "Count: " + total+1;
      }
  }, 10); 
}


// HANDLE LOAD FILE EVENT
document.getElementById('files').addEventListener('change', handleFileSelect, false);

stopwords.js

// Stop words from https://kb.yoast.com/kb/list-stop-words/

var stopwords=[
    "a",
    "about",
    "above",
    "after",
    "again",
    "against",
    "all",
    "am",
    "an",
    "and",
    "any",
    "are",
    "as",
    "at",
    "be",
    "because",
    "been",
    "before",
    "being",
    "below",
    "between",
    "both",
    "but",
    "by",
    "could",
    "did",
    "do",
    "does",
    "doing",
    "down",
    "during",
    "each",
    "few",
    "for",
    "from",
    "further",
    "had",
    "has",
    "have",
    "having",
    "he",
    "he’d",
    "he’ll",
    "he’s",
    "her",
    "here",
    "here’s",
    "hers",
    "herself",
    "him",
    "himself",
    "his",
    "how",
    "how’s",
    "I",
    "I’d",
    "I’ll",
    "I’m",
    "I’ve",
    "if",
    "in",
    "into",
    "is",
    "it",
    "it’s",
    "its",
    "itself",
    "let’s",
    "me",
    "more",
    "most",
    "my",
    "myself",
    "nor",
    "of",
    "on",
    "once",
    "only",
    "or",
    "other",
    "ought",
    "our",
    "ours",
    "ourselves",
    "out",
    "over",
    "own",
    "same",
    "she",
    "she’d",
    "she’ll",
    "she’s",
    "should",
    "so",
    "some",
    "such",
    "than",
    "that",
    "that’s",
    "the",
    "their",
    "theirs",
    "them",
    "themselves",
    "then",
    "there",
    "there’s",
    "these",
    "they",
    "they’d",
    "they’ll",
    "they’re",
    "they’ve",
    "this",
    "those",
    "through",
    "to",
    "too",
    "under",
    "until",
    "up",
    "very",
    "was",
    "we",
    "we’d",
    "we’ll",
    "we’re",
    "we’ve",
    "were",
    "what",
    "what’s",
    "when",
    "when’s",
    "where",
    "where’s",
    "which",
    "while",
    "who",
    "who’s",
    "whom",
    "why",
    "why’s",
    "with",
    "would",
    "you",
    "you’d",
    "you’ll",
    "you’re",
    "you’ve",
    "your",
    "yours",
    "yourself",
    "yourselves",
]

results matching ""

    No results matching ""