block by redblobgames 724411560f4796c90f6e9a0e9405d802

which d3 apis do people use

Full Screen

I was curious about which d3 apis people use in their blocks.

  1. I count if it’s used or not, not often it’s used in a block.
  2. Columns and rows are sorted by popularity.
  3. I color by how often something is used relative to that user’s most commonly used api
  4. people who have blue/green filled rows use many different apis evenly, whereas people who have one blue and mostly red use some apis frequently and others infrequently
  5. I combined some calls, like select+selectAll, min+max, hsl+rgb+hcl+lab, etc., to reduce the number of columns
  6. ascending/descending are commonly used (column is towards the left) but not often by the most prolific block authors (towards the top)

(I would love to set the table headers not to scroll but have forgotten how to do that)

index.html

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <style>
      body { margin:0; }
      table { table-layout: fixed; margin-top: 5em; font-family: sans-serif; font-size: 8px; border-collapse: separate; border-spacing: 0px; }
      thead th { width: 1ex; text-align: left; transform: rotate(-90deg) translateX(10px); }
      thead th:first-child { width: 8em; transform: none; }
      tr:hover { outline: 1px solid white; }
      tbody th { background-color: #eee; }
      td { text-align: right; margin: 0; padding: 0; }
  </style>
</head>

<body>
  <script>
    var dom = d3.select("body").append("table")
                .attr("width", "100%");

    d3.json('blocks-api.json', (error, blocks) => {
        if (error) throw error;

        var records = flattenApi(blocks, 'userId');
        var aggregateData = aggregate(records);
        var rowNamesAndCounts = uniqueColumn(records, 0);
        var columnNamesAndCounts = uniqueColumn(records, 1);
        var columnNames = columnNamesAndCounts.names.filter((column) => columnNamesAndCounts.counts[column] > 20);

        console.log(columnNamesAndCounts.counts['descending']);

        // Output table headers
        var header = dom.append('thead')
                        .append('tr');
        header.append('th');
        columnNames.forEach(function(columnName) {
            header.append('th').text(columnName);
        });
        
        // Output table rows
        var body = dom.append('tbody');
        rowNamesAndCounts.names.forEach(function(rowName) {
            var row = body.append('tr');
            row.append('th').text(rowName);
            var columnValues = columnNames.map((columnName) => aggregateData[[rowName, columnName]] || 0);
            var scale = 240 / Math.sqrt(d3.max(columnValues)+1);
            columnValues.forEach((count) => {
                row.append('td')
                   .text(count || "")
                   .style('background-color', d3.hsl((Math.sqrt(count)*scale)|0, 0.5, 0.8));
            });
        })
    });
    
    // Given records {key: value, {'api': {api: count, ...}}}
    // output a flat array [key, api] (*ignoring the count*)
    const renameApi = {'hsl': 'color*', 'rgb': 'color*', 'lab': 'color*', 'hcl': 'color*', 
                       'xhr': 'xhr*', 'json': 'xhr*', 'tsv': 'xhr*', 'xml': 'xhr*', 'csv': 'xhr*', 'loadData': 'xhr*', 'dsv': 'xhr*',
                       'min': 'extent*', 'max': 'extent*', 'extent': 'extent*', 
                       'select': 'select*', 'selectAll': 'select*',
                       'ascending': '{a,de}cending*', 'descending': '{a,de}cending*'
    };
    function flattenApi(blocks, key) {
        var results = [];
        blocks.forEach(function(block) {
            Object.keys(block.api).forEach(function(api) {
                api = api.split('.')[1];
                api = api.replace(/interpolate.*/, 'interpolate*');
                api = api.replace(/scale.*/, 'scale*');
                api = api.replace(/axis.*/, 'axis*');
                api = renameApi[api] || api;
                results.push([block[key], api]);
            })
        });
        return results;
    }

    // Given a table [[v0, v1, v2, ...], ...] and an index i,
    // return the set of unique v_i values, most frequent first
    function uniqueColumn(table, column) {
        var seen = {};
        var results = [];
        table.forEach(function(row) {
            var key = row[column];
            if (!seen[key]) {
                results.push(key);
                seen[key] = 0;
            }
            seen[key]++;
        });
        results.sort((a, b) => seen[b] - seen[a]);
        return {names: results, counts: seen};
    }
    
    // Given a table [[a, b], ...] return a map
    // {[a, b].toString(): count}
    function aggregate(table) {
        var results = {};
        table.forEach(function(row) {
            results[row] = (results[row] || 0) + 1;
        });
        return results;
    }
  </script>
</body>