Create cool js visualization of data - radial histogram / circos
New here? Learn about Bountify and follow @bountify to get notified of new bounties! x

This bounty is party functional but also creative. We have some stats on a specific type of medical diagnosis we are tracking. We would like to show it in a cool was as to hint at the seasonal nature of it. We like the idea of putting this on a circle since it will help align with the mental model of a yearly annual chart. We found some charts we like but I am having a tough time seeing how we get our current data in to anything that looks cool. But here are the ideas

https://github.com/nicgirault/circosjs#introduction

https://github.com/nicgirault/circosjs#introduction

https://github.com/nicgirault/circosjs#introduction

https://www.behance.net/gallery/29746987/The-more-the-merrier-Dataviz-for-wsd2015-challenge

here is my data

https://docs.google.com/spreadsheets/d/1e5u9aYvkpF0duV-68ksAXjDu9p5WNW_EDyTru7V7TTE/edit?usp=sharing

We have the week, year, number of cases we diagnosed, % of cases relative to total patients. We care mostly about column C as it is the specific thing we are looking for but I felt like there might be a way to plot this relative to total number of patients in case it made the visual more meaningful or relevant. Finally, I suppose to make some of these numbers pop more we will have to use some logarithmic function and/or color to help make things visually pop.

Given the nature of this task and the urgency in which we need some ideas I am giving 3 winners
1st place = $100

2nd = $50

3rd = $25

you can use any js lib you need, would be great if the data is easy to manage but at the moment we just need to get ideas and something working.

awarded to Wuddrum

Crowdsource coding tasks.

2 Solutions


Dependencies

  • d3
  • d3-queue
  • circosJS

Download the spreadsheet as a CSV (diagnostics - Sheet1.csv) and put it in the same directory as the following HTML file:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.js'></script>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3-queue/3.0.3/d3-queue.js'></script>
    <script src='https://cdn.jsdelivr.net/gh/nicgirault/circosJS@v2/dist/circos.js'></script>
  </head>
  <body>
    <div id="year-btns">
    </div>
    <div id="mode-btns">
      <button id="mode-perc">Percentage</button>
      <button id="mode-num">Number</button>
    </div>
    <div id='chart'></div>

    <script>
      var MONTHS = [
        {"len": 31, "color": "#8dd3c7", "label": "January", "id": "january"},
        {"len": 28, "color": "#ffffb3", "label": "February", "id": "february"},
        {"len": 31, "color": "#bebada", "label": "March", "id": "march"},
        {"len": 30, "color": "#fb8072", "label": "April", "id": "april"},
        {"len": 31, "color": "#80b1d3", "label": "May", "id": "may"},
        {"len": 30, "color": "#fdb462", "label": "June", "id": "june"},
        {"len": 31, "color": "#b3de69", "label": "July", "id": "july"},
        {"len": 31, "color": "#fccde5", "label": "August", "id": "august"},
        {"len": 30, "color": "#d9d9d9", "label": "September", "id": "september"},
        {"len": 31, "color": "#bc80bd", "label": "October", "id": "october"},
        {"len": 30, "color": "#ccebc5", "label": "November", "id": "november"},
        {"len": 31, "color": "#ffed6f", "label": "December", "id": "december"}
      ];

      var WIDTH = 500;

      function drawCircos(mode, diagnoses) {
        document.getElementById("chart").innerHTML = "";

        diagnoses = diagnoses.map(function(d) {
          d["value"] = (mode === "percentage" ?
                                  d["diagnosed"]/d["totalCases"] * 100 :
                                  d["diagnosed"]);
          d["block_id"] = d["month"];
          return d;
        });

        var circos = new Circos({
          container: '#chart',
          width: WIDTH,
          height: WIDTH
        });

        circos.layout(MONTHS, {
          innerRadius: WIDTH / 2 - 80,
          outerRadius: WIDTH / 2 - 30,
          ticks: {display: false},
          labels: {
            position: "center",
            display: true,
            size: 12,
            color: "#000"
          }
        }).heatmap("diagnoses", diagnoses, {
          innerRadius: 0.6,
          outerRadius: 0.98,
          logScale: true,
          color: "-Spectral",
          tooltipContent: function(datum, index) {
            var value = (mode === "percentage" ?
              datum.value.toFixed(4) + "%" :
              datum.diagnosed + "/" + datum.totalCases);
            return datum.dateRange + ": " + value;
          }
        }).render();
      }

      function splitDateRange(dateRange) {
        function convertMonth(month) {
          month = month.trim().toLowerCase();
          var monthIDs = MONTHS.map(function(m) { return m.id; });

          for (var i = 0; i < monthIDs.length; i++) {
            if (monthIDs[i].startsWith(month)) {
              return monthIDs[i];
            }
          }
        }
        dateRange = dateRange.split(" to ");
        if (dateRange.length !== 2) {
          return;
        }

        var start = dateRange[0].split(" ");
        var end = dateRange[1].split(" ");

        var monthStart = convertMonth(start[0]);
        var monthEnd = convertMonth(end[0]);

        if (end.length === 1 || monthStart === monthEnd) {
          return [
            [convertMonth(start[0]), Number(start[1]) - 1,
             monthEnd ? Number(end[1]) : Number(end[0])]
          ];
        } else {
          var monthDays = MONTHS.filter(function(m) { return m.id === monthStart; })
                                .map(function(m) { return m.len; })[0];
          return [
            [monthStart, Number(start[1]) - 1, monthDays],
            [monthEnd, 0, Number(end[1])]
          ];
        }
      }

      function parseCSV(error, diagnosesCSV) {
        var diagnoses = {};
        diagnosesCSV.forEach(function(row) {
          var split = splitDateRange(row["Week"]);
          if (typeof split === "undefined") {
            return;
          }

          var year = row["year"].trim();
          var diagnosed = Number(row["number of diagnosed "]);
          var totalCases = Number(row["Total cases"]);

          if (diagnosed === 0) {
            return;
          }

          if (typeof diagnoses[year] === "undefined") {
            diagnoses[year] = [];
          }

          split.forEach(function(range) {
            diagnoses[year].push({
              "month": range[0],
              "start": range[1],
              "end": range[2],
              "diagnosed": diagnosed,
              "totalCases": totalCases,
              "dateRange": row["Week"],
              "year": year
            });
          });
        });

        var years = Object.keys(diagnoses).sort();
        var yearBtns = document.getElementById("year-btns");
        yearBtns.innerHTML = "";

        years.forEach(function(year) {
          var button = document.createElement("button");
          button.innerHTML = year;
          button.onclick = function() {
            document.getElementById("mode-perc").onclick = function() {
              drawCircos("percentage", diagnoses[year]);
            };
            document.getElementById("mode-num").onclick = function() {
              drawCircos("number", diagnoses[year]);
            };
            drawCircos("percentage", diagnoses[year]);
          };
          yearBtns.appendChild(button);
        });

        document.getElementById("year-btns").firstChild.click();
      }

      d3.queue()
        .defer(d3.csv, "./diagnostics - Sheet1.csv")
        .await(parseCSV);
    </script>
  </body>
</html>

Note: This won't work in Chrome/Chromium because of the CORS policy, so use the CodePen instead: https://codepen.io/anon/pen/JVvBoo

Edit 1: Fixed typo (</svg> -> </div>).

Edit 2: Added dynamic year switching from data, removed ES6 features (const, for ... of) for compatibility, fixed percentage display.

Thanks for this! - do you mind putting in a codepen or fiddle?
Qdev 3 months ago
Here you go: https://codepen.io/anon/pen/JVvBoo
CyteBode 3 months ago
Winning solution

Hey Qdev, here's what I came up with:

Heatmap Demo: https://wuddrum.github.io/radial-seasonal-chart/heatmap.html

Line Demo: https://wuddrum.github.io/radial-seasonal-chart/line.html

Source: https://github.com/Wuddrum/radial-seasonal-chart

Dependencies

Heatmap

This is based on the default circosJS heatmap sample. I'm stacking both (all) years on the same chart and using overall min/max values to better show not only the seasonal difference within each year, but also the difference between the years themselves.

Line

Implemented similarly to heatmap, but instead of showing differences only with colors, it's using gradient filled line areas, which are separated by months. This shifts the main focus from color to size/shape of the area. Line chart might be a better option since it is easier to take in for colorblind people.

The solutions are compatible with all major browsers and IE 10+, it's also working fine on an old iPhone 3Gs.

Edit: Improved gradients for line chart and set charts' background to a dark color for a better contrast with light colors.

View Timeline