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 +4 -4
- data/CHANGELOG.md +10 -0
- data/app/components/poetry/charts/bar_chart/component.rb +67 -2
- data/app/components/poetry/charts/live.rb +11 -2
- data/app/javascript/poetry/charts/live/renderer.js +6 -1
- data/config/component_registry.yml +3 -1
- data/lib/poetry/charts/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 73dfd392d17b87636eeea4e4566432c7cd99db60f86a298e0c8f0e4db0ab975a
|
|
4
|
+
data.tar.gz: 4cffa7098a6f2244c9369584f492f8aaf005ef5738dfc608ac534419c6f7b274
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 =
|
|
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
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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: []
|