heatmap-builder 0.2.0 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +2 -5
- data/CHANGELOG.md +70 -3
- data/Gemfile.lock +27 -24
- data/README.md +105 -135
- data/bin/generate_examples +42 -99
- data/examples/calendar_blue_ocean.svg +1 -1
- data/examples/calendar_cell_borders.svg +1 -0
- data/examples/calendar_default.svg +1 -1
- data/examples/calendar_github_style.svg +1 -1
- data/examples/calendar_month_spacing_rounded.svg +1 -0
- data/examples/calendar_no_borders.svg +1 -0
- data/examples/calendar_purple_vibes.svg +1 -1
- data/examples/calendar_red_to_green.svg +1 -1
- data/examples/calendar_rounded_corners.svg +1 -1
- data/examples/calendar_rounded_corners_max_radius.svg +1 -1
- data/examples/calendar_sunday_start.svg +1 -1
- data/examples/calendar_warm_sunset.svg +1 -1
- data/examples/calendar_with_outside_cells.svg +1 -1
- data/heatmap-builder.gemspec +4 -4
- data/lib/heatmap-builder.rb +5 -31
- data/lib/heatmap_builder/calendar.rb +426 -0
- data/lib/heatmap_builder/color_helpers.rb +121 -141
- data/lib/heatmap_builder/svg_helpers.rb +11 -4
- data/lib/heatmap_builder/value_conversion.rb +9 -5
- data/lib/heatmap_builder/version.rb +1 -1
- metadata +13 -22
- data/examples/large_cells.svg +0 -1
- data/examples/linear_blue_ocean.svg +0 -1
- data/examples/linear_github_green.svg +0 -1
- data/examples/linear_neon_gradient.svg +0 -1
- data/examples/linear_purple_vibes.svg +0 -1
- data/examples/linear_red_to_green.svg +0 -1
- data/examples/linear_rounded_corners.svg +0 -1
- data/examples/linear_rounded_corners_max_radius.svg +0 -1
- data/examples/linear_warm_sunset.svg +0 -1
- data/examples/weekly_progress.svg +0 -1
- data/lib/heatmap_builder/builder.rb +0 -100
- data/lib/heatmap_builder/calendar_heatmap_builder.rb +0 -310
- data/lib/heatmap_builder/linear_heatmap_builder.rb +0 -74
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6493e6ea0c3c5fd533269a55e6cb18c8df0a65ddad86aab5061c6d5cf57c5586
|
|
4
|
+
data.tar.gz: fd5ae27f32ec084c3414d8f26a7def6c45bedef66b7266423ddd9fc3ec3df458
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1424b3fc448ef0d2a0afac402f87617fc981a179cc5fad4a6883585950f41a885b052680da36ec203bc071fab010c336b9c362b6c72ecbbc5680643f47d938ff
|
|
7
|
+
data.tar.gz: 8702e0d38c802162393f0185b6e485e504203a119115f4d92e43e72b583d32202bf61c6264b7e5df9ae3a6b77ac7526d92d2b21a253ac0893695ef0348109e7f
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -11,7 +11,7 @@ jobs:
|
|
|
11
11
|
runs-on: ubuntu-latest
|
|
12
12
|
strategy:
|
|
13
13
|
matrix:
|
|
14
|
-
ruby-version: ['3.
|
|
14
|
+
ruby-version: ['3.3', '3.4', '4.0']
|
|
15
15
|
|
|
16
16
|
steps:
|
|
17
17
|
- uses: actions/checkout@v4
|
|
@@ -23,10 +23,7 @@ jobs:
|
|
|
23
23
|
bundler-cache: true
|
|
24
24
|
|
|
25
25
|
- name: Run tests
|
|
26
|
-
run:
|
|
27
|
-
bundle exec ruby -Ilib:test test/heatmap_builder_test.rb
|
|
28
|
-
bundle exec ruby -Ilib:test test/calendar_heatmap_builder_test.rb
|
|
29
|
-
bundle exec ruby -Ilib:test test/linear_heatmap_builder_test.rb
|
|
26
|
+
run: bundle exec rake test
|
|
30
27
|
|
|
31
28
|
lint:
|
|
32
29
|
runs-on: ubuntu-latest
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,69 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.3] - 2026-06-15
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Calendar month labels now always sit above the first full week of the month.
|
|
14
|
+
Previously, with `month_spacing: 0`, a label could land on the straddling
|
|
15
|
+
column shared by two months; the label position is now derived from each
|
|
16
|
+
week's `week_start`, so straddling weeks defer the label to the next full week.
|
|
17
|
+
- A month is no longer labeled when its only visible week is incomplete (a
|
|
18
|
+
leading or trailing sliver at the edge of the data range). Previously such a
|
|
19
|
+
label could overlap the next month's label when the partial month occupied a
|
|
20
|
+
single column. Labels are now placed only on a fully visible week.
|
|
21
|
+
|
|
22
|
+
## [0.4.2] - 2026-06-15
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- New `border_lightness_factor` option controls how the cell border color is derived
|
|
26
|
+
from each cell's color by scaling its OKLCH lightness. Setting it to `1` makes
|
|
27
|
+
the border match the cell color, in which case the (now invisible) border is
|
|
28
|
+
omitted from the SVG entirely. Defaults to `0.9`, preserving previous output.
|
|
29
|
+
|
|
30
|
+
## [0.4.1] - 2026-06-13
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- Default value-to-score conversion now reserves score `0` for empty cells
|
|
34
|
+
(zero or missing values) and maps every non-zero value into the `1..max_score`
|
|
35
|
+
range, so the smallest amount of activity is always visually distinct from an
|
|
36
|
+
empty day. Regenerated `examples/*.svg` to reflect the new bucketing.
|
|
37
|
+
- Auto-calculated `value_min` now anchors on the smallest non-zero value instead
|
|
38
|
+
of zero. Because zero is the reserved empty bucket, this keeps the lightest
|
|
39
|
+
activity color reachable rather than stranding it on values that never occur.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
- Example calendar heatmaps misrendered high-activity days. The generator passed
|
|
43
|
+
raw values straight through as `scores:`, so any value beyond the palette's
|
|
44
|
+
color count wrapped around via modulo (`score_to_color`) and the busiest days
|
|
45
|
+
could render as nearly empty cells. The examples now feed values through the
|
|
46
|
+
bucketing conversion, so cell intensity increases monotonically with the
|
|
47
|
+
underlying value.
|
|
48
|
+
|
|
49
|
+
## [0.4.0] - 2026-06-12
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
- Tooltip support for calendar cells via the `tooltip:` option. Accepts a callable
|
|
53
|
+
invoked per active cell with `date:`, `score:`, and `value:` keyword arguments;
|
|
54
|
+
the return value becomes the tooltip text.
|
|
55
|
+
- Native SVG `<title>` element is always emitted as a zero-JS browser fallback.
|
|
56
|
+
- `tooltip_attribute:` option (default `"data-tooltip"`) controls which `data-*`
|
|
57
|
+
attribute is written on the cell's `<g>` wrapper for JS tooltip library pickup.
|
|
58
|
+
Set to `nil` to suppress the data attribute and use only the native fallback.
|
|
59
|
+
|
|
60
|
+
## [0.3.1] - 2026-06-11
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
- Relaxed `rake` and `minitest` development dependency constraints from `~>` to `>=` to allow future major versions
|
|
64
|
+
|
|
65
|
+
## [0.3.0] - 2026-06-11
|
|
66
|
+
|
|
67
|
+
### Changed
|
|
68
|
+
- Dropped support for Ruby 3.0, 3.1, and 3.2 (all EOL); minimum required version is now 3.3
|
|
69
|
+
- Updated all dependencies to latest stable versions
|
|
70
|
+
- Updated Bundler constraint from `~> 2.0` to `>= 2.0`; locked to Bundler 4.0.14
|
|
71
|
+
- CI matrix updated to Ruby 3.3, 3.4, and 4.0
|
|
72
|
+
|
|
10
73
|
## [0.2.0] - 2025-10-02
|
|
11
74
|
|
|
12
75
|
### Added
|
|
@@ -32,7 +95,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
32
95
|
- Automatic corner radius clamping to valid range
|
|
33
96
|
|
|
34
97
|
### Deprecated
|
|
35
|
-
- `HeatmapBuilder.generate(scores, options)` - use `HeatmapBuilder.build_linear(scores: scores, **options)` instead
|
|
36
98
|
- `HeatmapBuilder.generate_calendar(scores, options)` - use `HeatmapBuilder.build_calendar(scores: scores, **options)` instead
|
|
37
99
|
- Old API still works with deprecation warnings for backward compatibility
|
|
38
100
|
|
|
@@ -41,13 +103,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
41
103
|
Initial release with core heatmap visualization capabilities.
|
|
42
104
|
|
|
43
105
|
### Added
|
|
44
|
-
- Linear heatmap generation with `HeatmapBuilder.generate()`
|
|
45
106
|
- Calendar heatmap generation with `HeatmapBuilder.generate_calendar()`
|
|
46
107
|
- GitHub-style color schemes and styling
|
|
47
108
|
- Customizable cell size, spacing, colors, and fonts
|
|
48
109
|
- Support for custom start of week (Monday/Sunday)
|
|
49
110
|
- SVG output format for perfect scaling
|
|
50
111
|
|
|
51
|
-
[Unreleased]: https://github.com/dreikanter/heatmap-builder/compare/v0.
|
|
112
|
+
[Unreleased]: https://github.com/dreikanter/heatmap-builder/compare/v0.4.3...HEAD
|
|
113
|
+
[0.4.3]: https://github.com/dreikanter/heatmap-builder/compare/v0.4.2...v0.4.3
|
|
114
|
+
[0.4.2]: https://github.com/dreikanter/heatmap-builder/compare/v0.4.1...v0.4.2
|
|
115
|
+
[0.4.1]: https://github.com/dreikanter/heatmap-builder/compare/v0.4.0...v0.4.1
|
|
116
|
+
[0.4.0]: https://github.com/dreikanter/heatmap-builder/compare/v0.3.1...v0.4.0
|
|
117
|
+
[0.3.1]: https://github.com/dreikanter/heatmap-builder/compare/v0.3.0...v0.3.1
|
|
118
|
+
[0.3.0]: https://github.com/dreikanter/heatmap-builder/compare/v0.2.0...v0.3.0
|
|
52
119
|
[0.2.0]: https://github.com/dreikanter/heatmap-builder/compare/v0.1.0...v0.2.0
|
|
53
120
|
[0.1.0]: https://github.com/dreikanter/heatmap-builder/releases/tag/v0.1.0
|
data/Gemfile.lock
CHANGED
|
@@ -1,44 +1,47 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
heatmap-builder (0.
|
|
4
|
+
heatmap-builder (0.4.3)
|
|
5
5
|
|
|
6
6
|
GEM
|
|
7
7
|
remote: https://rubygems.org/
|
|
8
8
|
specs:
|
|
9
9
|
ast (2.4.3)
|
|
10
10
|
docile (1.4.1)
|
|
11
|
-
|
|
11
|
+
drb (2.2.3)
|
|
12
|
+
json (2.19.9)
|
|
12
13
|
language_server-protocol (3.17.0.5)
|
|
13
14
|
lint_roller (1.1.0)
|
|
14
|
-
minitest (
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
minitest (6.0.6)
|
|
16
|
+
drb (~> 2.0)
|
|
17
|
+
prism (~> 1.5)
|
|
18
|
+
parallel (2.1.0)
|
|
19
|
+
parser (3.3.11.1)
|
|
17
20
|
ast (~> 2.4.1)
|
|
18
21
|
racc
|
|
19
|
-
prism (1.
|
|
22
|
+
prism (1.9.0)
|
|
20
23
|
racc (1.8.1)
|
|
21
24
|
rainbow (3.1.1)
|
|
22
|
-
rake (13.
|
|
23
|
-
regexp_parser (2.
|
|
24
|
-
rubocop (1.
|
|
25
|
+
rake (13.4.2)
|
|
26
|
+
regexp_parser (2.12.0)
|
|
27
|
+
rubocop (1.87.0)
|
|
25
28
|
json (~> 2.3)
|
|
26
29
|
language_server-protocol (~> 3.17.0.2)
|
|
27
30
|
lint_roller (~> 1.1.0)
|
|
28
|
-
parallel (
|
|
31
|
+
parallel (>= 1.10)
|
|
29
32
|
parser (>= 3.3.0.2)
|
|
30
33
|
rainbow (>= 2.2.2, < 4.0)
|
|
31
34
|
regexp_parser (>= 2.9.3, < 3.0)
|
|
32
|
-
rubocop-ast (>= 1.
|
|
35
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
33
36
|
ruby-progressbar (~> 1.7)
|
|
34
37
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
35
|
-
rubocop-ast (1.
|
|
38
|
+
rubocop-ast (1.49.1)
|
|
36
39
|
parser (>= 3.3.7.2)
|
|
37
|
-
prism (~> 1.
|
|
38
|
-
rubocop-performance (1.
|
|
40
|
+
prism (~> 1.7)
|
|
41
|
+
rubocop-performance (1.26.1)
|
|
39
42
|
lint_roller (~> 1.1)
|
|
40
43
|
rubocop (>= 1.75.0, < 2.0)
|
|
41
|
-
rubocop-ast (>= 1.
|
|
44
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
42
45
|
ruby-progressbar (1.13.0)
|
|
43
46
|
simplecov (0.22.0)
|
|
44
47
|
docile (~> 1.1)
|
|
@@ -46,33 +49,33 @@ GEM
|
|
|
46
49
|
simplecov_json_formatter (~> 0.1)
|
|
47
50
|
simplecov-html (0.13.2)
|
|
48
51
|
simplecov_json_formatter (0.1.4)
|
|
49
|
-
standard (1.
|
|
52
|
+
standard (1.55.0)
|
|
50
53
|
language_server-protocol (~> 3.17.0.2)
|
|
51
54
|
lint_roller (~> 1.0)
|
|
52
|
-
rubocop (~> 1.
|
|
55
|
+
rubocop (~> 1.87.0)
|
|
53
56
|
standard-custom (~> 1.0.0)
|
|
54
57
|
standard-performance (~> 1.8)
|
|
55
58
|
standard-custom (1.0.2)
|
|
56
59
|
lint_roller (~> 1.0)
|
|
57
60
|
rubocop (~> 1.50)
|
|
58
|
-
standard-performance (1.
|
|
61
|
+
standard-performance (1.9.0)
|
|
59
62
|
lint_roller (~> 1.1)
|
|
60
|
-
rubocop-performance (~> 1.
|
|
63
|
+
rubocop-performance (~> 1.26.0)
|
|
61
64
|
unicode-display_width (3.2.0)
|
|
62
65
|
unicode-emoji (~> 4.1)
|
|
63
|
-
unicode-emoji (4.
|
|
66
|
+
unicode-emoji (4.2.0)
|
|
64
67
|
|
|
65
68
|
PLATFORMS
|
|
66
69
|
arm64-darwin-24
|
|
67
70
|
ruby
|
|
68
71
|
|
|
69
72
|
DEPENDENCIES
|
|
70
|
-
bundler (
|
|
73
|
+
bundler (>= 2.0)
|
|
71
74
|
heatmap-builder!
|
|
72
|
-
minitest (
|
|
73
|
-
rake (
|
|
75
|
+
minitest (>= 5.0)
|
|
76
|
+
rake (>= 13.0)
|
|
74
77
|
simplecov (~> 0.22)
|
|
75
78
|
standard (~> 1.0)
|
|
76
79
|
|
|
77
80
|
BUNDLED WITH
|
|
78
|
-
|
|
81
|
+
4.0.9
|
data/README.md
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
# HeatmapBuilder
|
|
2
2
|
|
|
3
|
-
A Ruby gem that generates embeddable SVG heatmap visualizations with GitHub-style calendar layouts
|
|
3
|
+
A Ruby gem that generates embeddable SVG heatmap visualizations with GitHub-style calendar layouts. Perfect for Rails applications and any project that needs to display activity data in a visual format.
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
- GitHub-style calendar layouts for date-based data.
|
|
10
|
-
- Linear heatmaps.
|
|
11
10
|
- Vector-based output (SVG) for crisp rendering at any resolution.
|
|
12
11
|
- Optional numeric values displayed in each cell.
|
|
13
12
|
- **Use pre-calculated scores or raw numeric values** - automatic mapping to color scales.
|
|
@@ -16,6 +15,7 @@ A Ruby gem that generates embeddable SVG heatmap visualizations with GitHub-styl
|
|
|
16
15
|
- Rounded corners (and circular cells, if you're into that kind of thing).
|
|
17
16
|
- Dynamic palette generation from two colors or manually-specified colors.
|
|
18
17
|
- OKLCH color interpolation for clean color transitions and perceptual uniformity.
|
|
18
|
+
- Tooltip support with native browser fallback and JS library integration hooks.
|
|
19
19
|
- **Zero dependencies.**
|
|
20
20
|
|
|
21
21
|
## Installation
|
|
@@ -36,29 +36,14 @@ Or install it yourself as:
|
|
|
36
36
|
|
|
37
37
|
## Usage
|
|
38
38
|
|
|
39
|
-
### Linear Heatmaps
|
|
40
|
-
|
|
41
|
-
```ruby
|
|
42
|
-
require 'heatmap-builder'
|
|
43
|
-
|
|
44
|
-
# Generate SVG for daily scores
|
|
45
|
-
scores = [0, 1, 2, 3, 4, 5, 2, 1]
|
|
46
|
-
svg = HeatmapBuilder.build_linear(scores: scores)
|
|
47
|
-
|
|
48
|
-
# In a Rails view
|
|
49
|
-
<%= raw HeatmapBuilder.build_linear(scores: @daily_scores) %>
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-

|
|
53
|
-
|
|
54
39
|
### Calendar Heatmaps
|
|
55
40
|
|
|
56
41
|
```ruby
|
|
57
42
|
# GitHub-style calendar heatmap
|
|
58
43
|
scores_by_date = {
|
|
59
|
-
'
|
|
60
|
-
'
|
|
61
|
-
'
|
|
44
|
+
'2026-01-01' => 2,
|
|
45
|
+
'2026-01-02' => 4,
|
|
46
|
+
'2026-01-03' => 1,
|
|
62
47
|
# ... more dates
|
|
63
48
|
}
|
|
64
49
|
|
|
@@ -67,34 +52,6 @@ svg = HeatmapBuilder.build_calendar(scores: scores_by_date)
|
|
|
67
52
|
|
|
68
53
|

|
|
69
54
|
|
|
70
|
-
### Linear Heatmap Options
|
|
71
|
-
|
|
72
|
-
You must provide either `scores:` or `values:` (but not both). All other options are optional keyword arguments with sensible defaults.
|
|
73
|
-
|
|
74
|
-
**Data options:**
|
|
75
|
-
|
|
76
|
-
- `scores` - Array of pre-calculated scores (integers from 0 to number of colors minus 1). Required if `values` is not provided.
|
|
77
|
-
- `values` - Array of arbitrary numeric values to be automatically mapped to scores. Required if `scores` is not provided. See [Using Raw Values Instead of Scores](#using-raw-values-instead-of-scores).
|
|
78
|
-
|
|
79
|
-
**Value-to-score conversion options** (only used with `values:`):
|
|
80
|
-
|
|
81
|
-
- `value_min` - Minimum boundary for value-to-score mapping. Defaults to the minimum value in your data.
|
|
82
|
-
- `value_max` - Maximum boundary for value-to-score mapping. Defaults to the maximum value in your data.
|
|
83
|
-
- `value_to_score` - Custom callable for value-to-score conversion. Receives `value:`, `index:`, `min:`, `max:`, `max_score:` parameters and must return an integer between 0 and `max_score`. See [Custom Scoring Logic](#custom-scoring-logic) for details.
|
|
84
|
-
|
|
85
|
-
**Appearance options:**
|
|
86
|
-
|
|
87
|
-
- `cell_size` - Size of each square in pixels. Defaults to 10.
|
|
88
|
-
- `cell_spacing` - Space between squares in pixels. Defaults to 1.
|
|
89
|
-
- `font_size` - Font size for score text in pixels. Defaults to 8.
|
|
90
|
-
- `border_width` - Border width around each cell in pixels. Defaults to 1.
|
|
91
|
-
- `corner_radius` - Corner radius for rounded cells. Must be between 0 (square corners) and `floor(cell_size/2)` (circular cells). Values outside this range are automatically clamped. Defaults to 0.
|
|
92
|
-
- `text_color` - Color of score text as a hex string. Defaults to `"#000000"` (black).
|
|
93
|
-
|
|
94
|
-
**Color options:**
|
|
95
|
-
|
|
96
|
-
- `colors` - Color palette for the heatmap. Can be a predefined palette constant (e.g., `HeatmapBuilder::GITHUB_GREEN`), an array of hex color strings (e.g., `%w[#ebedf0 #9be9a8 #40c463]`), or a hash for OKLCH interpolation (e.g., `{ from: "#ebedf0", to: "#216e39", steps: 5 }`). Defaults to `HeatmapBuilder::GITHUB_GREEN`. See [Predefined Color Palettes](#predefined-color-palettes) and [Dynamic Palettes Generation](#dynamic-palettes-generation).
|
|
97
|
-
|
|
98
55
|
### Calendar Heatmap Options
|
|
99
56
|
|
|
100
57
|
You must provide either `scores:` or `values:` (but not both). All other options are optional keyword arguments with sensible defaults.
|
|
@@ -116,21 +73,26 @@ You must provide either `scores:` or `values:` (but not both). All other options
|
|
|
116
73
|
- `cell_spacing` - Space between squares in pixels. Defaults to 1.
|
|
117
74
|
- `font_size` - Font size for labels in pixels. Defaults to 8.
|
|
118
75
|
- `border_width` - Border width around each cell in pixels. Defaults to 1.
|
|
76
|
+
- `border_lightness_factor` - Controls the border color, derived from each cell's color by scaling its lightness (in OKLCH) by this factor. Values below 1 produce a darker border; a value of 1 makes the border match the cell color, in which case the border is omitted entirely. Must be positive. Defaults to 0.9.
|
|
119
77
|
- `corner_radius` - Corner radius for rounded cells. Must be between 0 (square corners) and `floor(cell_size/2)` (circular cells). Values outside this range are automatically clamped. Defaults to 0.
|
|
120
|
-
- `text_color` - Color of label text as a hex string. Defaults to `"#000000"` (black).
|
|
121
78
|
|
|
122
79
|
**Color options:**
|
|
123
80
|
|
|
124
|
-
- `colors` - Color palette for the heatmap. Can be a predefined palette constant (e.g., `HeatmapBuilder::GITHUB_GREEN`), an array of hex color strings (e.g., `%w[#ebedf0 #9be9a8 #40c463]`), or a hash for OKLCH interpolation (e.g., `{ from: "#ebedf0", to: "#216e39", steps: 5 }`). Defaults to `HeatmapBuilder::GITHUB_GREEN`. See [Predefined Color Palettes](#predefined-color-palettes) and [Dynamic Palettes Generation](#dynamic-palettes-generation).
|
|
81
|
+
- `colors` - Color palette for the heatmap. Can be a predefined palette constant (e.g., `HeatmapBuilder::Calendar::GITHUB_GREEN`), an array of hex color strings (e.g., `%w[#ebedf0 #9be9a8 #40c463]`), or a hash for OKLCH interpolation (e.g., `{ from: "#ebedf0", to: "#216e39", steps: 5 }`). Defaults to `HeatmapBuilder::Calendar::GITHUB_GREEN`. See [Predefined Color Palettes](#predefined-color-palettes) and [Dynamic Palettes Generation](#dynamic-palettes-generation).
|
|
125
82
|
|
|
126
83
|
**Calendar-specific options:**
|
|
127
84
|
|
|
128
85
|
- `start_of_week` - First day of the week. One of `:sunday`, `:monday`, `:tuesday`, `:wednesday`, `:thursday`, `:friday`, `:saturday`. Defaults to `:monday`.
|
|
129
|
-
- `month_spacing` - Extra horizontal space between months in pixels. Defaults to
|
|
86
|
+
- `month_spacing` - Extra horizontal space between months in pixels. Defaults to 0.
|
|
130
87
|
- `show_month_labels` - Show month names at the top of the calendar. Defaults to `true`.
|
|
131
88
|
- `show_day_labels` - Show day abbreviations on the left side of the calendar. Defaults to `true`.
|
|
132
89
|
- `show_outside_cells` - Show cells outside the date range with inactive styling. Defaults to `false`.
|
|
133
90
|
|
|
91
|
+
**Tooltip options:**
|
|
92
|
+
|
|
93
|
+
- `tooltip` - Callable invoked per active cell with `date:`, `score:`, and `value:` keyword arguments. Return value is used as the tooltip text. Emits a native SVG `<title>` element (browser fallback) and a data attribute (JS library hook). Defaults to `nil` (no tooltip markup).
|
|
94
|
+
- `tooltip_attribute` - Name of the `data-*` attribute written on each cell's `<g>` wrapper for JS tooltip library pickup. Defaults to `"data-tooltip"`. Set to `nil` to suppress the data attribute and use only the native `<title>` fallback. See [Tooltips](#tooltips).
|
|
95
|
+
|
|
134
96
|
**Internationalization options:**
|
|
135
97
|
|
|
136
98
|
- `day_labels` - Array of day abbreviations starting from Sunday (7 elements). Defaults to `%w[S M T W T F S]`. See [I18n](#i18n).
|
|
@@ -143,19 +105,11 @@ A **score** is an integer (0 to N-1) that maps directly to a color in your palet
|
|
|
143
105
|
Instead of pre-calculating scores, you can provide raw numeric values (like 45.2, 78, 1000) and let the builder automatically map them to scores using linear distribution:
|
|
144
106
|
|
|
145
107
|
```ruby
|
|
146
|
-
# Linear heatmap with automatic score calculation
|
|
147
|
-
values = [10, 25, 50, 75, 100]
|
|
148
|
-
svg = HeatmapBuilder.build_linear(
|
|
149
|
-
values: values,
|
|
150
|
-
value_min: 0, # Optional: explicitly set minimum (defaults to actual min)
|
|
151
|
-
value_max: 100 # Optional: explicitly set maximum (defaults to actual max)
|
|
152
|
-
)
|
|
153
|
-
|
|
154
108
|
# Calendar heatmap with automatic score calculation
|
|
155
109
|
values_by_date = {
|
|
156
|
-
Date.new(
|
|
157
|
-
Date.new(
|
|
158
|
-
Date.new(
|
|
110
|
+
Date.new(2026, 1, 1) => 45.2,
|
|
111
|
+
Date.new(2026, 1, 2) => 78.5,
|
|
112
|
+
Date.new(2026, 1, 3) => 12.0
|
|
159
113
|
}
|
|
160
114
|
|
|
161
115
|
svg = HeatmapBuilder.build_calendar(
|
|
@@ -177,30 +131,17 @@ By default, values are mapped to scores using linear distribution. You can provi
|
|
|
177
131
|
|
|
178
132
|
The callable receives these parameters:
|
|
179
133
|
- `value:` - The current value being converted
|
|
180
|
-
- `
|
|
134
|
+
- `date:` - The date for the data point
|
|
181
135
|
- `min:` - The minimum boundary value
|
|
182
136
|
- `max:` - The maximum boundary value
|
|
183
137
|
- `max_score:` - The maximum valid score (color palette length minus 1)
|
|
184
138
|
|
|
185
139
|
The function must return an integer between 0 and `max_score`.
|
|
186
140
|
|
|
187
|
-
Custom scoring logic -
|
|
141
|
+
Custom scoring logic - logarithmic scale for data with wide range (e.g., 1 to 10000):
|
|
188
142
|
|
|
189
143
|
```ruby
|
|
190
|
-
|
|
191
|
-
((value - min) / (max - min) * max_score).round
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
svg = HeatmapBuilder.build_linear(
|
|
195
|
-
values: [10, 20, 30],
|
|
196
|
-
value_to_score: linear_formula
|
|
197
|
-
)
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
Logarithmic scale for data with wide range (e.g., 1 to 10000):
|
|
201
|
-
|
|
202
|
-
```ruby
|
|
203
|
-
logarithmic_formula = ->(value:, index:, min:, max:, max_score:) {
|
|
144
|
+
logarithmic_formula = ->(value:, date:, min:, max:, max_score:) {
|
|
204
145
|
return 0 if value <= 0 || min <= 0
|
|
205
146
|
|
|
206
147
|
log_value = Math.log10(value)
|
|
@@ -210,8 +151,8 @@ logarithmic_formula = ->(value:, index:, min:, max:, max_score:) {
|
|
|
210
151
|
((log_value - log_min) / (log_max - log_min) * max_score).round.clamp(0, max_score)
|
|
211
152
|
}
|
|
212
153
|
|
|
213
|
-
svg = HeatmapBuilder.
|
|
214
|
-
values:
|
|
154
|
+
svg = HeatmapBuilder.build_calendar(
|
|
155
|
+
values: values_by_date,
|
|
215
156
|
value_to_score: logarithmic_formula
|
|
216
157
|
)
|
|
217
158
|
```
|
|
@@ -221,13 +162,7 @@ svg = HeatmapBuilder.build_linear(
|
|
|
221
162
|
#### GitHub Green (Default)
|
|
222
163
|
|
|
223
164
|
```ruby
|
|
224
|
-
HeatmapBuilder.
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-

|
|
228
|
-
|
|
229
|
-
```ruby
|
|
230
|
-
HeatmapBuilder.build_calendar(scores: calendar_data, colors: HeatmapBuilder::GITHUB_GREEN)
|
|
165
|
+
HeatmapBuilder.build_calendar(scores: calendar_data, colors: HeatmapBuilder::Calendar::GITHUB_GREEN)
|
|
231
166
|
```
|
|
232
167
|
|
|
233
168
|

|
|
@@ -235,13 +170,7 @@ HeatmapBuilder.build_calendar(scores: calendar_data, colors: HeatmapBuilder::GIT
|
|
|
235
170
|
#### Blue Ocean
|
|
236
171
|
|
|
237
172
|
```ruby
|
|
238
|
-
HeatmapBuilder.
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-

|
|
242
|
-
|
|
243
|
-
```ruby
|
|
244
|
-
HeatmapBuilder.build_calendar(scores: calendar_data, colors: HeatmapBuilder::BLUE_OCEAN)
|
|
173
|
+
HeatmapBuilder.build_calendar(scores: calendar_data, colors: HeatmapBuilder::Calendar::BLUE_OCEAN)
|
|
245
174
|
```
|
|
246
175
|
|
|
247
176
|

|
|
@@ -249,13 +178,7 @@ HeatmapBuilder.build_calendar(scores: calendar_data, colors: HeatmapBuilder::BLU
|
|
|
249
178
|
#### Warm Sunset
|
|
250
179
|
|
|
251
180
|
```ruby
|
|
252
|
-
HeatmapBuilder.
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-

|
|
256
|
-
|
|
257
|
-
```ruby
|
|
258
|
-
HeatmapBuilder.build_calendar(scores: calendar_data, colors: HeatmapBuilder::WARM_SUNSET)
|
|
181
|
+
HeatmapBuilder.build_calendar(scores: calendar_data, colors: HeatmapBuilder::Calendar::WARM_SUNSET)
|
|
259
182
|
```
|
|
260
183
|
|
|
261
184
|

|
|
@@ -263,13 +186,7 @@ HeatmapBuilder.build_calendar(scores: calendar_data, colors: HeatmapBuilder::WAR
|
|
|
263
186
|
#### Purple Vibes
|
|
264
187
|
|
|
265
188
|
```ruby
|
|
266
|
-
HeatmapBuilder.
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-

|
|
270
|
-
|
|
271
|
-
```ruby
|
|
272
|
-
HeatmapBuilder.build_calendar(scores: calendar_data, colors: HeatmapBuilder::PURPLE_VIBES)
|
|
189
|
+
HeatmapBuilder.build_calendar(scores: calendar_data, colors: HeatmapBuilder::Calendar::PURPLE_VIBES)
|
|
273
190
|
```
|
|
274
191
|
|
|
275
192
|

|
|
@@ -277,13 +194,7 @@ HeatmapBuilder.build_calendar(scores: calendar_data, colors: HeatmapBuilder::PUR
|
|
|
277
194
|
#### Red to Green
|
|
278
195
|
|
|
279
196
|
```ruby
|
|
280
|
-
HeatmapBuilder.
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-

|
|
284
|
-
|
|
285
|
-
```ruby
|
|
286
|
-
HeatmapBuilder.build_calendar(scores: calendar_data, colors: HeatmapBuilder::RED_TO_GREEN)
|
|
197
|
+
HeatmapBuilder.build_calendar(scores: calendar_data, colors: HeatmapBuilder::Calendar::RED_TO_GREEN)
|
|
287
198
|
```
|
|
288
199
|
|
|
289
200
|

|
|
@@ -300,44 +211,46 @@ neon_gradient = {
|
|
|
300
211
|
steps: 5
|
|
301
212
|
}
|
|
302
213
|
|
|
303
|
-
svg = HeatmapBuilder.
|
|
214
|
+
svg = HeatmapBuilder.build_calendar(scores: calendar_data, colors: neon_gradient)
|
|
304
215
|
```
|
|
305
216
|
|
|
306
|
-

|
|
307
|
-
|
|
308
217
|
The OKLCH color space ensures perceptually uniform color transitions, making gradients appear smooth and natural to the human eye.
|
|
309
218
|
|
|
310
|
-
###
|
|
219
|
+
### Cell Borders
|
|
311
220
|
|
|
312
|
-
|
|
221
|
+
Each cell has a border whose color is derived from the cell's own color. The `border_width` option sets its thickness, and `border_lightness_factor` controls its shade: the cell color's OKLCH lightness is multiplied by this factor, so values below `1` produce a darker border (the default `0.9` is a subtle darkening).
|
|
313
222
|
|
|
314
|
-
|
|
223
|
+
Setting `border_lightness_factor` to `1` keeps the border color identical to the cell color. In that case the border is invisible, so it is omitted from the SVG entirely.
|
|
315
224
|
|
|
316
225
|
```ruby
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
cell_size: 18
|
|
226
|
+
HeatmapBuilder.build_calendar(
|
|
227
|
+
scores: calendar_data,
|
|
228
|
+
border_width: 1,
|
|
229
|
+
border_lightness_factor: 0.7
|
|
322
230
|
)
|
|
323
231
|
```
|
|
324
232
|
|
|
325
|
-

|
|
326
234
|
|
|
327
|
-
|
|
235
|
+
With `border_lightness_factor` set to `1`, the border is omitted entirely, leaving cells edge to edge:
|
|
328
236
|
|
|
329
237
|
```ruby
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
cell_size: 18
|
|
238
|
+
HeatmapBuilder.build_calendar(
|
|
239
|
+
scores: calendar_data,
|
|
240
|
+
border_width: 1,
|
|
241
|
+
border_lightness_factor: 1
|
|
335
242
|
)
|
|
336
243
|
```
|
|
337
244
|
|
|
338
|
-

|
|
246
|
+
|
|
247
|
+
### Rounded Corners
|
|
339
248
|
|
|
340
|
-
Calendar
|
|
249
|
+
Calendar heatmaps support rounded corners using the `corner_radius` option.
|
|
250
|
+
|
|
251
|
+
The `corner_radius` value must be between 0 (square corners) and `floor(cell_size/2)`. Values outside this range are automatically clamped to the valid range (negative values become 0, values exceeding the maximum become `floor(cell_size/2)`).
|
|
252
|
+
|
|
253
|
+
A typical value is around 2 pixels for a subtle rounded effect:
|
|
341
254
|
|
|
342
255
|
```ruby
|
|
343
256
|
# Calendar heatmap with rounded corners
|
|
@@ -350,6 +263,8 @@ HeatmapBuilder.build_calendar(
|
|
|
350
263
|
|
|
351
264
|

|
|
352
265
|
|
|
266
|
+
Maximum radius values render circular cells:
|
|
267
|
+
|
|
353
268
|
```ruby
|
|
354
269
|
# Calendar heatmap with max radius rounded corners - circular cells
|
|
355
270
|
HeatmapBuilder.build_calendar(
|
|
@@ -361,6 +276,61 @@ HeatmapBuilder.build_calendar(
|
|
|
361
276
|
|
|
362
277
|

|
|
363
278
|
|
|
279
|
+
### Month Spacing
|
|
280
|
+
|
|
281
|
+
Add visual separation between months using the `month_spacing` option. This adds horizontal gaps at month boundaries, making it easier to distinguish individual months:
|
|
282
|
+
|
|
283
|
+
```ruby
|
|
284
|
+
HeatmapBuilder.build_calendar(
|
|
285
|
+
scores: calendar_data,
|
|
286
|
+
cell_size: 14,
|
|
287
|
+
month_spacing: 10,
|
|
288
|
+
corner_radius: 3
|
|
289
|
+
)
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+

|
|
293
|
+
|
|
294
|
+
### Tooltips
|
|
295
|
+
|
|
296
|
+
The `tooltip:` option accepts a callable that is invoked once per active cell. It receives `date:`, `score:`, and `value:` keyword arguments and should return the tooltip text string. `value:` is the original input value when `values:` mode is used, and `nil` when `scores:` mode is used.
|
|
297
|
+
|
|
298
|
+
```ruby
|
|
299
|
+
HeatmapBuilder.build_calendar(
|
|
300
|
+
scores: calendar_data,
|
|
301
|
+
tooltip: ->(date:, score:, value: nil) { "#{date.strftime('%b %-d')}: #{score} contributions" }
|
|
302
|
+
)
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
When `tooltip:` is set, each active cell is wrapped in an SVG `<g>` element containing a `<title>` child. The `<title>` element is the standard SVG mechanism for native browser tooltips — it works out of the box in any browser that renders inline SVG, with no JavaScript required.
|
|
306
|
+
|
|
307
|
+
```xml
|
|
308
|
+
<g data-tooltip="Jan 15: 4 contributions">
|
|
309
|
+
<title>Jan 15: 4 contributions</title>
|
|
310
|
+
<rect fill="#40c463" .../>
|
|
311
|
+
</g>
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
The `tooltip_attribute:` option (default: `"data-tooltip"`) controls which `data-*` attribute is emitted on the `<g>` wrapper. This attribute is the hook for any JS tooltip library:
|
|
315
|
+
|
|
316
|
+
```js
|
|
317
|
+
// Tippy.js — one line of initialization
|
|
318
|
+
tippy('[data-tooltip]', { content: el => el.dataset.tooltip })
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Set `tooltip_attribute: nil` to suppress the data attribute and rely solely on the native `<title>` fallback. Use any other attribute name to match your tooltip library's expected selector:
|
|
322
|
+
|
|
323
|
+
```ruby
|
|
324
|
+
# Matches data-tippy-content used by some Tippy.js configurations
|
|
325
|
+
HeatmapBuilder.build_calendar(
|
|
326
|
+
scores: calendar_data,
|
|
327
|
+
tooltip: ->(date:, score:, value: nil) { "#{date}: #{score}" },
|
|
328
|
+
tooltip_attribute: "data-tippy-content"
|
|
329
|
+
)
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Outside cells rendered via `show_outside_cells: true` never receive tooltip markup.
|
|
333
|
+
|
|
364
334
|
### I18n
|
|
365
335
|
|
|
366
336
|
Calendar heatmaps support internationalization by customizing the `day_labels` and `month_labels` options:
|