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

here is my data

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


  • 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>
    <meta charset="utf-8">
    <script src=''></script>
    <script src=''></script>
    <script src=''></script>
    <div id="year-btns">
    <div id="mode-btns">
      <button id="mode-perc">Percentage</button>
      <button id="mode-num">Number</button>
    <div id='chart'></div>

      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 = {
          d["value"] = (mode === "percentage" ?
                                  d["diagnosed"]/d["totalCases"] * 100 :
          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;

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

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

        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 === 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") {

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

          if (diagnosed === 0) {

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

          split.forEach(function(range) {
              "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]);


        .defer(d3.csv, "./diagnostics - Sheet1.csv")

Note: This won't work in Chrome/Chromium because of the CORS policy, so use the CodePen instead:

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 1 year ago
Here you go:
CyteBode 1 year ago
Winning solution

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

Heatmap Demo:

Line Demo:




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.


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