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.
- checksums.yaml +7 -0
- data/.gitattributes +6 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +94 -0
- data/THIRD_PARTY_NOTICES.md +39 -0
- data/app/assets/stylesheets/poetry-charts.css +117 -0
- data/app/components/poetry/charts/adapter_chart/component.html.erb +6 -0
- data/app/components/poetry/charts/adapter_chart/component.rb +99 -0
- data/app/components/poetry/charts/adapter_chart/preview.rb +37 -0
- data/app/components/poetry/charts/adapter_chart/style.rb +16 -0
- data/app/components/poetry/charts/area_chart/component.html.erb +75 -0
- data/app/components/poetry/charts/area_chart/component.rb +251 -0
- data/app/components/poetry/charts/area_chart/preview.rb +95 -0
- data/app/components/poetry/charts/area_chart/style.rb +29 -0
- data/app/components/poetry/charts/bar_chart/component.html.erb +95 -0
- data/app/components/poetry/charts/bar_chart/component.rb +318 -0
- data/app/components/poetry/charts/bar_chart/preview.rb +115 -0
- data/app/components/poetry/charts/bar_chart/style.rb +28 -0
- data/app/components/poetry/charts/bar_math.rb +95 -0
- data/app/components/poetry/charts/cartesian_family.rb +105 -0
- data/app/components/poetry/charts/chart_family.rb +58 -0
- data/app/components/poetry/charts/composed_chart/component.html.erb +95 -0
- data/app/components/poetry/charts/composed_chart/component.rb +309 -0
- data/app/components/poetry/charts/composed_chart/preview.rb +62 -0
- data/app/components/poetry/charts/composed_chart/style.rb +24 -0
- data/app/components/poetry/charts/container/component.html.erb +4 -0
- data/app/components/poetry/charts/container/component.rb +80 -0
- data/app/components/poetry/charts/container/preview.rb +45 -0
- data/app/components/poetry/charts/container/style.rb +22 -0
- data/app/components/poetry/charts/error_bars.rb +35 -0
- data/app/components/poetry/charts/legend_content/component.html.erb +10 -0
- data/app/components/poetry/charts/legend_content/component.rb +122 -0
- data/app/components/poetry/charts/legend_content/preview.rb +23 -0
- data/app/components/poetry/charts/legend_content/style.rb +26 -0
- data/app/components/poetry/charts/line_chart/component.html.erb +85 -0
- data/app/components/poetry/charts/line_chart/component.rb +252 -0
- data/app/components/poetry/charts/line_chart/preview.rb +115 -0
- data/app/components/poetry/charts/line_chart/style.rb +28 -0
- data/app/components/poetry/charts/live.rb +296 -0
- data/app/components/poetry/charts/motion.rb +98 -0
- data/app/components/poetry/charts/pie_chart/component.html.erb +36 -0
- data/app/components/poetry/charts/pie_chart/component.rb +256 -0
- data/app/components/poetry/charts/pie_chart/preview.rb +100 -0
- data/app/components/poetry/charts/pie_chart/style.rb +19 -0
- data/app/components/poetry/charts/polar_family.rb +95 -0
- data/app/components/poetry/charts/radar_chart/component.html.erb +73 -0
- data/app/components/poetry/charts/radar_chart/component.rb +329 -0
- data/app/components/poetry/charts/radar_chart/preview.rb +97 -0
- data/app/components/poetry/charts/radar_chart/style.rb +21 -0
- data/app/components/poetry/charts/radial_bar_chart/component.html.erb +65 -0
- data/app/components/poetry/charts/radial_bar_chart/component.rb +424 -0
- data/app/components/poetry/charts/radial_bar_chart/preview.rb +99 -0
- data/app/components/poetry/charts/radial_bar_chart/style.rb +27 -0
- data/app/components/poetry/charts/reference_marks.rb +156 -0
- data/app/components/poetry/charts/scatter_chart/component.html.erb +55 -0
- data/app/components/poetry/charts/scatter_chart/component.rb +396 -0
- data/app/components/poetry/charts/scatter_chart/preview.rb +93 -0
- data/app/components/poetry/charts/scatter_chart/style.rb +22 -0
- data/app/components/poetry/charts/tooltip_content/component.html.erb +19 -0
- data/app/components/poetry/charts/tooltip_content/component.rb +168 -0
- data/app/components/poetry/charts/tooltip_content/preview.rb +39 -0
- data/app/components/poetry/charts/tooltip_content/style.rb +31 -0
- data/app/components/poetry/charts/tooltip_layer/component.html.erb +6 -0
- data/app/components/poetry/charts/tooltip_layer/component.rb +61 -0
- data/app/components/poetry/charts/tooltip_layer/style.rb +13 -0
- data/app/components/poetry/charts/tooltip_wiring.rb +167 -0
- data/app/helpers/poetry/charts/components_helper.rb +125 -0
- data/app/javascript/poetry/charts/adapter_controller.js +75 -0
- data/app/javascript/poetry/charts/adapter_registry.js +52 -0
- data/app/javascript/poetry/charts/adapters/chartjs.js +86 -0
- data/app/javascript/poetry/charts/d3.js +14 -0
- data/app/javascript/poetry/charts/index.js +67 -0
- data/app/javascript/poetry/charts/live/renderer.js +456 -0
- data/app/javascript/poetry/charts/live_controller.js +195 -0
- data/app/javascript/poetry/charts/motion/flip.js +169 -0
- data/app/javascript/poetry/charts/motion/sector.js +62 -0
- data/app/javascript/poetry/charts/motion/tween.js +105 -0
- data/app/javascript/poetry/charts/motion_controller.js +230 -0
- data/app/javascript/poetry/charts/tooltip_controller.js +331 -0
- data/app/javascript/poetry/charts/window_controller.js +195 -0
- data/config/component_descriptions.yml +14 -0
- data/config/component_registry.yml +3055 -0
- data/config/controllers_manifest.json +100 -0
- data/config/importmap.rb +10 -0
- data/lib/poetry/charts/cartesian.rb +277 -0
- data/lib/poetry/charts/config.rb +149 -0
- data/lib/poetry/charts/engine.rb +76 -0
- data/lib/poetry/charts/geometry/area.rb +68 -0
- data/lib/poetry/charts/geometry/curve.rb +327 -0
- data/lib/poetry/charts/geometry/line.rb +64 -0
- data/lib/poetry/charts/geometry/nice_ticks.rb +220 -0
- data/lib/poetry/charts/geometry/path.rb +96 -0
- data/lib/poetry/charts/geometry/scale/band.rb +81 -0
- data/lib/poetry/charts/geometry/scale/linear.rb +127 -0
- data/lib/poetry/charts/geometry/stack.rb +129 -0
- data/lib/poetry/charts/geometry/ticks.rb +103 -0
- data/lib/poetry/charts/geometry.rb +66 -0
- data/lib/poetry/charts/polar.rb +179 -0
- data/lib/poetry/charts/spec.rb +108 -0
- data/lib/poetry/charts/theme_style.rb +53 -0
- data/lib/poetry/charts/version.rb +8 -0
- data/lib/poetry/charts.rb +114 -0
- data/lib/poetry-charts.rb +4 -0
- data/themes/default.css +146 -0
- data/themes/luma.css +139 -0
- data/themes/lyra.css +140 -0
- data/themes/maia.css +139 -0
- data/themes/mira.css +139 -0
- data/themes/nova.css +139 -0
- data/themes/rhea.css +139 -0
- data/themes/sera.css +140 -0
- data/themes/vega.css +144 -0
- data/vendor/d3-kernel/LICENSE-d3.txt +17 -0
- data/vendor/d3-kernel/LICENSE-decimal.js-light.txt +25 -0
- data/vendor/d3-kernel/LICENSE-recharts.txt +26 -0
- data/vendor/d3-kernel/README.md +30 -0
- data/vendor/d3-kernel/build.mjs +66 -0
- data/vendor/d3-kernel/entry.js +27 -0
- data/vendor/d3-kernel/recharts/state/cartesianAxisSlice.ts +6 -0
- data/vendor/d3-kernel/recharts/util/scale/getNiceTickValues.ts +310 -0
- data/vendor/d3-kernel/recharts/util/scale/util/arithmetic.ts +54 -0
- data/vendor/d3-kernel/recharts/util/types.ts +5 -0
- 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 %>
|