JSFiddle - React, Tailwind, and code Playground

by stitot

HTML

<script src="https://code.highcharts.com/highcharts.js"></script>
    <script src="https://code.highcharts.com/modules/navigator.js"></script>

<div id="container-total"></div>
<div id="navigator-container"></div>

CSS

.container {
  height: 400px;
  margin: 20px;
}

body {
  font-family: sans-serif;
  padding: 1rem;
}
label {
  margin-right: 1rem;
}
#controls {
  margin-bottom: 1rem;
}
#last-updated {
  margin-top: 0.5rem;
  font-size: 0.9rem;
  color: #666;
}

div.tooltip div {
  display: flex;
  justify-content: space-between;
  padding: 4px 0;
}

div.tooltip span:first-child {
  flex: 1;
  text-align: left;
}

div.tooltip span:last-child {
  text-align: right;
  min-width: 80px;
  font-variant-numeric: tabular-nums;
}

JavaScript

const BASES = {
  jsDelivr: "https://data.jsdelivr.com/v1/stats/packages/npm",
  npmRange: "https://api.npmjs.org/downloads/range",
  npmRegistry: "https://registry.npmjs.org",
};

const PACKAGE = "@highcharts/dashboards";
const EARLIEST = {
  jsDelivr: new Date("2018-06-01"),
  npm: new Date("2015-03-11"),
};

async function fetchDownloadStats() {
  const fetchJson = async (url, label) => {
    console.log(label + ": ", url);
    const res = await fetch(url);
    if (!res.ok) throw new Error(`${label} fetch failed`);
    return res.json();
  };

  const fetchVersions = async () => {
    const url = `${BASES.npmRegistry}/${encodeURIComponent(PACKAGE)}`;
    const data = await fetchJson(url, "versions");
    return Object.entries(data.time)
      .filter(([k]) => k !== "created" && k !== "modified")
      .reduce((acc, [ver, date]) => ({ ...acc, [ver]: date.slice(0, 10) }), {});
  };

  const versions = await fetchVersions();
  const versionDates = Object.values(versions).sort();
  const versionStart = new Date(versionDates[0]);

  const fromDateJs = new Date(Math.max(versionStart, EARLIEST.jsDelivr));
  const fromDateNpm = new Date(Math.max(versionStart, EARLIEST.npm));

  const toDate = new Date();
  toDate.setDate(toDate.getDate() - 2); // safety buffer

  const fetchStats = async (source, fromDate) => {
    const result = [];

    if (source === "jsdelivr") {
      for (let y = fromDate.getFullYear(); y <= toDate.getFullYear(); y++) {
        const period = y === new Date().getFullYear() ? "year" : y.toString();
        const url = `${BASES.jsDelivr}/${PACKAGE}?period=${period}`;
        const json = await fetchJson(url, "jsDelivr");
        for (const [day, hits] of Object.entries(json.hits?.dates || {})) {
          const d = new Date(day);
          if (d >= fromDate && d <= toDate) result.push([day, hits]);
        }
      }
    }

    if (source === "npm") {
      let start = new Date(fromDate);
     ...