Module: RailsuiCharts::ChartHelper
- Defined in:
- lib/railsui_charts/chart_helper.rb
Constant Summary collapse
- CIRCULAR_TYPES =
Forms whose placeholder should be a disc rather than a plotting area.
%i[pie donut polar_area radar].freeze
Instance Method Summary collapse
- #railsui_chart(data, type: :line, **options) ⇒ Object
-
#railsui_chart_empty(title: nil, description: nil, height: nil) ⇒ Object
Holds the chart's footprint so a card keeps its shape whether or not the query came back with anything.
- #railsui_chart_error(title: nil, description: nil, height: nil) ⇒ Object
-
#railsui_chart_skeleton(height: nil, type: :line, label: "Loading chart", align: :left) ⇒ Object
Server-rendered placeholder for a chart whose data has not arrived yet — the thing a Turbo frame shows before it swaps in the real one.
-
#railsui_small_multiples(series, type: :line, height: 120, columns: 3, **options) ⇒ Object
One small chart per series, sharing a y-scale.
-
#shared_scale(entries) ⇒ Object
Facets only compare if they share a scale.
Instance Method Details
#railsui_chart(data, type: :line, **options) ⇒ Object
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/railsui_charts/chart_helper.rb', line 8 def railsui_chart(data, type: :line, **) # A chart whose series are not the numbers a reader wants can say so. # Deleted rather than read, so it never reaches the builder and never # gets serialised into the Stimulus value alongside the real config. explicit_table = .delete(:accessible_table) # An empty dataset is a normal day one, not an error. Rendering axes # around nothing looks like a chart that failed rather than a chart with # nothing to show yet. return railsui_chart_empty(**()) if ApexOptionsBuilder.blank?(data) config = ApexOptionsBuilder.new(data, type: type, **).build id = [:id] || "rui-chart-#{SecureRandom.hex(4)}" table = if [:accessible] == false nil elsif explicit_table.present? explicit_accessibility_table(explicit_table, id: id) else accessibility_table(config, id: id) end content_tag(:div, id: id, class: "railsui-chart", # Charts draw when they scroll into view, so the space has to # be held from the start. Without it the page is short on # load and grows under the reader as they scroll. style: reserved_height(config), data: { controller: "railsui-chart", "railsui-chart-options-value": config.to_json }) do table end end |
#railsui_chart_empty(title: nil, description: nil, height: nil) ⇒ Object
Holds the chart's footprint so a card keeps its shape whether or not the query came back with anything.
86 87 88 |
# File 'lib/railsui_charts/chart_helper.rb', line 86 def railsui_chart_empty(title: nil, description: nil, height: nil) chart_state(:empty, title: title || "No data", description: description, height: height) end |
#railsui_chart_error(title: nil, description: nil, height: nil) ⇒ Object
90 91 92 |
# File 'lib/railsui_charts/chart_helper.rb', line 90 def railsui_chart_error(title: nil, description: nil, height: nil) chart_state(:error, title: title || "Couldn't load this chart", description: description, height: height) end |
#railsui_chart_skeleton(height: nil, type: :line, label: "Loading chart", align: :left) ⇒ Object
Server-rendered placeholder for a chart whose data has not arrived yet — the thing a Turbo frame shows before it swaps in the real one. It keeps the chart footprint and avoids drawing fake chart geometry before data exists.
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/railsui_charts/chart_helper.rb', line 98 def railsui_chart_skeleton(height: nil, type: :line, label: "Loading chart", align: :left) alignment = skeleton_alignment(align) content_tag(:div, class: "railsui-chart-state railsui-chart-state--loading railsui-chart-state--loading-#{alignment}", style: state_height(height), role: "status", aria: { label: label, busy: true }) do content_tag(:div, class: "railsui-chart-skeleton", aria: { hidden: true }) do safe_join([ content_tag(:span, "", class: "railsui-skeleton-bar railsui-skeleton-bar--label"), content_tag(:span, "", class: "railsui-skeleton-bar railsui-skeleton-bar--value"), content_tag(:span, "", class: "railsui-skeleton-bar railsui-skeleton-bar--meta"), content_tag(:span, "", class: "railsui-chart-skeleton__plot"), content_tag(:span, "", class: "railsui-skeleton-bar railsui-skeleton-bar--footer") ]) end end end |
#railsui_small_multiples(series, type: :line, height: 120, columns: 3, **options) ⇒ Object
One small chart per series, sharing a y-scale.
This is the honest answer when there are more categories than a single chart can hold. Eight lines on one axis is a plate of spaghetti, and a ninth colour is not distinguishable from the others anyway — facets scale where colour does not.
<%= railsui_small_multiples @plans, type: :area, columns: 3 %>
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/railsui_charts/chart_helper.rb', line 51 def railsui_small_multiples(series, type: :line, height: 120, columns: 3, **) return railsui_chart_empty(**()) if ApexOptionsBuilder.blank?(series) entries = Array(series) shared = shared_scale(entries) content_tag(:div, class: "railsui-small-multiples", style: "--rui-small-multiple-columns: #{columns.to_i}") do safe_join(entries.each_with_index.map do |entry, index| content_tag(:div, class: "railsui-small-multiple") do safe_join([ content_tag(:p, entry[:name] || entry["name"] || "Series #{index + 1}", class: "railsui-small-multiple__title"), # Every facet takes the same colour. The title carries identity # here, so spending a hue on it would say nothing extra. railsui_chart(entry[:data] || entry["data"], type: type, height: height, yaxis: shared.merge(tickAmount: 3), **) ]) end end) end end |
#shared_scale(entries) ⇒ Object
Facets only compare if they share a scale. Left to themselves each one would fit its own data and a small series would look like a large one.
74 75 76 77 78 79 80 81 82 |
# File 'lib/railsui_charts/chart_helper.rb', line 74 def shared_scale(entries) values = entries.flat_map { |entry| Array(entry[:data] || entry["data"]).map { |point| ApexOptionsBuilder.value_of(point) } }.compact return {} if values.empty? min, max = values.minmax return {} if min == max { min: min, max: max } end |