poetry-charts 0.1.2 → 0.1.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9ddba8811f1d688f8e5404a49df8831559ee34fb35c84f963f1e50c302b1c3c2
4
- data.tar.gz: a409c8899a1cabbe20c91f0d50adb82a57622308fbbbab5c8af06a3249f42da9
3
+ metadata.gz: 73dfd392d17b87636eeea4e4566432c7cd99db60f86a298e0c8f0e4db0ab975a
4
+ data.tar.gz: 4cffa7098a6f2244c9369584f492f8aaf005ef5738dfc608ac534419c6f7b274
5
5
  SHA512:
6
- metadata.gz: bab7bbbe2b44f740da46aed0f6f9da2be90fdac7b4ba21d2b8157898e83488e920a60a0317ce7f093942f1bd3fdc17c71a246dc33ab027f1ded7b28041bf33d8
7
- data.tar.gz: dfad68141b7d158b2080421e2e57ddcae51445448d3a2754c9b9ae8814bff9fc4e59d470d58cd02c2716b9c28f7e887e2bc191cd19f16fb77927647f12e13899
6
+ metadata.gz: 0ffc568f9e74a322576b7f2003d821349cdd7cf4849073bd3771d709f637ad33325f11bc3c28d9526ddb17fcef06cd727664d9c45cec463728a46e42b3c17331
7
+ data.tar.gz: fcdc5263f1d4c8792bd881f68341630b5ec231c3761df8b7d719ef2bebbbe0a780eac62dfb5ba5df0f60b436eb7feff9987bcde338dd0fdb9dc20af1c60fe1ae
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.4] - 2026-09-15
4
+
5
+ ### Changed
6
+
7
+ - A horizontal bar chart's category strip fits its labels. The reserved left strip held about eight characters at the tick size, so a merchant name or a title was cut at the SVG edge. The strip now grows through the left margin to fit the longest formatted label (estimated by character count, capped at forty percent of the width), labels past the cap end in an ellipsis, and a live chart's recompute prints the same. An explicit `margin: { left: }` is the caller's layout and keeps the reserved strip.
8
+
9
+ ## [0.1.3] - 2026-09-13
10
+
11
+ Lockstep release with the family; no changes in this gem.
12
+
3
13
  ## [0.1.2] - 2026-09-13
4
14
 
5
15
  ### Changed
@@ -72,7 +72,9 @@ module Poetry
72
72
  option :orientation, :symbol, default: :vertical,
73
73
  doc: ":vertical = columns (the default); :horizontal = bars growing rightward " \
74
74
  "- the category axis moves to the Y side (with_y_axis data_key:) and the " \
75
- "numeric axis hides."
75
+ "numeric axis hides. The category strip widens to fit the longest label " \
76
+ "(up to 40% of the width; longer labels end in an ellipsis) unless " \
77
+ "margin: sets left: yourself."
76
78
 
77
79
  validates :offset, inclusion: { in: Cartesian::OFFSETS }
78
80
  validates :orientation, inclusion: { in: Cartesian::LAYOUTS }
@@ -242,9 +244,69 @@ module Poetry
242
244
  formatter ? formatter.call(category).to_s : category.to_s
243
245
  end
244
246
 
245
- # The horizontal layout's category labels (the Y side).
247
+ # The horizontal layout's category labels (the Y side), cut to the
248
+ # strip with an ellipsis when the strip is auto-sized and capped.
246
249
  # @api private
247
250
  def category_tick_label(category)
251
+ label = formatted_category_label(category)
252
+ limit = category_label_max_chars
253
+ limit && label.length > limit ? "#{label[0, limit - 1]}…" : label
254
+ end
255
+
256
+ # The reserved left strip (Cartesian::Y_AXIS_WIDTH) fits about
257
+ # eight characters at the 12px tick size. In the horizontal layout
258
+ # the strip grows through the left margin to fit the longest
259
+ # formatted label - estimated by character count, capped at
260
+ # CATEGORY_STRIP_SHARE of the width - so merchant names and titles
261
+ # are not clipped at the SVG edge. An explicit margin left: is the
262
+ # caller's layout and switches the estimate (and the ellipsis) off.
263
+ CATEGORY_LABEL_CHAR_WIDTH = 6.4
264
+ # The widest the auto-sized strip may grow, as a share of the width.
265
+ CATEGORY_STRIP_SHARE = 0.4
266
+
267
+ # @api private
268
+ def axis_margin
269
+ base = super
270
+ allowance = category_strip_allowance
271
+ allowance.positive? ? base.merge(left: Cartesian::DEFAULT_MARGIN[:left] + allowance) : base
272
+ end
273
+
274
+ # @api private
275
+ def auto_category_strip?
276
+ horizontal? && y_axis? && !(margin || {}).to_h.symbolize_keys.key?(:left)
277
+ end
278
+
279
+ # Pixels the left margin grows by so the strip fits the labels.
280
+ # @api private
281
+ def category_strip_allowance
282
+ return 0 unless auto_category_strip?
283
+
284
+ needed = (longest_category_label * CATEGORY_LABEL_CHAR_WIDTH) + y_axis_config.tick_margin.to_f
285
+ [[needed, category_strip_cap].min - Cartesian::Y_AXIS_WIDTH, 0].max.ceil
286
+ end
287
+
288
+ # @api private
289
+ def category_strip_cap
290
+ (width * CATEGORY_STRIP_SHARE).floor
291
+ end
292
+
293
+ # Characters a label may keep before the ellipsis, nil when the
294
+ # strip is not auto-sized.
295
+ # @api private
296
+ def category_label_max_chars
297
+ return nil unless auto_category_strip?
298
+
299
+ [((category_strip_cap - y_axis_config.tick_margin.to_f) / CATEGORY_LABEL_CHAR_WIDTH).floor, 2].max
300
+ end
301
+
302
+ # @api private
303
+ def longest_category_label
304
+ key = y_axis_config.data_key
305
+ data.map { |row| formatted_category_label(row.to_h[key] || row.to_h[key.to_sym]).length }.max || 0
306
+ end
307
+
308
+ # @api private
309
+ def formatted_category_label(category)
248
310
  formatter = y_axis_config&.tick_formatter
249
311
  formatter ? formatter.call(category).to_s : category.to_s
250
312
  end
@@ -305,12 +367,15 @@ module Poetry
305
367
  {
306
368
  "barGap" => bar_gap,
307
369
  "barCategoryGap" => bar_category_gap,
370
+ "categoryLabelMaxChars" => category_label_max_chars,
308
371
  "series" => series_entries.to_h { |e| [e.key, { "radius" => e.radius }] }
309
372
  }
310
373
  end
311
374
 
312
375
  private :cartesian, :bar_slots, :cells, :bar_path, :cell_fill, :active?
313
376
  private :cell_label, :x_tick_label, :category_tick_label, :y_tick_label, :svg_label_prefix, :coordinates_json
377
+ private :axis_margin, :auto_category_strip?, :category_strip_allowance, :category_strip_cap
378
+ private :category_label_max_chars, :longest_category_label, :formatted_category_label
314
379
  private :live_type, :live_series, :live_axes, :live_x_scale_type, :live_category_axis?, :live_frame_extras
315
380
  end
316
381
  end
@@ -136,13 +136,22 @@ module Poetry
136
136
  # bottom margin the cartesian sees.
137
137
  # @api private
138
138
  def live_margin
139
- base = (margin || {}).to_h.symbolize_keys
139
+ base = axis_margin
140
140
  return base unless brush_config
141
141
 
142
142
  bottom = (base[:bottom] || Cartesian::DEFAULT_MARGIN[:bottom]).to_f
143
143
  base.merge(bottom: bottom + brush_config[:height] + BRUSH_GAP)
144
144
  end
145
145
 
146
+ # Hook: the caller's margin, before the brush reserve - a family
147
+ # that sizes an axis strip from its labels (the bar chart's
148
+ # horizontal category strip) grows a side here, so the server
149
+ # geometry and the client recompute read the same margin.
150
+ # @api private
151
+ def axis_margin
152
+ (margin || {}).to_h.symbolize_keys
153
+ end
154
+
146
155
  # The brush strip's y position - below the plot, above the reserved
147
156
  # margin.
148
157
  # @api private
@@ -288,7 +297,7 @@ module Poetry
288
297
  series_entries.any? { |entry| entry.respond_to?(:labels) && entry.labels }
289
298
  end
290
299
 
291
- private :brush_config, :zoom?, :window_features?, :live_margin, :brush_top, :brush_svg
300
+ private :brush_config, :zoom?, :window_features?, :live_margin, :axis_margin, :brush_top, :brush_svg
292
301
  private :zoom_selection_svg, :window_plot_json, :window_brush_json, :live_payload, :live_payload_json, :live_spec
293
302
  private :live_frame, :live_frame_extras, :ensure_live_compatible!, :live_labels_configured?
294
303
  end
@@ -29,6 +29,11 @@ import {
29
29
  const X_AXIS_HEIGHT = 30
30
30
  const Y_AXIS_WIDTH = 60
31
31
 
32
+ // The server cuts a horizontal chart's category labels to the auto-sized
33
+ // strip with an ellipsis; a recompute prints them the same way.
34
+ const categoryLabel = (label, maxChars) =>
35
+ maxChars && label.length > maxChars ? `${label.slice(0, maxChars - 1)}…` : label
36
+
32
37
  const CURVES = {
33
38
  linear: curveLinear,
34
39
  natural: curveNatural,
@@ -413,7 +418,7 @@ function applyAxes(svg, geometry, payload) {
413
418
  reconcile(yAxis.querySelectorAll("text"), geometry.categories.length).forEach((text, i) => {
414
419
  text.setAttribute("x", fnum(geometry.plotLeft - (frame.yTickMargin ?? 8)))
415
420
  text.setAttribute("y", fnum(geometry.xCenters[i]))
416
- text.textContent = String(geometry.categories[i])
421
+ text.textContent = categoryLabel(String(geometry.categories[i]), frame.categoryLabelMaxChars)
417
422
  })
418
423
  } else {
419
424
  reconcile(yAxis.querySelectorAll("text"), geometry.yTicks.length).forEach((text, i) => {
@@ -583,7 +583,9 @@ components:
583
583
  default: vertical
584
584
  description: ":vertical = columns (the default); :horizontal = bars growing
585
585
  rightward - the category axis moves to the Y side (with_y_axis data_key:)
586
- and the numeric axis hides."
586
+ and the numeric axis hides. The category strip widens to fit the longest label
587
+ (up to 40% of the width; longer labels end in an ellipsis) unless margin:
588
+ sets left: yourself."
587
589
  - name: sync
588
590
  type: string
589
591
  - name: width
@@ -3,6 +3,6 @@
3
3
  module Poetry
4
4
  module Charts
5
5
  # The gem version.
6
- VERSION = "0.1.2"
6
+ VERSION = "0.1.4"
7
7
  end
8
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: poetry-charts
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Solt
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.1.2
18
+ version: 0.1.4
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.1.2
25
+ version: 0.1.4
26
26
  description: Charts whose geometry is computed on the server (Ruby; d3-scale/d3-shape
27
27
  semantics, recharts' nice ticks) and shipped as finished SVG - no-JS valid, CSS-variable
28
28
  themed, zero client chart math. Stimulus chrome reads server-embedded coordinates
@@ -180,7 +180,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
180
180
  - !ruby/object:Gem::Version
181
181
  version: '0'
182
182
  requirements: []
183
- rubygems_version: 4.0.16
183
+ rubygems_version: 4.0.20
184
184
  specification_version: 4
185
185
  summary: 'Poetry''s chart tier: server-rendered SVG charts for Rails.'
186
186
  test_files: []