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

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
# File 'lib/railsui_charts/chart_helper.rb', line 8

def railsui_chart(data, type: :line, **options)
  # 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(**empty_options(options)) if ApexOptionsBuilder.blank?(data)

  config = ApexOptionsBuilder.new(data, type: type, **options).build
  id = options[:id] || "rui-chart-#{SecureRandom.hex(4)}"

  table = options[:accessible] != false ? accessibility_table(config, id: id) : nil

  (: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.



74
75
76
# File 'lib/railsui_charts/chart_helper.rb', line 74

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



78
79
80
# File 'lib/railsui_charts/chart_helper.rb', line 78

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.



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/railsui_charts/chart_helper.rb', line 86

def railsui_chart_skeleton(height: nil, type: :line, label: "Loading chart", align: :left)
  alignment = skeleton_alignment(align)

  (: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
    (:div, class: "railsui-chart-skeleton", aria: { hidden: true }) do
      safe_join([
        (:span, "", class: "railsui-skeleton-bar railsui-skeleton-bar--label"),
        (:span, "", class: "railsui-skeleton-bar railsui-skeleton-bar--value"),
        (:span, "", class: "railsui-skeleton-bar railsui-skeleton-bar--meta"),
        (:span, "", class: "railsui-chart-skeleton__plot"),
        (: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 %>


39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/railsui_charts/chart_helper.rb', line 39

def railsui_small_multiples(series, type: :line, height: 120, columns: 3, **options)
  return railsui_chart_empty(**empty_options(options)) if ApexOptionsBuilder.blank?(series)

  entries = Array(series)
  shared = shared_scale(entries)

  (:div, class: "railsui-small-multiples", style: "--rui-small-multiple-columns: #{columns.to_i}") do
    safe_join(entries.each_with_index.map do |entry, index|
      (:div, class: "railsui-small-multiple") do
        safe_join([
          (: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), **options)
        ])
      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.



62
63
64
65
66
67
68
69
70
# File 'lib/railsui_charts/chart_helper.rb', line 62

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