File size: 3,538 Bytes
6b42508
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import plotly.graph_objects as go
import plotly.io as pio
import numpy as np
import datetime as dt
import os

"""
Calendar-like heatmap (GitHub-style) over the last 52 weeks.
Minimal, responsive, transparent background; suitable for Distill.
"""

# Parameters
NUM_WEEKS = 52
DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]

# Build dates matrix (7 rows x NUM_WEEKS columns)
today = dt.date.today()
# Align to start of current week (Monday)
start = today - dt.timedelta(days=(today.weekday()))  # Monday of current week
weeks = [start - dt.timedelta(weeks=w) for w in range(NUM_WEEKS-1, -1, -1)]
dates = [[weeks[c] + dt.timedelta(days=r) for c in range(NUM_WEEKS)] for r in range(7)]

# Generate values (synthetic) — smooth seasonal pattern + noise
def gen_value(d: dt.date) -> float:
    day_of_year = d.timetuple().tm_yday
    base = 0.5 + 0.45 * np.sin(2 * np.pi * (day_of_year / 365.0))
    noise = np.random.default_rng(hash(d) % 2**32).uniform(-0.15, 0.15)
    return max(0.0, min(1.0, base + noise))

z = [[gen_value(d) for d in row] for row in dates]
custom = [[d.isoformat() for d in row] for row in dates]

# Colors aligned with other charts (slate / blue / gray)
colorscale = [
    [0.00, "#e5e7eb"],   # light gray background for low
    [0.40, "#64748b"],   # slate-500
    [0.75, "#2563eb"],   # blue-600
    [1.00, "#4b5563"],   # gray-600 (high end accent)
]

fig = go.Figure(
    data=go.Heatmap(
        z=z,
        x=[w.isoformat() for w in weeks],
        y=DAYS,
        colorscale=colorscale,
        showscale=False,
        hovertemplate="Date: %{customdata}<br>Value: %{z:.2f}<extra></extra>",
        customdata=custom,
        xgap=2,
        ygap=2,
    )
)

fig.update_layout(
    autosize=True,
    paper_bgcolor="rgba(0,0,0,0)",
    plot_bgcolor="rgba(0,0,0,0)",
    margin=dict(l=28, r=12, t=8, b=28),
    xaxis=dict(
        showgrid=False,
        zeroline=False,
        showline=False,
        ticks="",
        showticklabels=False,
        fixedrange=True,
    ),
    yaxis=dict(
        showgrid=False,
        zeroline=False,
        showline=False,
        ticks="",
        tickfont=dict(size=12, color="rgba(0,0,0,0.65)"),
        fixedrange=True,
    ),
)

post_script = """
(function(){
  var plots = document.querySelectorAll('.js-plotly-plot');
  plots.forEach(function(gd){
    function round(){
      try {
        var root = gd && gd.parentNode ? gd.parentNode : document;
        var rects = root.querySelectorAll('.hoverlayer .hovertext rect');
        rects.forEach(function(r){ r.setAttribute('rx', 8); r.setAttribute('ry', 8); });
      } catch(e) {}
    }
    if (gd && gd.on){
      gd.on('plotly_hover', round);
      gd.on('plotly_unhover', round);
      gd.on('plotly_relayout', round);
    }
    setTimeout(round, 0);
  });
})();
"""

html = pio.to_html(
    fig,
    include_plotlyjs=False,
    full_html=False,
    post_script=post_script,
    config={
        "displayModeBar": False,
        "responsive": True,
        "scrollZoom": False,
        "doubleClick": False,
        "modeBarButtonsToRemove": [
            "zoom2d", "pan2d", "select2d", "lasso2d",
            "zoomIn2d", "zoomOut2d", "autoScale2d", "resetScale2d",
            "toggleSpikelines"
        ],
    },
)

fig.write_html("./plotly-heatmap.html", 
               include_plotlyjs=False, 
               full_html=False, 
               config={
                   'displayModeBar': False,
                   'responsive': True, 
                   'scrollZoom': False,
               })