| | 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. |
| | """ |
| |
|
| | |
| | NUM_WEEKS = 52 |
| | DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] |
| |
|
| | |
| | today = dt.date.today() |
| | |
| | start = today - dt.timedelta(days=(today.weekday())) |
| | 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)] |
| |
|
| | |
| | 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] |
| |
|
| | |
| | colorscale = [ |
| | [0.00, "#e5e7eb"], |
| | [0.40, "#64748b"], |
| | [0.75, "#2563eb"], |
| | [1.00, "#4b5563"], |
| | ] |
| |
|
| | 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, |
| | }) |
| |
|
| |
|