Module: PredictabilityEngine::VegaVisualizer::CfdLayout
- Defined in:
- lib/predictability_engine/vega_visualizer/cfd_layout.rb
Overview
Layout and data logic for CFD charts in Vega-Lite.
Class Method Summary collapse
- .area_layer(pcts, legend: true) ⇒ Object
- .build_unified_data(data, percentiles) ⇒ Object
- .color_scale(pcts) ⇒ Object
- .line_layer ⇒ Object
- .vert_data(forecast, percentiles) ⇒ Object
- .vert_layers(forecast, percentiles) ⇒ Object
Class Method Details
.area_layer(pcts, legend: true) ⇒ Object
32 33 34 35 36 37 38 39 40 |
# File 'lib/predictability_engine/vega_visualizer/cfd_layout.rb', line 32 def self.area_layer(pcts, legend: true) cfg = { field: 'type', type: 'nominal' } cfg[:legend] = { title: 'Flow & Forecast', orient: 'bottom', columns: 3 } if legend && !pcts.empty? { mark: { type: 'area' }, encoding: { y: VegaVisualizer.quantitative_y_axis('count', title: 'Total Items', stack: nil), color: cfg, order: { field: 'order', type: 'quantitative' }, tooltip: VegaVisualizer.cfd_tooltip_fields } } end |
.build_unified_data(data, percentiles) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# File 'lib/predictability_engine/vega_visualizer/cfd_layout.rb', line 7 def self.build_unified_data(data, percentiles) res = [] sorted_pcts = percentiles.sort data[:dates].each_with_index do |date, i| res << { date: PredictabilityEngine.format_date(date), count: data[:arrivals][i], type: 'Arrivals', order: 0 } sorted_pcts.each_with_index do |p, pi| res << { date: PredictabilityEngine.format_date(date), count: data[:forecasts][p][i], type: "#{p}% Confidence", order: pi + 1 } end if i < data[:departed].size res << { date: PredictabilityEngine.format_date(date), count: data[:departed][i], type: 'Departures', order: sorted_pcts.size + 1 } end end res end |
.color_scale(pcts) ⇒ Object
24 25 26 27 28 29 30 |
# File 'lib/predictability_engine/vega_visualizer/cfd_layout.rb', line 24 def self.color_scale(pcts) sorted_pcts = pcts.sort dom = ['Arrivals'] + sorted_pcts.map { |p| "#{p}% Confidence" } + ['Departures'] palette = ['#72b7b2', '#e45756', '#b279a2', '#ff9da7', '#ad494a', '#8ca27a'] range = ['#4c78a8'] + palette.take(sorted_pcts.size) + ['#59a14f'] [dom, range] end |
.line_layer ⇒ Object
42 43 44 45 46 47 48 49 50 |
# File 'lib/predictability_engine/vega_visualizer/cfd_layout.rb', line 42 def self.line_layer { mark: { type: 'line' }, encoding: { y: VegaVisualizer.quantitative_y_axis('count', title: 'Total Items'), strokeDash: { condition: { test: "datum.type == 'Arrivals' || datum.type == 'Departures'", value: [] }, value: [4, 4] }, tooltip: VegaVisualizer.cfd_tooltip_fields } } end |
.vert_data(forecast, percentiles) ⇒ Object
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/predictability_engine/vega_visualizer/cfd_layout.rb', line 86 def self.vert_data(forecast, percentiles) sorted_pcts = percentiles.sort data_by_date = group_pcts_by_date(forecast, sorted_pcts) # IMMUTABLE invariant — see CLAUDE.md §"Forecast alignment invariant". # Rule height = percentile-surface plateau (departed_so_far + wip), so each # vertical rule hits the top-right corner of its p% surface exactly. plateau = forecast[:summary][:departed_so_far] + forecast[:summary][:wip] data_by_date.sort_by { |date, _| date }.map do |date, p_list| date_str = PredictabilityEngine.format_date(date) label = "#{p_list.sort.map { |p| "#{p}%" }.join(', ')} (#{date_str})" { date: date_str, label: label, tooltip: p_list.map { |p| "#{p}% Confidence (#{date_str})" }.join("\n"), count: plateau } end end |
.vert_layers(forecast, percentiles) ⇒ Object
52 53 54 55 |
# File 'lib/predictability_engine/vega_visualizer/cfd_layout.rb', line 52 def self.vert_layers(forecast, percentiles) data = vert_data(forecast, percentiles) [rule_layer(data), text_layer(data)] end |