heatmap-builder 0.1.0 → 0.2.0

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +1 -1
  3. data/.gitignore +3 -0
  4. data/CHANGELOG.md +33 -2
  5. data/Gemfile.lock +9 -1
  6. data/README.md +340 -37
  7. data/Rakefile +15 -0
  8. data/bin/generate_examples +234 -0
  9. data/examples/calendar_blue_ocean.svg +1 -0
  10. data/examples/calendar_default.svg +1 -0
  11. data/examples/calendar_github_style.svg +1 -3
  12. data/examples/calendar_purple_vibes.svg +1 -0
  13. data/examples/calendar_red_to_green.svg +1 -0
  14. data/examples/calendar_rounded_corners.svg +1 -0
  15. data/examples/calendar_rounded_corners_max_radius.svg +1 -0
  16. data/examples/calendar_sunday_start.svg +1 -3
  17. data/examples/calendar_warm_sunset.svg +1 -0
  18. data/examples/calendar_with_outside_cells.svg +1 -3
  19. data/examples/large_cells.svg +1 -3
  20. data/examples/linear_blue_ocean.svg +1 -0
  21. data/examples/linear_github_green.svg +1 -0
  22. data/examples/linear_neon_gradient.svg +1 -0
  23. data/examples/linear_purple_vibes.svg +1 -0
  24. data/examples/linear_red_to_green.svg +1 -0
  25. data/examples/linear_rounded_corners.svg +1 -0
  26. data/examples/linear_rounded_corners_max_radius.svg +1 -0
  27. data/examples/linear_warm_sunset.svg +1 -0
  28. data/examples/weekly_progress.svg +1 -3
  29. data/heatmap-builder.gemspec +1 -0
  30. data/lib/heatmap-builder.rb +47 -3
  31. data/lib/heatmap_builder/builder.rb +100 -0
  32. data/lib/heatmap_builder/calendar_heatmap_builder.rb +177 -162
  33. data/lib/heatmap_builder/color_helpers.rb +170 -0
  34. data/lib/heatmap_builder/linear_heatmap_builder.rb +49 -103
  35. data/lib/heatmap_builder/svg_helpers.rb +75 -0
  36. data/lib/heatmap_builder/value_conversion.rb +64 -0
  37. data/lib/heatmap_builder/version.rb +1 -1
  38. metadata +36 -4
  39. data/examples/generate_samples.rb +0 -114
  40. data/mise.toml +0 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 89293cf9e6068ce5530ee73db83843afaef78d4a76aa8b228e1967eee045a5a6
4
- data.tar.gz: 762d8a6ac9b4f9186a508484c02d765331550dab22410a0ad0c042ac1fd7d673
3
+ metadata.gz: cf666db73abc3356f19fea3869f4a6bb4af21c28a2036f2926c811250636c904
4
+ data.tar.gz: 6ba3349f270af6ef2216d7501888187aa39e2c11d8b874d084e861cf3f41ef8b
5
5
  SHA512:
6
- metadata.gz: 4b9c8d43222905f84007b4664a713a337f229d5220f6242f090daa7a220a1345b3d92e848560141bcfcf99a18eed10b5ca6ad33d46fe6bd4eaefb40fbb016403
7
- data.tar.gz: 71a3a436f449d1703120167c7775200d721cf6a5419737423ca2eca4b890d23e3c2a38cbf86bcb36fa103d5863c852d2d0be8cb2bc1eeaa1f47bdf68badd3abe
6
+ metadata.gz: 65f06b77aeeda0e45054bbfe402b9d344bf2996a7a5423ca17ab005f79a2aaced93a656eb830294d021b46c2c7ad3c01338553d7d8ece9259d7208ace6ece1b9
7
+ data.tar.gz: 5fdb4ea1d6c8ff1f7d4966cadd13f6cbeb06916754bdd1e68e553b58632cbc6d49a154f7629f8fc1bb127513dc848831ae87c6d26503f94c29510d8772ee0e08
@@ -11,7 +11,7 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
  strategy:
13
13
  matrix:
14
- ruby-version: ['3.0', '3.1', '3.2', '3.3']
14
+ ruby-version: ['3.0', '3.1', '3.2', '3.3', '3.4']
15
15
 
16
16
  steps:
17
17
  - uses: actions/checkout@v4
data/.gitignore CHANGED
@@ -10,3 +10,6 @@
10
10
 
11
11
  # Gem build artifacts
12
12
  *.gem
13
+
14
+ # Development environment config
15
+ mise.toml
data/CHANGELOG.md CHANGED
@@ -7,10 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2025-10-02
11
+
12
+ ### Added
13
+ - Support for raw numeric values with automatic score calculation using linear distribution
14
+ - `values:` parameter as alternative to `scores:` for both linear and calendar heatmaps
15
+ - `value_min:` and `value_max:` options for explicit boundary control
16
+ - `value_to_score:` option for custom value-to-score conversion functions
17
+ - Automatic boundary detection from input data when not explicitly specified
18
+ - Dynamic color palette generation using OKLCH color space interpolation
19
+ - Rounded corners support for heatmap cells via `corner_radius` option
20
+ - Test coverage reporting with SimpleCov
21
+ - Snapshot testing for visual regression testing
22
+ - YARD documentation for public API methods
23
+ - Logarithmic scale example for custom scoring logic
24
+ - Detailed options documentation in README
25
+
26
+ ### Changed
27
+ - Primary API now uses keyword arguments (`scores:`, `values:`, etc.) for clarity and flexibility
28
+ - Refactored common validation logic into base Builder class
29
+ - Improved test suite with Minitest specs syntax
30
+ - Enhanced README with better organization and forward references
31
+ - Renamed `num_scores` parameter to `max_score` for better clarity in custom scoring functions
32
+ - Automatic corner radius clamping to valid range
33
+
34
+ ### Deprecated
35
+ - `HeatmapBuilder.generate(scores, options)` - use `HeatmapBuilder.build_linear(scores: scores, **options)` instead
36
+ - `HeatmapBuilder.generate_calendar(scores, options)` - use `HeatmapBuilder.build_calendar(scores: scores, **options)` instead
37
+ - Old API still works with deprecation warnings for backward compatibility
38
+
10
39
  ## [0.1.0] - 2025-09-19
11
40
 
41
+ Initial release with core heatmap visualization capabilities.
42
+
12
43
  ### Added
13
- - Initial release of HeatmapBuilder gem
14
44
  - Linear heatmap generation with `HeatmapBuilder.generate()`
15
45
  - Calendar heatmap generation with `HeatmapBuilder.generate_calendar()`
16
46
  - GitHub-style color schemes and styling
@@ -18,5 +48,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
18
48
  - Support for custom start of week (Monday/Sunday)
19
49
  - SVG output format for perfect scaling
20
50
 
21
- [Unreleased]: https://github.com/dreikanter/heatmap-builder/compare/v0.1.0...HEAD
51
+ [Unreleased]: https://github.com/dreikanter/heatmap-builder/compare/v0.2.0...HEAD
52
+ [0.2.0]: https://github.com/dreikanter/heatmap-builder/compare/v0.1.0...v0.2.0
22
53
  [0.1.0]: https://github.com/dreikanter/heatmap-builder/releases/tag/v0.1.0
data/Gemfile.lock CHANGED
@@ -1,12 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- heatmap-builder (0.1.0)
4
+ heatmap-builder (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
9
  ast (2.4.3)
10
+ docile (1.4.1)
10
11
  json (2.14.1)
11
12
  language_server-protocol (3.17.0.5)
12
13
  lint_roller (1.1.0)
@@ -39,6 +40,12 @@ GEM
39
40
  rubocop (>= 1.75.0, < 2.0)
40
41
  rubocop-ast (>= 1.38.0, < 2.0)
41
42
  ruby-progressbar (1.13.0)
43
+ simplecov (0.22.0)
44
+ docile (~> 1.1)
45
+ simplecov-html (~> 0.11)
46
+ simplecov_json_formatter (~> 0.1)
47
+ simplecov-html (0.13.2)
48
+ simplecov_json_formatter (0.1.4)
42
49
  standard (1.51.1)
43
50
  language_server-protocol (~> 3.17.0.2)
44
51
  lint_roller (~> 1.0)
@@ -64,6 +71,7 @@ DEPENDENCIES
64
71
  heatmap-builder!
65
72
  minitest (~> 5.0)
66
73
  rake (~> 13.0)
74
+ simplecov (~> 0.22)
67
75
  standard (~> 1.0)
68
76
 
69
77
  BUNDLED WITH
data/README.md CHANGED
@@ -8,10 +8,15 @@ A Ruby gem that generates embeddable SVG heatmap visualizations with GitHub-styl
8
8
 
9
9
  - GitHub-style calendar layouts for date-based data.
10
10
  - Linear heatmaps.
11
+ - Vector-based output (SVG) for crisp rendering at any resolution.
12
+ - Optional numeric values displayed in each cell.
13
+ - **Use pre-calculated scores or raw numeric values** - automatic mapping to color scales.
14
+ - Custom value-to-score conversion functions for advanced scoring logic.
11
15
  - Parametric everything: customize cell size, spacing, colors, fonts, etc.
12
- - Shows numeric values in each cell.
16
+ - Rounded corners (and circular cells, if you're into that kind of thing).
17
+ - Dynamic palette generation from two colors or manually-specified colors.
18
+ - OKLCH color interpolation for clean color transitions and perceptual uniformity.
13
19
  - **Zero dependencies.**
14
- - Responsive: SVG format scales perfectly at any size
15
20
 
16
21
  ## Installation
17
22
 
@@ -38,10 +43,10 @@ require 'heatmap-builder'
38
43
 
39
44
  # Generate SVG for daily scores
40
45
  scores = [0, 1, 2, 3, 4, 5, 2, 1]
41
- svg = HeatmapBuilder.generate(scores)
46
+ svg = HeatmapBuilder.build_linear(scores: scores)
42
47
 
43
48
  # In a Rails view
44
- <%= raw HeatmapBuilder.generate(@daily_scores) %>
49
+ <%= raw HeatmapBuilder.build_linear(scores: @daily_scores) %>
45
50
  ```
46
51
 
47
52
  ![Weekly Progress](examples/weekly_progress.svg)
@@ -57,64 +62,362 @@ scores_by_date = {
57
62
  # ... more dates
58
63
  }
59
64
 
60
- svg = HeatmapBuilder.generate_calendar(scores_by_date)
65
+ svg = HeatmapBuilder.build_calendar(scores: scores_by_date)
61
66
  ```
62
67
 
63
- ### Custom Configuration
68
+ ![GitHub-style Calendar](examples/calendar_github_style.svg)
69
+
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
+ ### Calendar Heatmap Options
99
+
100
+ You must provide either `scores:` or `values:` (but not both). All other options are optional keyword arguments with sensible defaults.
101
+
102
+ **Data options:**
103
+
104
+ - `scores` - Hash of pre-calculated scores by date (integers from 0 to number of colors minus 1). Keys can be Date objects or date strings (e.g., `'2024-01-01'`). Required if `values` is not provided.
105
+ - `values` - Hash of arbitrary numeric values by date to be automatically mapped to scores. Keys can be Date objects or date strings. Required if `scores` is not provided. See [Using Raw Values Instead of Scores](#using-raw-values-instead-of-scores).
106
+
107
+ **Value-to-score conversion options** (only used with `values:`):
108
+
109
+ - `value_min` - Minimum boundary for value-to-score mapping. Defaults to the minimum value in your data.
110
+ - `value_max` - Maximum boundary for value-to-score mapping. Defaults to the maximum value in your data.
111
+ - `value_to_score` - Custom callable for value-to-score conversion. Receives `value:`, `date:`, `min:`, `max:`, `max_score:` parameters and must return an integer between 0 and `max_score`. See [Custom Scoring Logic](#custom-scoring-logic) for details.
112
+
113
+ **Appearance options:**
114
+
115
+ - `cell_size` - Size of each square in pixels. Defaults to 12.
116
+ - `cell_spacing` - Space between squares in pixels. Defaults to 1.
117
+ - `font_size` - Font size for labels in pixels. Defaults to 8.
118
+ - `border_width` - Border width around each cell in pixels. Defaults to 1.
119
+ - `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
+
122
+ **Color options:**
123
+
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).
125
+
126
+ **Calendar-specific options:**
127
+
128
+ - `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 5.
130
+ - `show_month_labels` - Show month names at the top of the calendar. Defaults to `true`.
131
+ - `show_day_labels` - Show day abbreviations on the left side of the calendar. Defaults to `true`.
132
+ - `show_outside_cells` - Show cells outside the date range with inactive styling. Defaults to `false`.
133
+
134
+ **Internationalization options:**
135
+
136
+ - `day_labels` - Array of day abbreviations starting from Sunday (7 elements). Defaults to `%w[S M T W T F S]`. See [I18n](#i18n).
137
+ - `month_labels` - Array of month abbreviations from January to December (12 elements). Defaults to `%w[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec]`. See [I18n](#i18n).
138
+
139
+ ### Using Raw Values Instead of Scores
140
+
141
+ A **score** is an integer (0 to N-1) that maps directly to a color in your palette. For example, with 5 colors, valid scores are 0-4.
142
+
143
+ 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
+
145
+ ```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
+ # Calendar heatmap with automatic score calculation
155
+ values_by_date = {
156
+ Date.new(2024, 1, 1) => 45.2,
157
+ Date.new(2024, 1, 2) => 78.5,
158
+ Date.new(2024, 1, 3) => 12.0
159
+ }
160
+
161
+ svg = HeatmapBuilder.build_calendar(
162
+ values: values_by_date,
163
+ value_min: 0,
164
+ value_max: 100
165
+ )
166
+ ```
167
+
168
+ The builder will automatically:
169
+ - Calculate min/max boundaries from your data if not specified
170
+ - Map values to color scores using linear distribution
171
+ - Clamp values outside the boundaries
172
+ - Handle nil values by treating them as the minimum boundary
173
+
174
+ ### Custom Scoring Logic
175
+
176
+ By default, values are mapped to scores using linear distribution. You can provide a custom value-to-score conversion function for different behaviors like logarithmic scales, exponential curves, or custom thresholds.
177
+
178
+ The callable receives these parameters:
179
+ - `value:` - The current value being converted
180
+ - `index:` or `date:` - The position in the data (linear heatmaps use `index:`, calendar heatmaps use `date:`)
181
+ - `min:` - The minimum boundary value
182
+ - `max:` - The maximum boundary value
183
+ - `max_score:` - The maximum valid score (color palette length minus 1)
184
+
185
+ The function must return an integer between 0 and `max_score`.
186
+
187
+ Custom scoring logic - linear distribution example:
64
188
 
65
189
  ```ruby
66
- # Customize linear heatmap appearance
67
- options = {
68
- cell_size: 35, # Size of each square (default: 20)
69
- cell_spacing: 1, # Space between squares (default: 2)
70
- font_size: 20, # Font size for score text (default: 12)
71
- colors: %w[ # Custom color palette (default: GitHub-style)
72
- #f0f0f0
73
- #c6e48b
74
- #7bc96f
75
- #239a3b
76
- #196127
77
- ]
190
+ linear_formula = ->(value:, index:, min:, max:, max_score:) {
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:) {
204
+ return 0 if value <= 0 || min <= 0
205
+
206
+ log_value = Math.log10(value)
207
+ log_min = Math.log10(min)
208
+ log_max = Math.log10(max)
209
+
210
+ ((log_value - log_min) / (log_max - log_min) * max_score).round.clamp(0, max_score)
78
211
  }
79
212
 
80
- svg = HeatmapBuilder.generate([1, 2, 3, 4, 5, 6, 7], options)
213
+ svg = HeatmapBuilder.build_linear(
214
+ values: [1, 10, 100, 1000, 10000],
215
+ value_to_score: logarithmic_formula
216
+ )
217
+ ```
218
+
219
+ ### Predefined Color Palettes
220
+
221
+ #### GitHub Green (Default)
222
+
223
+ ```ruby
224
+ HeatmapBuilder.build_linear(scores: scores, colors: HeatmapBuilder::GITHUB_GREEN)
225
+ ```
226
+
227
+ ![GitHub Green Linear](examples/linear_github_green.svg)
228
+
229
+ ```ruby
230
+ HeatmapBuilder.build_calendar(scores: calendar_data, colors: HeatmapBuilder::GITHUB_GREEN)
231
+ ```
232
+
233
+ ![Default Calendar](examples/calendar_default.svg)
234
+
235
+ #### Blue Ocean
236
+
237
+ ```ruby
238
+ HeatmapBuilder.build_linear(scores: scores, colors: HeatmapBuilder::BLUE_OCEAN)
239
+ ```
240
+
241
+ ![Blue Ocean Linear](examples/linear_blue_ocean.svg)
242
+
243
+ ```ruby
244
+ HeatmapBuilder.build_calendar(scores: calendar_data, colors: HeatmapBuilder::BLUE_OCEAN)
245
+ ```
246
+
247
+ ![Blue Ocean Calendar](examples/calendar_blue_ocean.svg)
248
+
249
+ #### Warm Sunset
250
+
251
+ ```ruby
252
+ HeatmapBuilder.build_linear(scores: scores, colors: HeatmapBuilder::WARM_SUNSET)
253
+ ```
254
+
255
+ ![Warm Sunset Linear](examples/linear_warm_sunset.svg)
256
+
257
+ ```ruby
258
+ HeatmapBuilder.build_calendar(scores: calendar_data, colors: HeatmapBuilder::WARM_SUNSET)
81
259
  ```
82
260
 
83
- ![Large Cells](examples/large_cells.svg)
261
+ ![Warm Sunset Calendar](examples/calendar_warm_sunset.svg)
262
+
263
+ #### Purple Vibes
264
+
265
+ ```ruby
266
+ HeatmapBuilder.build_linear(scores: scores, colors: HeatmapBuilder::PURPLE_VIBES)
267
+ ```
268
+
269
+ ![Purple Vibes Linear](examples/linear_purple_vibes.svg)
270
+
271
+ ```ruby
272
+ HeatmapBuilder.build_calendar(scores: calendar_data, colors: HeatmapBuilder::PURPLE_VIBES)
273
+ ```
274
+
275
+ ![Purple Vibes Calendar](examples/calendar_purple_vibes.svg)
276
+
277
+ #### Red to Green
278
+
279
+ ```ruby
280
+ HeatmapBuilder.build_linear(scores: scores, colors: HeatmapBuilder::RED_TO_GREEN)
281
+ ```
282
+
283
+ ![Red to Green Linear](examples/linear_red_to_green.svg)
284
+
285
+ ```ruby
286
+ HeatmapBuilder.build_calendar(scores: calendar_data, colors: HeatmapBuilder::RED_TO_GREEN)
287
+ ```
288
+
289
+ ![Red to Green Calendar](examples/calendar_red_to_green.svg)
290
+
291
+ ### Dynamic Palettes Generation
292
+
293
+ Generate custom color palettes from any two colors using OKLCH color space for superior color interpolation:
84
294
 
85
295
  ```ruby
86
- # Calendar heatmap options
87
- calendar_options = {
88
- cell_size: 14,
89
- start_of_week: :sunday, # :monday (default) or :sunday
90
- show_outside_cells: true # Show cells outside date range
296
+ # Generate a 5-step palette from electric cyan to hot magenta
297
+ neon_gradient = {
298
+ from: "#00FFFF",
299
+ to: "#FF1493",
300
+ steps: 5
91
301
  }
92
302
 
93
- svg = HeatmapBuilder.generate_calendar(scores_by_date, calendar_options)
303
+ svg = HeatmapBuilder.build_linear(scores: scores, colors: neon_gradient)
304
+ ```
305
+
306
+ ![Neon Gradient Linear](examples/linear_neon_gradient.svg)
307
+
308
+ The OKLCH color space ensures perceptually uniform color transitions, making gradients appear smooth and natural to the human eye.
309
+
310
+ ### Rounded Corners
311
+
312
+ Both linear and calendar heatmaps support rounded corners using the `corner_radius` option.
313
+
314
+ A typical value is around 2 pixels for a subtle rounded effect:
315
+
316
+ ```ruby
317
+ # Linear heatmap with rounded corners
318
+ HeatmapBuilder.build_linear(
319
+ scores: scores,
320
+ corner_radius: 2,
321
+ cell_size: 18
322
+ )
323
+ ```
324
+
325
+ ![Linear Rounded Corners](examples/linear_rounded_corners.svg)
326
+
327
+ 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)`). Maximum radius values render circular cells:
328
+
329
+ ```ruby
330
+ # Linear heatmap with max radius rounded corners - circular cells
331
+ HeatmapBuilder.build_linear(
332
+ scores: scores,
333
+ corner_radius: 9,
334
+ cell_size: 18
335
+ )
94
336
  ```
95
337
 
96
- ![Calendar with Sunday Start](examples/calendar_sunday_start.svg)
338
+ ![Linear Rounded Corners](examples/linear_rounded_corners_max_radius.svg)
97
339
 
98
- ### Color Mapping
340
+ Calendar heatmap examples:
99
341
 
100
- - Score `0`: Uses the first color (typically light gray)
101
- - Score `1+`: Cycles through remaining colors based on score value
102
- - Higher scores automatically map to available colors in the palette
342
+ ```ruby
343
+ # Calendar heatmap with rounded corners
344
+ HeatmapBuilder.build_calendar(
345
+ scores: calendar_data,
346
+ corner_radius: 2,
347
+ cell_size: 14
348
+ )
349
+ ```
350
+
351
+ ![Calendar Rounded Corners](examples/calendar_rounded_corners.svg)
352
+
353
+ ```ruby
354
+ # Calendar heatmap with max radius rounded corners - circular cells
355
+ HeatmapBuilder.build_calendar(
356
+ scores: calendar_data,
357
+ corner_radius: 7,
358
+ cell_size: 14
359
+ )
360
+ ```
361
+
362
+ ![Calendar Rounded Corners](examples/calendar_rounded_corners_max_radius.svg)
363
+
364
+ ### I18n
365
+
366
+ Calendar heatmaps support internationalization by customizing the `day_labels` and `month_labels` options:
367
+
368
+ ```ruby
369
+ # French calendar
370
+ HeatmapBuilder.build_calendar(
371
+ scores: calendar_data,
372
+ day_labels: %w[D L M M J V S], # Dimanche, Lundi, Mardi, etc.
373
+ month_labels: %w[Jan Fév Mar Avr Mai Jun Jul Aoû Sep Oct Nov Déc]
374
+ )
375
+
376
+ # German calendar
377
+ HeatmapBuilder.build_calendar(
378
+ scores: calendar_data,
379
+ day_labels: %w[S M D M D F S], # Sonntag, Montag, Dienstag, etc.
380
+ month_labels: %w[Jan Feb Mär Apr Mai Jun Jul Aug Sep Okt Nov Dez]
381
+ )
382
+
383
+ # Italian calendar
384
+ HeatmapBuilder.build_calendar(
385
+ scores: calendar_data,
386
+ day_labels: %w[D L M M G V S], # Domenica, Lunedì, Martedì, etc.
387
+ month_labels: %w[Gen Feb Mar Apr Mag Giu Lug Ago Set Ott Nov Dic]
388
+ )
389
+
390
+ # Spanish calendar
391
+ HeatmapBuilder.build_calendar(
392
+ scores: calendar_data,
393
+ day_labels: %w[D L M X J V S], # Domingo, Lunes, Martes, etc.
394
+ month_labels: %w[Ene Feb Mar Abr May Jun Jul Ago Sep Oct Nov Dic]
395
+ )
396
+ ```
397
+
398
+ The `day_labels` array should contain 7 elements starting from Sunday, and `month_labels` should contain 12 elements for January through December.
103
399
 
104
400
  ## Development
105
401
 
106
- After checking out the repo, run `bin/setup` to install dependencies. Run tests with:
402
+ After checking out the repo, run `bin/setup` to install development dependencies.
403
+
404
+ ### Running Tests
107
405
 
108
406
  ```bash
109
- ruby -Ilib:test test/heatmap_builder_test.rb
110
- ```
407
+ # Run all tests
408
+ rake test
409
+
410
+ # Run tests with code linting
411
+ rake
111
412
 
112
- To install this gem onto your local machine, run `bundle exec rake install`.
413
+ # Update test snapshots after making intentional changes to output
414
+ rake update_snapshots
415
+ ```
113
416
 
114
417
  To generate all example SVG files you see in this readme:
115
418
 
116
419
  ```bash
117
- ruby examples/generate_samples.rb
420
+ bin/generate_examples
118
421
  ```
119
422
 
120
423
  ## Contributing
@@ -127,4 +430,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
127
430
 
128
431
  ## Code of Conduct
129
432
 
130
- Everyone interacting in the HeatmapBuilder project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/dreikanter/heatmap-builder/blob/master/CODE_OF_CONDUCT.md).
433
+ Everyone interacting in the HeatmapBuilder project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/dreikanter/heatmap-builder/blob/main/CODE_OF_CONDUCT.md).
data/Rakefile CHANGED
@@ -13,4 +13,19 @@ rescue LoadError
13
13
  # standard is not available
14
14
  end
15
15
 
16
+ desc "Update test snapshots"
17
+ task :update_snapshots do
18
+ require "fileutils"
19
+ snapshots_dir = File.expand_path("test/snapshots", __dir__)
20
+
21
+ if Dir.exist?(snapshots_dir)
22
+ puts "Removing existing snapshots..."
23
+ FileUtils.rm_rf(Dir["#{snapshots_dir}/*"])
24
+ end
25
+
26
+ puts "Regenerating snapshots..."
27
+ ENV["UPDATE_SNAPSHOTS"] = "1"
28
+ Rake::Task[:test].invoke
29
+ end
30
+
16
31
  task default: [:standard, :test]