activity_chart.js

← Back to explorer
static/js/activity_chart.js
let activityChart = null;

function cssVar(name) {
  return getComputedStyle(document.documentElement)
    .getPropertyValue(name)
    .trim();
}

/* turn hex/rgb/rgba into rgba with alpha */
function withAlpha(color, alpha) {
  if (!color) return `rgba(0,0,0,${alpha})`;

  // #RGB or #RRGGBB
  if (color[0] === "#") {
    let hex = color.slice(1).trim();
    if (hex.length === 3) hex = hex.split("").map(c => c + c).join("");
    const r = parseInt(hex.slice(0, 2), 16);
    const g = parseInt(hex.slice(2, 4), 16);
    const b = parseInt(hex.slice(4, 6), 16);
    return `rgba(${r}, ${g}, ${b}, ${alpha})`;
  }

  // rgb(...)
  if (color.startsWith("rgb(")) {
    return color.replace("rgb(", "rgba(").replace(")", `, ${alpha})`);
  }

  // rgba(...)
  if (color.startsWith("rgba(")) {
    const inside = color.slice(5, -1).split(",").map(s => s.trim());
    return `rgba(${inside[0]}, ${inside[1]}, ${inside[2]}, ${alpha})`;
  }

  // fallback (named colours etc.)
  return color;
}

/* helper for tooltip glass background (canvas, so no real blur) */
function tooltipBg() {
  const theme = document.documentElement.dataset.theme || "light";
  return theme === "dark"
    ? "rgba(68, 68, 78, 0.85)"
    : "rgba(255, 253, 242, 0.85)";
}

function applyTheme(chart) {
  const accent = cssVar("--accent");
  const text = cssVar("--text");
  const muted = cssVar("--muted");
  const grid = cssVar("--line");

  // datasets (order matters!)
  const dsKeys = chart.data.datasets[0];
  const dsLines = chart.data.datasets[1];
  const dsHours = chart.data.datasets[2];

  dsKeys.borderColor = accent;
  dsKeys.backgroundColor = withAlpha(accent, 0.14);

  dsLines.borderColor = withAlpha(accent, 0.65);
  dsLines.backgroundColor = withAlpha(accent, 0.10);

  dsHours.borderColor = withAlpha(accent, 0.80);
  dsHours.backgroundColor = withAlpha(accent, 0.08);

  // legend
  chart.options.plugins.legend.labels.color = muted;

  // tooltip
  chart.options.plugins.tooltip.titleColor = text;
  chart.options.plugins.tooltip.bodyColor = text;
  chart.options.plugins.tooltip.backgroundColor = tooltipBg();
  chart.options.plugins.tooltip.borderColor = grid;

  // axes
  chart.options.scales.x.ticks.color = muted;
  chart.options.scales.x.grid.color = grid;

  chart.options.scales.y.ticks.color = muted;
  chart.options.scales.y.grid.color = grid;
  chart.options.scales.y.title.color = muted;

  chart.options.scales.y1.ticks.color = muted;
  chart.options.scales.y1.title.color = muted;

  chart.update("none");
}

document.addEventListener("DOMContentLoaded", () => {
  const canvas = document.getElementById("activityChart");
  if (!canvas || typeof Chart === "undefined") return;

  const ctx = canvas.getContext("2d");
  const days =
    typeof initialDaysData !== "undefined" && initialDaysData
      ? initialDaysData
      : [];

  const labels = days.map(d => d.date);
  const keysPressed = days.map(d => d.keys_pressed);
  const hours = days.map(d => d.hours);
  const lines = days.map(d => d.lines);

  activityChart = new Chart(ctx, {
    type: "line",
    data: {
      labels,
      datasets: [
        {
          label: "Keys pressed",
          data: keysPressed,
          borderWidth: 2,
          tension: 0.35,
          pointRadius: 3,
          pointHoverRadius: 5,
          yAxisID: "y"
        },
        {
          label: "Lines",
          data: lines,
          borderWidth: 2,
          tension: 0.35,
          pointRadius: 3,
          pointHoverRadius: 5,
          yAxisID: "y"
        },
        {
          label: "Hours",
          data: hours,
          borderWidth: 2,
          tension: 0.35,
          pointRadius: 3,
          pointHoverRadius: 5,
          borderDash: [6, 6],
          yAxisID: "y1"
        }
      ]
    },
    options: {
      responsive: true,
      maintainAspectRatio: false,
      interaction: { mode: "index", intersect: false },
      plugins: {
        legend: {
          labels: {
            boxWidth: 12,
            boxHeight: 12
          }
        },
        tooltip: {
          borderWidth: 1,
          cornerRadius: 10,
          padding: 12,
          boxPadding: 6,
          titleFont: { weight: "600" },
          bodyFont: { weight: "500" },
          callbacks: {
            label: (c) => ` ${c.dataset.label}: ${c.formattedValue}`
          }
        }
      },
      scales: {
        x: {
          ticks: { maxRotation: 45, minRotation: 45 }
        },
        y: {
          position: "left",
          title: { display: true, text: "Keys / lines" }
        },
        y1: {
          position: "right",
          grid: { drawOnChartArea: false },
          title: { display: true, text: "Hours" }
        }
      }
    }
  });

  applyTheme(activityChart);
});

window.addEventListener("theme:changed", () => {
  if (!activityChart) return;
  requestAnimationFrame(() => applyTheme(activityChart));
});