poetry-charts 0.0.2

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 (124) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +6 -0
  3. data/CHANGELOG.md +3 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +94 -0
  6. data/THIRD_PARTY_NOTICES.md +39 -0
  7. data/app/assets/stylesheets/poetry-charts.css +117 -0
  8. data/app/components/poetry/charts/adapter_chart/component.html.erb +6 -0
  9. data/app/components/poetry/charts/adapter_chart/component.rb +99 -0
  10. data/app/components/poetry/charts/adapter_chart/preview.rb +37 -0
  11. data/app/components/poetry/charts/adapter_chart/style.rb +16 -0
  12. data/app/components/poetry/charts/area_chart/component.html.erb +75 -0
  13. data/app/components/poetry/charts/area_chart/component.rb +251 -0
  14. data/app/components/poetry/charts/area_chart/preview.rb +95 -0
  15. data/app/components/poetry/charts/area_chart/style.rb +29 -0
  16. data/app/components/poetry/charts/bar_chart/component.html.erb +95 -0
  17. data/app/components/poetry/charts/bar_chart/component.rb +318 -0
  18. data/app/components/poetry/charts/bar_chart/preview.rb +115 -0
  19. data/app/components/poetry/charts/bar_chart/style.rb +28 -0
  20. data/app/components/poetry/charts/bar_math.rb +95 -0
  21. data/app/components/poetry/charts/cartesian_family.rb +105 -0
  22. data/app/components/poetry/charts/chart_family.rb +58 -0
  23. data/app/components/poetry/charts/composed_chart/component.html.erb +95 -0
  24. data/app/components/poetry/charts/composed_chart/component.rb +309 -0
  25. data/app/components/poetry/charts/composed_chart/preview.rb +62 -0
  26. data/app/components/poetry/charts/composed_chart/style.rb +24 -0
  27. data/app/components/poetry/charts/container/component.html.erb +4 -0
  28. data/app/components/poetry/charts/container/component.rb +80 -0
  29. data/app/components/poetry/charts/container/preview.rb +45 -0
  30. data/app/components/poetry/charts/container/style.rb +22 -0
  31. data/app/components/poetry/charts/error_bars.rb +35 -0
  32. data/app/components/poetry/charts/legend_content/component.html.erb +10 -0
  33. data/app/components/poetry/charts/legend_content/component.rb +122 -0
  34. data/app/components/poetry/charts/legend_content/preview.rb +23 -0
  35. data/app/components/poetry/charts/legend_content/style.rb +26 -0
  36. data/app/components/poetry/charts/line_chart/component.html.erb +85 -0
  37. data/app/components/poetry/charts/line_chart/component.rb +252 -0
  38. data/app/components/poetry/charts/line_chart/preview.rb +115 -0
  39. data/app/components/poetry/charts/line_chart/style.rb +28 -0
  40. data/app/components/poetry/charts/live.rb +296 -0
  41. data/app/components/poetry/charts/motion.rb +98 -0
  42. data/app/components/poetry/charts/pie_chart/component.html.erb +36 -0
  43. data/app/components/poetry/charts/pie_chart/component.rb +256 -0
  44. data/app/components/poetry/charts/pie_chart/preview.rb +100 -0
  45. data/app/components/poetry/charts/pie_chart/style.rb +19 -0
  46. data/app/components/poetry/charts/polar_family.rb +95 -0
  47. data/app/components/poetry/charts/radar_chart/component.html.erb +73 -0
  48. data/app/components/poetry/charts/radar_chart/component.rb +329 -0
  49. data/app/components/poetry/charts/radar_chart/preview.rb +97 -0
  50. data/app/components/poetry/charts/radar_chart/style.rb +21 -0
  51. data/app/components/poetry/charts/radial_bar_chart/component.html.erb +65 -0
  52. data/app/components/poetry/charts/radial_bar_chart/component.rb +424 -0
  53. data/app/components/poetry/charts/radial_bar_chart/preview.rb +99 -0
  54. data/app/components/poetry/charts/radial_bar_chart/style.rb +27 -0
  55. data/app/components/poetry/charts/reference_marks.rb +156 -0
  56. data/app/components/poetry/charts/scatter_chart/component.html.erb +55 -0
  57. data/app/components/poetry/charts/scatter_chart/component.rb +396 -0
  58. data/app/components/poetry/charts/scatter_chart/preview.rb +93 -0
  59. data/app/components/poetry/charts/scatter_chart/style.rb +22 -0
  60. data/app/components/poetry/charts/tooltip_content/component.html.erb +19 -0
  61. data/app/components/poetry/charts/tooltip_content/component.rb +168 -0
  62. data/app/components/poetry/charts/tooltip_content/preview.rb +39 -0
  63. data/app/components/poetry/charts/tooltip_content/style.rb +31 -0
  64. data/app/components/poetry/charts/tooltip_layer/component.html.erb +6 -0
  65. data/app/components/poetry/charts/tooltip_layer/component.rb +61 -0
  66. data/app/components/poetry/charts/tooltip_layer/style.rb +13 -0
  67. data/app/components/poetry/charts/tooltip_wiring.rb +167 -0
  68. data/app/helpers/poetry/charts/components_helper.rb +125 -0
  69. data/app/javascript/poetry/charts/adapter_controller.js +75 -0
  70. data/app/javascript/poetry/charts/adapter_registry.js +52 -0
  71. data/app/javascript/poetry/charts/adapters/chartjs.js +86 -0
  72. data/app/javascript/poetry/charts/d3.js +14 -0
  73. data/app/javascript/poetry/charts/index.js +67 -0
  74. data/app/javascript/poetry/charts/live/renderer.js +456 -0
  75. data/app/javascript/poetry/charts/live_controller.js +195 -0
  76. data/app/javascript/poetry/charts/motion/flip.js +169 -0
  77. data/app/javascript/poetry/charts/motion/sector.js +62 -0
  78. data/app/javascript/poetry/charts/motion/tween.js +105 -0
  79. data/app/javascript/poetry/charts/motion_controller.js +230 -0
  80. data/app/javascript/poetry/charts/tooltip_controller.js +331 -0
  81. data/app/javascript/poetry/charts/window_controller.js +195 -0
  82. data/config/component_descriptions.yml +14 -0
  83. data/config/component_registry.yml +3055 -0
  84. data/config/controllers_manifest.json +100 -0
  85. data/config/importmap.rb +10 -0
  86. data/lib/poetry/charts/cartesian.rb +277 -0
  87. data/lib/poetry/charts/config.rb +149 -0
  88. data/lib/poetry/charts/engine.rb +76 -0
  89. data/lib/poetry/charts/geometry/area.rb +68 -0
  90. data/lib/poetry/charts/geometry/curve.rb +327 -0
  91. data/lib/poetry/charts/geometry/line.rb +64 -0
  92. data/lib/poetry/charts/geometry/nice_ticks.rb +220 -0
  93. data/lib/poetry/charts/geometry/path.rb +96 -0
  94. data/lib/poetry/charts/geometry/scale/band.rb +81 -0
  95. data/lib/poetry/charts/geometry/scale/linear.rb +127 -0
  96. data/lib/poetry/charts/geometry/stack.rb +129 -0
  97. data/lib/poetry/charts/geometry/ticks.rb +103 -0
  98. data/lib/poetry/charts/geometry.rb +66 -0
  99. data/lib/poetry/charts/polar.rb +179 -0
  100. data/lib/poetry/charts/spec.rb +108 -0
  101. data/lib/poetry/charts/theme_style.rb +53 -0
  102. data/lib/poetry/charts/version.rb +8 -0
  103. data/lib/poetry/charts.rb +114 -0
  104. data/lib/poetry-charts.rb +4 -0
  105. data/themes/default.css +146 -0
  106. data/themes/luma.css +139 -0
  107. data/themes/lyra.css +140 -0
  108. data/themes/maia.css +139 -0
  109. data/themes/mira.css +139 -0
  110. data/themes/nova.css +139 -0
  111. data/themes/rhea.css +139 -0
  112. data/themes/sera.css +140 -0
  113. data/themes/vega.css +144 -0
  114. data/vendor/d3-kernel/LICENSE-d3.txt +17 -0
  115. data/vendor/d3-kernel/LICENSE-decimal.js-light.txt +25 -0
  116. data/vendor/d3-kernel/LICENSE-recharts.txt +26 -0
  117. data/vendor/d3-kernel/README.md +30 -0
  118. data/vendor/d3-kernel/build.mjs +66 -0
  119. data/vendor/d3-kernel/entry.js +27 -0
  120. data/vendor/d3-kernel/recharts/state/cartesianAxisSlice.ts +6 -0
  121. data/vendor/d3-kernel/recharts/util/scale/getNiceTickValues.ts +310 -0
  122. data/vendor/d3-kernel/recharts/util/scale/util/arithmetic.ts +54 -0
  123. data/vendor/d3-kernel/recharts/util/types.ts +5 -0
  124. metadata +186 -0
@@ -0,0 +1,251 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Charts
5
+ # The area chart family.
6
+ module AreaChart
7
+ # Renders an area chart - filled trends over an ordered axis,
8
+ # optionally stacked - as server-computed SVG through the Cartesian
9
+ # pipeline: the finished chart is in the initial HTML. Slots
10
+ # compose the whole chart declaratively: grid, axes, series,
11
+ # legend, tooltip.
12
+ #
13
+ # @example Two stacked series with a formatted month axis
14
+ # <%= poetry_area_chart(data:, config:, margin: { left: 12, right: 12 }) do |c| %>
15
+ # <% c.with_grid %>
16
+ # <% c.with_x_axis data_key: :month, tick_formatter: ->(v) { v[0, 3] } %>
17
+ # <% c.with_area data_key: :desktop, stack: :a %>
18
+ # <% c.with_area data_key: :mobile, stack: :a %>
19
+ # <% c.with_legend %>
20
+ # <% end %>
21
+ #
22
+ # Series colors ride var(--color-<key>) (the Container emission);
23
+ # per-x pixel coordinates are embedded for the tooltip controller.
24
+ class Component < Poetry::Core::Component
25
+ include Poetry::Charts::ChartFamily
26
+ include Poetry::Charts::TooltipWiring
27
+ include Poetry::Charts::Motion
28
+ include Poetry::Charts::Live
29
+ include Poetry::Charts::ReferenceMarks
30
+
31
+ # Projected into the registry and agent surface.
32
+ AGENT_RULES = [
33
+ "Compose from slots: with_grid / with_x_axis(data_key:) / with_area(data_key:) / with_legend.",
34
+ "Stack areas by giving them the same stack: id; offset: :expand makes the stack percent-based.",
35
+ "Colors come from the config - never set fill/stroke on an area directly.",
36
+ "gradient: true on an area gets the source's 5%/95% fade fill.",
37
+ "Charts render complete on the server; the tooltip layer attaches separately.",
38
+ "Entrance animation is on by default (source parity); animate: false for a static chart. " \
39
+ "Reduced-motion users always get the finished chart."
40
+ ].freeze
41
+
42
+ # One with_area call's captured series config.
43
+ Series = Data.define(:key, :stack, :curve, :fill_opacity, :gradient, :stroke_width)
44
+
45
+ option :data, ActiveModel::Type::Value.new, required: true,
46
+ doc: "The rows to plot: an array of hashes, one per x category."
47
+ option :config, ActiveModel::Type::Value.new, required: true,
48
+ doc: "The series config - key => { label:, color: } - naming " \
49
+ "and coloring every series."
50
+ option :id, :string,
51
+ doc: "Explicit DOM id token, stable across renders; otherwise the chart gets a unique per-render id."
52
+ option :width, :integer, default: 640,
53
+ doc: "ViewBox width in pixels; the rendered chart scales to its container."
54
+ option :height, :integer, default: 360, doc: "ViewBox height in pixels."
55
+ option :margin, ActiveModel::Type::Value.new,
56
+ doc: "Plot margin overrides ({ top:, right:, bottom:, left: }), merged over the defaults."
57
+ option :offset, :symbol, default: :none,
58
+ doc: "Stack baseline mode - :expand normalizes each stack to percentages."
59
+ option :label, :string,
60
+ doc: "Accessible name for the chart SVG; defaults to one built from the configured series."
61
+
62
+ motion_options
63
+ live_option
64
+
65
+ validates :offset, inclusion: { in: Cartesian::OFFSETS }
66
+
67
+ part "chart-svg", "The chart canvas (<svg>) - server-computed geometry in a fixed viewBox; " \
68
+ "role=img, or the focusable role=application accessibilityLayer when the " \
69
+ "tooltip attaches",
70
+ states: {
71
+ "data-animate" => "present when animate (the default) - the motion stylesheet and " \
72
+ "controller key the entrance off it",
73
+ "data-motion" => { condition: "runtime - the motion rig stamps the animation " \
74
+ "lifecycle (entrance/morph, then settled)",
75
+ values: %w[entrance morph settled] }
76
+ },
77
+ vars: {
78
+ "--poetry-motion-duration" => "the entrance/morph duration (animation_duration, ms)",
79
+ "--poetry-motion-easing" => "the animation easing keyword (animation_easing)",
80
+ "--poetry-motion-delay" => "the pre-animation hold (animation_begin, ms)"
81
+ }
82
+ part "chart-motion-reveal", "The entrance clipPath rect (the ported area reveal) - the " \
83
+ "motion stylesheet scales it 0 -> 1; only when animate"
84
+ part "chart-grid", "The gridline group (with_grid) - horizontal and/or vertical rules " \
85
+ "across the plot"
86
+ part "chart-cursor", "The hover cursor, hidden until the tooltip controller positions " \
87
+ "and reveals it at the active index - a vertical rule or a " \
88
+ "translucent band rect (bar charts)"
89
+ part "chart-areas", "The area-mark group - a fill path plus top-curve stroke per series, " \
90
+ "clipped by the reveal rect while animating"
91
+ part "chart-area", "One series' fill path (var(--color-<key>) or its gradient)",
92
+ states: { "data-key" => "the series key" }
93
+ part "chart-area-stroke", "One series' top-curve stroke path (the source strokes the " \
94
+ "curve, never the area outline)",
95
+ states: { "data-key" => "the series key" }
96
+ part "chart-active-dots", "The hover-marker group (with_tooltip) - pre-rendered hidden " \
97
+ "circles for every series x index"
98
+ part "chart-active-dot", "One hover marker - display=none until the tooltip controller " \
99
+ "reveals the active index's dot",
100
+ states: {
101
+ "data-key" => "the series key",
102
+ "data-index" => "the datum index",
103
+ "data-active" => "runtime - rides the marker while its index is the active one"
104
+ }
105
+ part "chart-x-axis", "The x-axis tick-label group (with_x_axis)"
106
+ part "chart-y-axis", "The y-axis tick-label group (with_y_axis)"
107
+ part "chart-coordinates", "The embedded per-index geometry payload " \
108
+ "(<script type=application/json>) the tooltip controller " \
109
+ "reads - zero chart math in the browser"
110
+
111
+ renders_many :areas,
112
+ doc: "An area series bound to data_key:. Areas sharing a stack: id pile up; gradient: true " \
113
+ "fades the fill; curve: picks the interpolation.",
114
+ renders: lambda { |data_key:, stack: nil, curve: :natural, fill_opacity: 0.4,
115
+ gradient: false, stroke_width: 1|
116
+ raise ArgumentError, "unknown curve #{curve.inspect}" unless CURVES.include?(curve.to_sym)
117
+
118
+ (@series_entries ||= []) << Series.new(key: data_key.to_s, stack:, curve: curve.to_sym,
119
+ fill_opacity:, gradient:, stroke_width:)
120
+ nil
121
+ }
122
+
123
+ include Poetry::Charts::CartesianFamily
124
+
125
+ # The captured Series configs, forcing lazy slot evaluation.
126
+ # @api private
127
+ def series_entries
128
+ areas? # force slot evaluation
129
+ @series_entries ||= []
130
+ end
131
+
132
+ # The chart's cartesian geometry, built once per render.
133
+ # @api private
134
+ def cartesian
135
+ @cartesian ||= Cartesian.new(
136
+ data: data,
137
+ series: series_entries,
138
+ width: width,
139
+ height: height,
140
+ x_key: x_axis_config&.data_key,
141
+ margin: live_margin,
142
+ category_axis: x_axis?,
143
+ value_axis: y_axis?,
144
+ # The hidden implicit Y axis still drives the scale: default
145
+ # tick count 5; an explicit with_y_axis supplies its own.
146
+ y_tick_count: y_axis_config&.tick_count || 5,
147
+ offset: offset
148
+ )
149
+ end
150
+
151
+ # One series' fill path (the area between its baseline and top
152
+ # curve).
153
+ # @api private
154
+ def series_path(entry)
155
+ points = cartesian.points(entry)
156
+ Geometry::Area.new(
157
+ x: ->(p, _i) { p[:x] },
158
+ y0: ->(p, _i) { p[:y0] },
159
+ y1: ->(p, _i) { p[:y1] },
160
+ curve: entry.curve,
161
+ defined: ->(p, _i) { !p[:value].nan? }
162
+ ).path(points)
163
+ end
164
+
165
+ # The stroke rides a SEPARATE path along the top curve only -
166
+ # stroking the fill path's outline would also draw the left,
167
+ # right, and baseline edges.
168
+ # @api private
169
+ def series_stroke_path(entry)
170
+ points = cartesian.points(entry)
171
+ Geometry::Line.new(
172
+ x: ->(p, _i) { p[:x] },
173
+ y: ->(p, _i) { p[:y1] },
174
+ curve: entry.curve,
175
+ defined: ->(p, _i) { !p[:value].nan? }
176
+ ).path(points)
177
+ end
178
+
179
+ # One x tick's label, through the slot's formatter when given.
180
+ # @api private
181
+ def x_tick_label(category)
182
+ formatter = x_axis_config&.tick_formatter
183
+ formatter ? formatter.call(category).to_s : category.to_s
184
+ end
185
+
186
+ # One y tick's label, through the slot's formatter when given.
187
+ # @api private
188
+ def y_tick_label(tick)
189
+ formatter = y_axis_config&.tick_formatter
190
+ formatter ? formatter.call(tick).to_s : Geometry.js_number(tick.to_f)
191
+ end
192
+
193
+ # The per-series gradient def's id, scoped by the chart id.
194
+ # @api private
195
+ def gradient_id(entry)
196
+ "#{chart_id}-fill-#{entry.key}"
197
+ end
198
+
199
+ # The series fill: its gradient url when gradient:, else its
200
+ # config color var.
201
+ # @api private
202
+ def fill_for(entry)
203
+ entry.gradient ? "url(##{gradient_id(entry)})" : "var(--color-#{entry.key})"
204
+ end
205
+
206
+ # ChartFamily#svg_label's chart-type lead-in.
207
+ # @api private
208
+ def svg_label_prefix = "Area chart"
209
+
210
+ # The embedded per-index geometry payload the tooltip controller
211
+ # reads.
212
+ # @api private
213
+ def coordinates_json
214
+ cartesian.coordinates.to_json
215
+ end
216
+
217
+ # -- live mode ---------------------------------------------------
218
+
219
+ # The chart type in the live spec.
220
+ # @api private
221
+ def live_type = :area
222
+
223
+ # The series list serialized into the live spec.
224
+ # @api private
225
+ def live_series
226
+ series_entries.map { |e| { data_key: e.key, stack: e.stack, curve: e.curve }.compact }
227
+ end
228
+
229
+ # The axis config serialized into the live spec.
230
+ # @api private
231
+ def live_axes
232
+ axes = {}
233
+ axes[:x] = { data_key: x_axis_config.data_key } if x_axis_config&.data_key
234
+ axes[:y] = { tick_count: y_axis_config.tick_count } if y_axis_config
235
+ axes
236
+ end
237
+
238
+ # The x scale kind the live renderer rebuilds.
239
+ # @api private
240
+ def live_x_scale_type = :point
241
+ # Whether the live frame renders a category axis.
242
+ # @api private
243
+ def live_category_axis? = x_axis?
244
+
245
+ private :cartesian, :series_path, :series_stroke_path, :x_tick_label, :y_tick_label
246
+ private :gradient_id, :fill_for, :svg_label_prefix, :coordinates_json, :live_type, :live_series, :live_axes
247
+ private :live_x_scale_type, :live_category_axis?
248
+ end
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Charts
5
+ module AreaChart
6
+ # The area family across its variants: default (natural),
7
+ # linear, step, stacked, percent-stacked, gradient, legend, and the
8
+ # visible-axes variant. Shared sample data (visitors by month).
9
+ class Preview < Poetry::Core::Preview::Base
10
+ DATA = [
11
+ { month: "January", desktop: 186, mobile: 80 },
12
+ { month: "February", desktop: 305, mobile: 200 },
13
+ { month: "March", desktop: 237, mobile: 120 },
14
+ { month: "April", desktop: 73, mobile: 190 },
15
+ { month: "May", desktop: 209, mobile: 130 },
16
+ { month: "June", desktop: 214, mobile: 140 }
17
+ ].freeze
18
+
19
+ ONE = { desktop: { label: "Desktop", color: "var(--chart-1)" } }.freeze
20
+ TWO = {
21
+ desktop: { label: "Desktop", color: "var(--chart-1)" },
22
+ mobile: { label: "Mobile", color: "var(--chart-2)" }
23
+ }.freeze
24
+
25
+ MARGIN = { left: 12, right: 12 }.freeze
26
+
27
+ def default
28
+ simple_chart(id: "area-default", tooltip: true)
29
+ end
30
+
31
+ def linear
32
+ simple_chart(id: "area-linear", curve: :linear)
33
+ end
34
+
35
+ def step
36
+ simple_chart(id: "area-step", curve: :step)
37
+ end
38
+
39
+ def stacked
40
+ two_series_chart(id: "area-stacked")
41
+ end
42
+
43
+ def stacked_expanded
44
+ two_series_chart(id: "area-expand", offset: :expand)
45
+ end
46
+
47
+ def gradient
48
+ two_series_chart(id: "area-gradient", gradient: true)
49
+ end
50
+
51
+ def legend
52
+ two_series_chart(id: "area-legend", &:with_legend)
53
+ end
54
+
55
+ def axes
56
+ two_series_chart(id: "area-axes") { |chart| chart.with_y_axis(tick_count: 3) }
57
+ end
58
+
59
+ # The full live-window surface in one frame (see BarChart::Preview#
60
+ # live_window) - no tick_formatter: lambdas cannot ride the payload.
61
+ def live_window
62
+ render_component(data: DATA, config: ONE, id: "area-live", margin: MARGIN, live: true,
63
+ zoom: true, sync: "area-demo") do |chart|
64
+ chart.with_grid
65
+ chart.with_x_axis(data_key: :month)
66
+ chart.with_area(data_key: :desktop)
67
+ chart.with_tooltip(indicator: :line)
68
+ chart.with_brush
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def simple_chart(id:, curve: :natural, tooltip: false)
75
+ render_component(data: DATA, config: ONE, id: id, margin: MARGIN) do |chart|
76
+ chart.with_grid
77
+ chart.with_x_axis(data_key: :month, tick_formatter: ->(v) { v[0, 3] })
78
+ chart.with_area(data_key: :desktop, curve: curve)
79
+ chart.with_tooltip(indicator: :line) if tooltip
80
+ end
81
+ end
82
+
83
+ def two_series_chart(id:, offset: :none, gradient: false)
84
+ render_component(data: DATA, config: TWO, id: id, offset: offset, margin: MARGIN) do |chart|
85
+ chart.with_grid
86
+ chart.with_x_axis(data_key: :month, tick_formatter: ->(v) { v[0, 3] })
87
+ chart.with_area(data_key: :mobile, stack: :a, gradient: gradient)
88
+ chart.with_area(data_key: :desktop, stack: :a, gradient: gradient)
89
+ yield chart if block_given?
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Charts
5
+ module AreaChart
6
+ # The cartesian chart chrome, riding the SHARED cn-chart-* theme
7
+ # rules - what would otherwise be container-level descendant
8
+ # overrides lands as direct classes on parts poetry owns. Series
9
+ # fill/stroke are var(--color-<key>) ATTRIBUTES, never classes.
10
+ class Style < Poetry::Core::Style
11
+ base ""
12
+
13
+ element :frame, "contents"
14
+ element :svg, "w-full min-h-0 flex-1"
15
+ element :grid_line, "cn-chart-grid-line"
16
+ element :tick, "cn-chart-tick"
17
+ element :cursor_line, "cn-chart-cursor-line"
18
+ element :cursor_band, "cn-chart-cursor-band"
19
+ element :reference_line, "cn-chart-reference-line"
20
+ element :reference_area, "cn-chart-reference-area"
21
+ element :reference_dot, "cn-chart-reference-dot"
22
+ element :error_bar, "cn-chart-error-bar"
23
+ element :brush_track, "cn-chart-brush-track"
24
+ element :brush_window, "cn-chart-brush-window"
25
+ element :brush_handle, "cn-chart-brush-handle"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,95 @@
1
+ <%= render Poetry::Charts::Container::Component.new(config: chart_config, id: chart_id.delete_prefix("chart-")) do %>
2
+ <%= content_tag(:div, frame_attributes.merge("class" => css(:frame))) do %>
3
+ <svg viewBox="0 0 <%= width %> <%= height %>" class="<%= css(:svg) %>" data-slot="chart-svg" aria-label="<%= svg_label %>" preserveAspectRatio="xMidYMid meet" <%= tag.attributes(svg_interaction_attributes) %>>
4
+ <% if grid? %>
5
+ <g data-slot="chart-grid" aria-hidden="true">
6
+ <% if horizontal? %>
7
+ <%# Value ticks run along X in the horizontal layout. %>
8
+ <% if grid_config.vertical %>
9
+ <% cartesian.y_ticks.each do |tick| %>
10
+ <line class="<%= css(:grid_line) %>" x1="<%= fnum(cartesian.y_scale.call(tick)) %>" x2="<%= fnum(cartesian.y_scale.call(tick)) %>" y1="<%= fnum(cartesian.plot_top) %>" y2="<%= fnum(cartesian.plot_bottom) %>" />
11
+ <% end %>
12
+ <% end %>
13
+ <% if grid_config.horizontal %>
14
+ <% cartesian.x_centers.each do |y| %>
15
+ <line class="<%= css(:grid_line) %>" x1="<%= fnum(cartesian.plot_left) %>" x2="<%= fnum(cartesian.plot_right) %>" y1="<%= fnum(y) %>" y2="<%= fnum(y) %>" />
16
+ <% end %>
17
+ <% end %>
18
+ <% else %>
19
+ <% if grid_config.horizontal %>
20
+ <% cartesian.y_ticks.each do |tick| %>
21
+ <line class="<%= css(:grid_line) %>" x1="<%= fnum(cartesian.plot_left) %>" x2="<%= fnum(cartesian.plot_right) %>" y1="<%= fnum(cartesian.y_scale.call(tick)) %>" y2="<%= fnum(cartesian.y_scale.call(tick)) %>" />
22
+ <% end %>
23
+ <% end %>
24
+ <% if grid_config.vertical %>
25
+ <% cartesian.x_centers.each do |x| %>
26
+ <line class="<%= css(:grid_line) %>" x1="<%= fnum(x) %>" x2="<%= fnum(x) %>" y1="<%= fnum(cartesian.plot_top) %>" y2="<%= fnum(cartesian.plot_bottom) %>" />
27
+ <% end %>
28
+ <% end %>
29
+ <% end %>
30
+ </g>
31
+ <% end %>
32
+ <%= cursor_svg(:band) %>
33
+ <g data-slot="chart-bars">
34
+ <% series_entries.each do |entry| %>
35
+ <g data-slot="chart-bar-series" data-key="<%= entry.key %>">
36
+ <% cells(entry).each do |cell| %>
37
+ <path data-slot="chart-bar" data-key="<%= entry.key %>" data-index="<%= cell[:index] %>" d="<%= bar_path(entry, cell) %>" fill="<%= cell_fill(entry, cell) %>"<% if animate? %> data-motion-origin="<%= motion_origin(cell) %>"<% end %><% if active?(entry, cell) %> fill-opacity="0.8" stroke="<%= cell_fill(entry, cell) %>" stroke-width="<%= entry.stroke_width %>" stroke-dasharray="4" stroke-dashoffset="4" data-active=""<% end %> />
38
+ <% end %>
39
+ </g>
40
+ <% if entry.error_key && !horizontal? %>
41
+ <g data-slot="chart-error-bars" data-key="<%= entry.key %>" aria-hidden="true">
42
+ <% cells(entry).each do |cell| %>
43
+ <% range = error_range(cell[:row], entry.error_key, cell[:value]) %>
44
+ <% if range %>
45
+ <path class="<%= css(:error_bar) %>" d="<%= error_bar_path(cell[:x] + (cell[:width] / 2.0), cartesian.y_scale.call(range[0]), cartesian.y_scale.call(range[1]), entry.error_width) %>" fill="none" />
46
+ <% end %>
47
+ <% end %>
48
+ </g>
49
+ <% end %>
50
+ <% if entry.labels %>
51
+ <g data-slot="chart-labels" data-key="<%= entry.key %>">
52
+ <% cells(entry).each do |cell| %>
53
+ <text class="<%= css(:label) %>" x="<%= fnum(cell[:x] + (cell[:width] / 2.0)) %>" y="<%= fnum(cell[:y] - 12) %>" text-anchor="middle"><%= cell_label(entry, cell) %></text>
54
+ <% end %>
55
+ </g>
56
+ <% end %>
57
+ <% end %>
58
+ </g>
59
+ <%= reference_marks_svg %>
60
+ <% if horizontal? %>
61
+ <% if y_axis? %>
62
+ <%# The category labels down the left strip (the horizontal/mixed blocks). %>
63
+ <g data-slot="chart-y-axis">
64
+ <% cartesian.categories.each_with_index do |category, i| %>
65
+ <text class="<%= css(:tick) %>" x="<%= fnum(cartesian.plot_left - y_axis_config.tick_margin) %>" y="<%= fnum(cartesian.x_centers[i]) %>" dy="0.355em" text-anchor="end"><%= category_tick_label(category) %></text>
66
+ <% end %>
67
+ </g>
68
+ <% end %>
69
+ <% else %>
70
+ <% if x_axis? %>
71
+ <g data-slot="chart-x-axis">
72
+ <% cartesian.categories.each_with_index do |category, i| %>
73
+ <text class="<%= css(:tick) %>" x="<%= fnum(cartesian.x_centers[i]) %>" y="<%= fnum(cartesian.plot_bottom + x_axis_config.tick_margin) %>" dy="0.71em" text-anchor="middle"><%= x_tick_label(category) %></text>
74
+ <% end %>
75
+ </g>
76
+ <% end %>
77
+ <% if y_axis? %>
78
+ <g data-slot="chart-y-axis">
79
+ <% cartesian.y_ticks.each do |tick| %>
80
+ <text class="<%= css(:tick) %>" x="<%= fnum(cartesian.plot_left - y_axis_config.tick_margin) %>" y="<%= fnum(cartesian.y_scale.call(tick)) %>" dy="0.355em" text-anchor="end"><%= y_tick_label(tick) %></text>
81
+ <% end %>
82
+ </g>
83
+ <% end %>
84
+ <% end %>
85
+ <%= brush_svg %>
86
+ <%= zoom_selection_svg %>
87
+ </svg>
88
+ <% if legend? %>
89
+ <%= render legend_component %>
90
+ <% end %>
91
+ <% if tooltip? %><%= render tooltip_layer_component %><% end %>
92
+ <script type="application/json" data-slot="chart-coordinates" <%= tag.attributes(stimulus_attributes_for(:coordinates)) %>><%= script_json(coordinates_json) %></script>
93
+ <% if live? %><script type="application/json" data-slot="chart-live-payload" <%= tag.attributes(stimulus_attributes_for(:live_payload)) %>><%= script_json(live_payload_json) %></script>
94
+ <% end %> <% end %>
95
+ <% end %>